λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
React

Storybook + React, μž₯점뢀터 μ„€μΉ˜μ™€ λ¬Έμ„œ μž‘μ„±λ²• κ°€μ΄λ“œ

by LasBe 2024. 3. 12.
λ°˜μ‘ν˜•

πŸ“’ Storybook


Storybook은 UI μ»΄ν¬λ„ŒνŠΈλ₯Ό 개발, ν…ŒμŠ€νŠΈ 및 λ¬Έμ„œν™”ν•˜κΈ° μœ„ν•œ μ˜€ν”ˆ μ†ŒμŠ€ λ„κ΅¬μž…λ‹ˆλ‹€.

κ°œλ°œν•œ 곡톡 μ»΄ν¬λ„ŒνŠΈ ν˜Ήμ€ λ””μžμΈ μ‹œμŠ€ν…œμ„ ν…ŒμŠ€νŠΈν•˜κ³  μ‹œκ°μ μœΌλ‘œ 확인할 수 μžˆλŠ” ν™˜κ²½μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

μ΄λ²ˆμ— 전사 곡톡 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ„€κ³„ν•˜κ³  μ—¬λŸ¬ λ¬Έμ„œν™” νˆ΄μ„ κ³ λ―Όν•˜λ‹€ κ²°κ΅­ μŠ€ν† λ¦¬λΆμ„ μ„ νƒν•˜κ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

단 ν•œ 번만 μŠ€ν† λ¦¬ 파일과 mdx λ¬Έμ„œ 예제λ₯Ό μž‘μ„±ν•΄ 놓고 배포 μžλ™ν™”κΉŒμ§€ ν•΄λ†“μœΌλ‹ˆ μƒκ°ν–ˆλ˜ 것보닀 큰 어렀움은 μ—†μ—ˆκ³  λ§Œμ‘±λ„λŠ” ꡉμž₯히 λ†’μ•˜μŠ΅λ‹ˆλ‹€.

그럼 μŠ€ν† λ¦¬λΆμ˜ μž₯점과 μ‚¬μš©λ°©λ²•μ— λŒ€ν•΄ μ†Œκ°œν•©λ‹ˆλ‹€.

 

πŸ“Œ Storybook μž₯점

πŸ”Ž μ‹œκ°μ  ν…ŒμŠ€νŠΈ

백문이 λΆˆμ–΄μΌκ²¬.

각각의 μŠ€ν† λ¦¬λ₯Ό 톡해 μ»΄ν¬λ„ŒνŠΈλ“€μ˜ propsλ₯Ό 직접 μ‘°μž‘ν•˜λ©° μ‹œκ°μ μœΌλ‘œ 확인할 수 μžˆλŠ” ν™˜κ²½μ„ μ œκ³΅ν•©λ‹ˆλ‹€.

λ‹€μ–‘ν•œ μƒνƒœμ—μ„œ μ»΄ν¬λ„ŒνŠΈκ°€ μ–΄λ–»κ²Œ λ³΄μ΄λŠ”μ§€ 미리 ν™•μΈν•˜μ—¬ λ””μžμΈμ˜ 일관성을 μœ μ§€ν•  수 μžˆλ„λ‘ λ•μŠ΅λ‹ˆλ‹€.

λ˜ν•œ ν”„λ‘ νŠΈ 개발이 μ •ν™•ν•˜κ²Œ 무엇인지 λͺ¨λ₯΄λŠ” μΈμ›μ—κ²Œ νŠΉμ • μ»΄ν¬λ„ŒνŠΈλ₯Ό κ°œλ°œν•œλ‹€κ³  μ„€λͺ…ν•˜λŠ” 것이 λ‚œκ°ν•  λ•Œκ°€ ν•œλ‘ 번이 μ•„λ‹ˆμ—ˆλŠ”λ°, κ·Έλƒ₯ μ„€λͺ… 없이 두 눈으둜 ν™•μΈμ‹œμΌœ 쀄 수 μžˆλ‹€λŠ” μž₯점도 μžˆμŠ΅λ‹ˆλ‹€.

πŸ”Ž 독립적인 뢄리

μ‚¬λ‚΄μ—μ„œ κ³΅ν†΅μ μœΌλ‘œ μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλ“€μ„ μ •μ˜ν•΄ Storybook에 λ“±λ‘ν•˜λ € ν•œλ‹€λ©΄, 도메인에 상관없이 μ–΄λ– ν•œ ν”„λ‘œμ νŠΈμ—μ„œλ„ μ‚¬μš©ν•  수 μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.

그러기 μœ„ν•΄μ„œ μ»΄ν¬λ„ŒνŠΈλŠ” νŠΉμ • 관심사와 μ˜μ‘΄μ„±λ“€μ„ λͺ¨λ‘ μ œκ±°ν•΄ λ²”μš©μ„±μ΄ λ†’κ³  독립적이어야 ν•©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ μ»΄ν¬λ„ŒνŠΈλ“€μ„ μ„€κ³„ν•˜λ‹€ 보면 μžμ—°μŠ€λ ˆ μž¬μ‚¬μš©μ„±μ΄ 높은 μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€ 수 μžˆλŠ” 사고λ ₯κ³Ό μ‹€λ ₯이 점차 ν–₯μƒλ©λ‹ˆλ‹€.

πŸ”Ž μœ μ§€λ³΄μˆ˜

μ»΄ν¬λ„ŒνŠΈκ°€ λ…λ¦½μ μ΄λΌλŠ” 것은 μ½”λ“œλ₯Ό 집쀑화해, ν•œ κ³³μ—μ„œ 관리할 수 μžˆλ‹€λŠ” μ—„μ²­λ‚œ μž₯점이 μžˆμŠ΅λ‹ˆλ‹€.

이에 집쀑화 된 μ½”λ“œλ₯Ό 배포 ν›„ μ² μ €ν•œ 버전관리λ₯Ό ν•¨μœΌλ‘œμ¨ 일관성 μžˆλŠ” μœ μ§€λ³΄μˆ˜κ°€ κ°€λŠ₯ν•΄μ§‘λ‹ˆλ‹€.

πŸ”Ž λ¬Έμ„œν™”

μŠ€ν† λ¦¬λΆμ€ μ»΄ν¬λ„ŒνŠΈμ— λŒ€ν•œ λ¬Έμ„œλ₯Ό μžλ™μœΌλ‘œ μƒμ„±ν•˜λ©°, μ‚¬μš©μžμ—κ²Œ μ»΄ν¬λ„ŒνŠΈμ˜ Props와 μ‚¬μš©λ²•, 예제λ₯Ό μ‰½κ²Œ μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

덕뢄에 λ‚΄κ°€ λ§Œλ“€κ±°λ‚˜ λ‹€λ₯Έ μ‚¬λžŒμ΄ λ§Œλ“  μ»΄ν¬λ„ŒνŠΈλ“€μ„ 더 μ‰½κ²Œ ν™•μΈν•˜κ³  μ‚¬μš©ν•  수 있게 λ©λ‹ˆλ‹€.

 

πŸ“Œ Storybook μ„€μΉ˜

$ npx storybook@latest init

μœ„ λͺ…λ Ήμ–΄λ‘œ ν”„λ‘œμ νŠΈ λ£¨νŠΈμ—μ„œ μŠ€ν† λ¦¬λΆμ„ μ„€μΉ˜ν•˜λ©΄ λ‹€μŒκ³Ό 같은 폴더 ꡬ쑰가 μžλ™μœΌλ‘œ κ΅¬μ„±λ©λ‹ˆλ‹€.

 

package.json νŒŒμΌμ—λŠ” μŠ€ν† λ¦¬λΆμ„ μ‹€ν–‰ν•˜κ³  λΉŒλ“œν•  수 μžˆλŠ” λͺ…령어듀이 μΆ”κ°€λ©λ‹ˆλ‹€.

 

그리고 .storybook 폴더 μ•„λž˜μ—λŠ” μŠ€ν† λ¦¬λΆμ„ κ΅¬μ„±ν•˜λŠ” μ„€μ • 파일이 μœ„μΉ˜ν•˜κ²Œ λ©λ‹ˆλ‹€.

μ—¬κΈ°μ„œ stories μ†μ„±μ˜ λ°°μ—΄ κ°’μ—λŠ” μŠ€ν† λ¦¬ νŒŒμΌμ„ 인식할 경둜λ₯Ό μ œκ³΅ν•΄ μ£Όλ©΄ λ©λ‹ˆλ‹€.

 

πŸ“Œ Story μž‘μ„± 방법

μœ„μ—μ„œ src κ²½λ‘œμ— μžˆλŠ” μŠ€ν† λ¦¬ νŒŒμΌλ“€μ„ μ½μ–΄μ˜€λ„λ‘ μ„€μ •ν•΄μ„œ ν•΄λ‹Ή μœ„μΉ˜μ— μ»΄ν¬λ„ŒνŠΈμ™€ μŠ€ν† λ¦¬ νŒŒμΌμ„ 생성해 μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

κ·Έ ν›„ κ°„λ‹¨ν•˜κ²Œ childrenκ³Ό backgroundColorλ₯Ό props둜 λ°›λŠ” λ²„νŠΌ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ§Œλ“€μ–΄ μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

// src/components/Mybutton.tsx
import React from 'react';
export const MyButton = (props: {
  children: React.ReactNode;
  backgroundColor?: string;
}) => {
  return (
    <button style={{ backgroundColor: props.backgroundColor }}>
      {props.children}
    </button>
  );
};

κ·Έλ‹€μŒ ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈμ˜ μŠ€ν† λ¦¬ νŒŒμΌμ„ λ‹€μŒκ³Ό 같이 μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

import { MyButton } from './MyButton';

const meta = {
  title: 'MyComponent/MyButton',
  component: MyButton,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};

export default meta;

export const Primary = {
  args: {
    children: 'Button',
        backgroundColor: '#fff',
  },
};

μž‘μ„±μ„ 마친 ν›„ npm run storybook λͺ…λ Ήμ–΄λ‘œ μŠ€ν† λ¦¬λΆμ„ μ‹€ν–‰ν•˜λ©΄ μž‘μ„±ν•œ μ»΄ν¬λ„ŒνŠΈ μŠ€ν† λ¦¬ λ¬Έμ„œλ₯Ό 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

μœ„μ—μ„œ μž‘μ„±ν•œ μŠ€ν† λ¦¬ λ¬Έμ„œμ˜ μ£Όμš”ν•œ 객체와 속성듀은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

πŸ”Ž const meta = { ... };

  • meta κ°μ²΄λŠ” μŠ€ν† λ¦¬μ˜ 메타 정보λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  • title: 'MyComponent/MyButton': ν˜„μž¬ μŠ€ν† λ¦¬μ˜ 제λͺ©μ„ μ •μ˜ν•©λ‹ˆλ‹€.
    μ΄λŠ” Storybookμ—μ„œ μŠ€ν† λ¦¬μ˜ μœ„μΉ˜ 및 ꡬ쑰λ₯Ό κ²°μ •ν•˜λŠ” 데 μ‚¬μš©λ©λ‹ˆλ‹€.
    / 둜 μŠ€ν† λ¦¬ κ°„ 계측 ꡬ쑰λ₯Ό μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • component: MyButton: ν˜„μž¬ μŠ€ν† λ¦¬μ—μ„œ μ‚¬μš©λ˜λŠ” μ»΄ν¬λ„ŒνŠΈλ₯Ό μ§€μ •ν•©λ‹ˆλ‹€.
  • argTypes: { ... }: μ»΄ν¬λ„ŒνŠΈμ˜ 인자(Props)λ₯Ό 직접 μ‘°μž‘ν•  수 μžˆλ„λ‘ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

πŸ”Ž  export const Primary = { ... };

  • PrimaryλΌλŠ” μŠ€ν† λ¦¬λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€.
  • args: { children: 'Button', backgroundColor: '#fff' }: Primary μŠ€ν† λ¦¬μ˜ κΈ°λ³Έ 인자λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. 즉, MyButton μ»΄ν¬λ„ŒνŠΈμ— μ „λ‹¬λ˜λŠ” Props κ°’μž…λ‹ˆλ‹€. μ—¬κΈ°μ„œλŠ” children에 'Button' λ¬Έμžμ—΄μ„, backgroundColorμ—λŠ” #fffλ₯Ό κΈ°λ³Έ κ°’μœΌλ‘œ μ„€μ •ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ“Œ argTypes Control Types

λ²„νŠΌ μ»΄ν¬λ„ŒνŠΈμ—μ„œ argTypes의 control 속성에 colorλ₯Ό 지정해 μœ„μ™€ 같이 컬러 피컀λ₯Ό μ‚¬μš©ν•΄ propsλ₯Ό μ‘°μž‘ν•  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

이와 같이 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ§κ΄€μ μœΌλ‘œ μ‘°μž‘ν•  수 μžˆλ„λ‘ λ•λŠ” μ—¬λŸ¬ 가지 control 속성듀은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  1. Boolean
    • μ‚¬μš©μžμ—κ²Œ μ²΄ν¬λ°•μŠ€λ₯Ό μ œκ³΅ν•˜μ—¬, true/false 값을 ν† κΈ€ ν•  수 있게 ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'boolean' } }
  2. Text
    • λ¬Έμžμ—΄μ„ μž…λ ₯ν•  수 μžˆλŠ” ν…μŠ€νŠΈ ν•„λ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'text' } }
  3. Number
    • 숫자λ₯Ό μž…λ ₯ν•  수 μžˆλŠ” ν•„λ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. μ΅œμ†Ÿκ°’, μ΅œλŒ“κ°’, μŠ€ν…(증감 λ‹¨μœ„)을 μ˜΅μ…˜μœΌλ‘œ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • 예: { control: { type: 'number', min: 0, max: 100, step: 5 } }
  4. Color
    • 색상 선택기λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. μ‚¬μš©μžκ°€ μ»΄ν¬λ„ŒνŠΈμ˜ 색상을 μ‰½κ²Œ λ³€κ²½ν•  수 있게 ν•΄ μ€λ‹ˆλ‹€.
    • 예: { control: { type: 'color' } }
  5. Object
    • 객체λ₯Ό JSON ν˜•νƒœλ‘œ μž…λ ₯ν•  수 μžˆλŠ” ν…μŠ€νŠΈ μ˜μ—­μ„ μ œκ³΅ν•©λ‹ˆλ‹€. λ³΅μž‘ν•œ 객체 ꡬ쑰λ₯Ό props둜 전달할 λ•Œ μœ μš©ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'object' } }
  6. Select
    • μ‚¬μš©μžκ°€ λͺ©λ‘μ—μ„œ μ—¬λŸ¬ μ˜΅μ…˜ 쀑 ν•˜λ‚˜λ₯Ό 선택할 수 있게 ν•˜λŠ” λ“œλ‘­λ‹€μš΄ 메뉴λ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'select', options: ['Option 1', 'Option 2'] } }
  7. Radio
    • μ—¬λŸ¬ μ˜΅μ…˜ 쀑 ν•˜λ‚˜λ₯Ό 선택할 수 μžˆλŠ” λΌλ””μ˜€ λ²„νŠΌμ„ μ œκ³΅ν•©λ‹ˆλ‹€. select와 μœ μ‚¬ν•˜μ§€λ§Œ UIκ°€ λ‹€λ¦…λ‹ˆλ‹€.
    • 예: { control: { type: 'radio', options: ['Option 1', 'Option 2'] } }
  8. InlineRadio
    • λΌλ””μ˜€ λ²„νŠΌμ„ 인라인(μˆ˜ν‰)으둜 ν‘œμ‹œν•©λ‹ˆλ‹€. 선택 μ˜΅μ…˜μ„ μ˜†μœΌλ‘œ λ‚˜μ—΄ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'inline-radio', options: ['Option 1', 'Option 2'] } }
  9. Check
    • μ‚¬μš©μžκ°€ μ—¬λŸ¬ μ˜΅μ…˜μ„ 선택할 수 μžˆλŠ” μ²΄ν¬λ°•μŠ€ 그룹을 μ œκ³΅ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'check', options: ['Option 1', 'Option 2'] } }
  10. InlineCheck
    • μ²΄ν¬λ°•μŠ€λ₯Ό 인라인(μˆ˜ν‰)으둜 ν‘œμ‹œν•©λ‹ˆλ‹€. μ—¬λŸ¬ μ˜΅μ…˜μ„ μ˜†μœΌλ‘œ λ‚˜μ—΄ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'inline-check', options: ['Option 1', 'Option 2'] } }
  11. Range
    • μŠ¬λΌμ΄λ”λ₯Ό 톡해 숫자 값을 선택할 수 μžˆλŠ” μ»¨νŠΈλ‘€μ„ μ œκ³΅ν•©λ‹ˆλ‹€. μ΅œμ†Ÿκ°’, μ΅œλŒ“κ°’, μŠ€ν…μ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • 예: { control: { type: 'range', min: 0, max: 100, step: 5 } }
  12. Date
    • λ‚ μ§œλ₯Ό 선택할 수 μžˆλŠ” 달λ ₯ UIλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€.
    • 예: { control: { type: 'date' } }
  13. File
    • νŒŒμΌμ„ μ—…λ‘œλ“œν•  수 μžˆλŠ” μž…λ ₯ ν•„λ“œλ₯Ό μ œκ³΅ν•©λ‹ˆλ‹€. ν—ˆμš©ν•˜λŠ” 파일 νƒ€μž…μ„ μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
    • 예: { control: { type: 'file', accept: '.png,.jpg' } }

 

πŸ“Œ mdx둜 λ¬Έμ„œ μž‘μ„±

.storybook/main.ts νŒŒμΌμ— mdx νŒŒμΌλ„ 인식할 수 μžˆλ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

μŠ€ν† λ¦¬λΆμ€ Story 파일뿐 μ•„λ‹ˆλΌ mdx ν™•μž₯자 νŒŒμΌλ‘œλ„ λ¬Έμ„œν™”λ₯Ό ν•  수 μžˆλŠ” κΈ°λŠ₯을 μ œκ³΅ν•©λ‹ˆλ‹€.

μš°μ„  μƒμ†Œν•  수 μžˆμœΌλ‹ˆ mdx νŒŒμΌμ— λŒ€ν•œ μ„€λͺ…은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

 

πŸ”Ž mdx νŒŒμΌμ΄λž€

MDXλŠ” Markdown 기반의 λ¬Έμ„œλ₯Ό ν™•μž₯ν•œ κ²ƒμœΌλ‘œ, React μ»΄ν¬λ„ŒνŠΈλ₯Ό 포함할 수 μžˆλŠ” λ§ˆν¬λ‹€μš΄ λ¬Έμ„œ ν˜•μ‹μž…λ‹ˆλ‹€.

MDX νŒŒμΌμ€ JavaScript와 JSX μ½”λ“œλ₯Ό ν¬ν•¨ν•˜μ—¬ 더 동적이고 μΈν„°λž™ν‹°λΈŒ ν•œ μ½˜ν…μΈ λ₯Ό μž‘μ„±ν•  수 있게 ν•΄ μ€λ‹ˆλ‹€.

주둜 λ¬Έμ„œ, λΈ”λ‘œκ·Έ 포슀트, ν”„λ‘œμ νŠΈμ˜ μ‚¬μš© μ„€λͺ…μ„œ 등을 μž‘μ„±ν•  λ•Œ μ‚¬μš©λ©λ‹ˆλ‹€.

ν•œλ§ˆλ””λ‘œ λ§ˆν¬λ‹€μš΄ ν˜•μ‹μ„ μ‚¬μš©ν•˜λ©΄μ„œ λ¦¬μ•‘νŠΈ μ»΄ν¬λ„ŒνŠΈλ₯Ό λ Œλ”λ§ ν•  수 μžˆλŠ” λ¬Έμ„œμž…λ‹ˆλ‹€.

 

μŠ€ν† λ¦¬λΆ ν™˜κ²½μ€ 자체적으둜 μ‹€ν–‰λ˜κΈ° λ•Œλ¬Έμ— νŠΉμ • 라이브러리λ₯Ό μ‚¬μš©ν•œ μ»΄ν¬λ„ŒνŠΈλ₯Ό μŠ€ν† λ¦¬λ‘œ λ§Œλ“œλ € ν•  λ•Œ 였λ₯˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

그럴 λ•Œ λ‹€λ₯Έ λ¬Έμ„œν™” ν”„λ ˆμž„μ›Œν¬λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šκ³  mdx ν˜•μ‹μ„ μ΄μš©ν•΄ μŠ€ν† λ¦¬λΆ ν™˜κ²½ λ‚΄μ—μ„œ λ¬Έμ„œλ₯Ό 확인할 수 μžˆλ„λ‘ ν•©λ‹ˆλ‹€.

μœ„μ™€ 같이 μž‘μ„±ν•˜κ³  Meta에 μœ„μ™€ 같이 계측ꡬ쑰λ₯Ό μ •ν•΄ title을 μ •ν•΄μ£Όλ©΄ λ‹€μŒκ³Ό 같이 ν‘œμ‹œλ©λ‹ˆλ‹€.

 

λ°˜μ‘ν˜•

λŒ“κΈ€


μ˜€ν”ˆ 채νŒ