Story 작성

Story란?

Story는 UI 컴포넌트의 렌더링 된 상태를 캡처합니다. 인자 집합이 주어지면 컴포넌트 상태를 반환하는 함수입니다.

Storybook은 React 또는 Vue의 props, Angular의 @Input 및 기타 유사한 개념(전달 된 속성)을 일반적인 용어인 인자(줄여서 args)를 사용합니다.

Story 구성

Storybook은 컴포넌트와 그 하위 스토리의 2가지 기본 단계로 구성되어 있습니다. 스토리는 컴포넌트에 대한 개별 이야기입니다. 필요한 만큼의 스토리를 컴포넌트 별로 작성할 수 있습니다.

컴포넌트
├── 스토리
├── 스토리
├── ...
└── 스토리

Story 파일 생성

Story는 컴포넌트 파일이 위치한 디렉토리 안에 작성합니다. 이 파일은 개발용이며 프로덕션 번들에 포함되지 않습니다. Story를 구성할 컴포넌트를 작성한 후, 컴포넌트 파일과 같은 위치에 Story 파일을 추가합니다.

components/
└─ StyleInput/
   ├─ StoryInput.scss
   ├─ StyleInput.js
   └─ StyleInput.stories.js # Story 파일

Story 파일 포멧

컴포넌트 스토리 포멧(CSF)은 Story를 구성하는 객체를 말하며, 작성할 Story의 정보를 작성합니다.

속성

설명

Storybook 앱 사이드바에 표시되는 컴포넌트 이름

Story를 작성 할 컴포넌트 설정 (컴포넌트 설명, props 추출)

모든 Story에 공통 적용할 전달 인자 설정

각 Story 인자(args)의 행동(behaviour) 방식 설정

Story를 감싸는 렌더링 함수 (Story 보강, 렌더링 세부 정보 수집 등)

Story에 대한 정적 메타 데이터 정의 (다양한 애드온 구성 제공)

Storybook에서 Story를 내보낼 때 렌더링에서 제외 설정 (정규 표현 식)

컴포넌트 스토리 포멧(CSF)은 "기본(default) 내보내기"로 내보내야 합니다.

export default {
  title: '시스템/그룹/컴포넌트 이름',
  component: Component,
  // args: {},
  // argTypes: {}
  // decorators: []
  // ...
}

Story 작성(정의)

컴포넌트의 Story(CSF)는 "지정 된 이름(named) 내보내기"를 사용하여 컴포넌트 Story를 정의합니다. Story 이름은 TitleCase로 작성하는 것이 권장됩니다.

export const Story = () => <Button secondary>스토리 버튼</Buton>

Storybook에 표시되는 Story 이름을 변경해야 한다면? storyName 속성을 사용할 수 있습니다.

StoryButton.storyName = 'Secondary Button'

멀티 Story 작성

Story는 컴포넌트를 렌더링 하는 방법을 설명하는 함수입니다. 컴포넌트 마다 여러 개의 Story를 가질 수 있습니다. Story를 만드는 가장 간단한 방법은 인자가 다른 컴포넌트를 여러 번 렌더링하는 것입니다.

export const Primary = () => <Button children="프라이머리 버튼" />
export const Secondary = () => <Button secondary children="세컨더리 버튼" />
export const Tertiary = () => <Button tertiary children="터시어리 버튼" />

템플릿, 인자 활용

하나 이상 컴포넌트의 스토리를 만들 경우 Template 변수에 컴포넌트 복사본을 할당하는 것이 편리합니다. 이 패턴을 Story에 도입하면 작성 또는 유지보수 해야 할 코드 양이 줄어듭니다.

// Story 템플릿
const Template = (args) => <Button {...args} />

// 템플릿 복사본
export const Primary = Template.bind({})
export const Secondary = () => Template.bind({})
export const Tertiary = () => Template.bind({})

// 복사한 각 템플릿(Copyed Story)에 인자 설정
Primary.args = { children: "프라이머리 버튼" }
Secondary.args = { secondary: true, children: "세컨더리 버튼" }
Tertiary.args = { tertiary: true, children: "터시어리 버튼" }

Template.bind({ })는 함수의 복사본을 만드는 표준 JavaScript의 기법입니다. 이 기법을 사용하여 각각의 스토리가 고유한 속성(properties)을 갖지만, 동시에 동일한 구현을 사용하도록 할 수 있습니다.

인자(arguments)는 줄여서 args를 사용하며, Storybook을 다시 시작하지 않고도 Controls addon으로 컴포넌트를 실시간으로 수정할 수 있습니다. 값이 변하면 컴포넌트도 실시간 업데이트 됩니다.

아래 코드는 Story 작성 예시입니다.

// Story를 구성할 컴포넌트 파일 불러오기
import StoryInput from './StoryInput'

/* ------------------------------------------------------------------- */

export default {
  // 컴포넌트 설명을 입력하면 Storybook에 카테고리 되어 표시됩니다.
  title: 'FormControl/StoryInput',
  // 컴포넌트 설정
  component: StoryInput,
  // 전달인자 공통 설정
  args: {
    label: '이메일',
    type: 'email',
    placeholder: 'yamoo9@euid.dev',
  },
  // 전달 인자 유형 설정
  argTypes: {
    backgroundColor: { control: 'color' },
    disabled: { control: 'boolean' },
  },
}

// 컴포넌트 템플릿
// 함수의 복사본을 만드는 표준 JavaScript 기법
const Template = (args) => <StoryInput {...args} />

// sm 사이즈 컴포넌트
export const SmSize = Template.bind({})
SmSize.storyName = 'Small'
SmSize.args = {
  id: 'sm-size-kwdj1',
  size: 'sm',
}
SmSize.parameters = {
  viewport: {
    defaultViewport: 'iphonex',
  },
}

// md 사이즈 컴포넌트
export const MdSize = Template.bind({})
MdSize.storyName = 'Medium'
MdSize.args = {
  id: 'md-size-kwdj5',
  size: 'md',
}
MdSize.parameters = {
  viewport: {
    defaultViewport: 'iphonexr',
  },
}

// lg 사이즈 컴포넌트
export const LgSize = Template.bind({})
LgSize.storyName = 'Large'
LgSize.args = {
  id: 'lg-size-kwdj8',
  size: 'lg',
}

Storybook 구동

storybook 구동 명령을 사용해 Storybook을 웹 브라우저에서 확인할 수 있습니다.

npm run storybook

Light 모드

Dark 모드 (별도 설정 필요)

Storybook 스니펫

Storybook 컴포넌트 스니펫을 사용하면 손쉽게 컴포넌트 Story를 구성할 수 있습니다.

import $1 from './$1'

const storyConfig = {
  // 컴포넌트 설명
  title: '${2:시스템}/${3:그룹}/$1',
  // 컴포넌트 설정
  component: $1,
  // 전달 인자 설정
  // args: {
  //   설정 예시 
  //   type: 'email',
  // },
  // 전달 인자 유형 설정
  // argTypes: {
    // 컬러 피커 컨트롤 설정 예시
    // backgroundColor: { control: 'color' },
  // }
}

export default storyConfig

// 컴포넌트 템플릿
const Template = (args) => <$1 {...args} />

// 예제 컴포넌트 생성
export const Example = Template.bind({})
// 예제 컴포넌트 props 설정
Example.args = {
  // prop 속성 설정
}

매개변수 활용

매개변수는 Story에 대한 정적 메타 데이터를 정의하는 Storybook의 방법입니다. Story의 매개변수를 사용하여 Story 또는 Story 그룹 레벨에서 다양한 애드온에 구성을 제공 할 수 있습니다. 예를 들어 앱의 다른 컴포넌트와 다른 배경에서 컴포넌트를 테스트 하고 싶다면? 다음과 같이 CSF를 설정합니다. (컴포넌트 레의 매개변수 설정)

export default {
  title: 'Button',
  component: Button,
  //👇 Story 매개 변수 설정
  parameters: {
    backgrounds: {
      values: [
        { name: 'darkred', value: '#340000' },
        { name: 'storypink', value: '#fb6597' }
      ],
    },
  },
};

데코레이터 활용

데코레이터는 Story를 렌더링 할 때 임의의 마크업으로 컴포넌트를 감싸는 메커니즘입니다. 예를 들어 테마 또는 레이아웃 래퍼(wrapper)가 필요할 수 있습니다. 또는 UI에 특정 컨텍스트 혹은 데이터 공급자(provider)가 필요할 수 있습니다.

간단한 예는 컴포넌트의 Story에 스타일 래퍼를 추가하는 것입니다. 다음과 같이 Story를 감싸는 데코레이터를 작성하면 Storybook 뷰포트에 스타일이 반영된 래퍼 요소가 렌더링 됩니다.

export default {
  title: 'Button',
  component: Button,
  decorators: [
    (Story) => (
      <div style={{ margin: '40px' }}>
        <Story />
      </div>
    ),
  ],
};

보다 복잡한 데코레이터 구성 예(ThemeProvider 등)를 참고하세요.

멀티 컴포넌트 Story

디자인 시스템 또는 컴포넌트 라이브러리를 빌드 할 때 함께 작동하도록 디자인 된 2개 이상 컴포너트의 Story를 작성하려면 상위 컴포넌트와 하위 컴포넌트 모두 불러와 사용하고, 컴포넌트 작동 상황 별 Story를 렌더링 하는 것이 좋습니다.

import List from './List';
import ListItem from './ListItem';

export default {
  component: List,
  title: 'List',
};

// 하위 컴포넌트를 포함하지 않은 상위 컴포넌트 Story
export const Empty = (args) => <List {...args} />;

// 1개의 하위 컴포넌트를 포함한 상위 컴포넌트 Story
export const OneItem = (args) => (
  <List {...args}>
    <ListItem />
  </List>
);

// 1개 이상 하위 컴포넌트를 포함한 상위 컴포넌트 Story
export const ManyItems = (args) => (
  <List {...args}>
    <ListItem />
    <ListItem />
    <ListItem />
  </List>
);

컴포넌트 구성이 보다 복잡하다면 멀티 컴포넌트 Story 워크플로우를 참고하세요.

Last updated

Was this helpful?