개발/프론트엔드 (JS & React)

[React & Storybook] 1 - 리액트에서 스토리북 사용하기

jungwon_ 2022. 9. 22. 18:22

이 글에서는 스토리북과 스토리가 무엇인지 알아보고 리액트에서 스토리북(Storybook)을 시작하는 방법을 다뤄보자.


스토리북이란?

출처 : https://youtu.be/p-LFh5Y89eM

우선 스토리북은 무엇이고 왜 사용할까?

 

공식 문서에서 설명하는 스토리북이란 각각의 UI 컴포넌트와 페이지를 분리해서 빌드하는 프론트엔드 workshop이다.

 

쉽게 설명하자면 프론트엔트는 다수의 UI 컴포넌트로 구성되는데, 각각의 UI 컴포넌트를 따로 볼 수 있게 만들어서 개발 및 테스트를 더 쉽게 도와주는 것이라고 생각하면 된다.

 

스토리북은 React, Vue, Angular 등 UI 컴포넌트 기반의 프레임워크/라이브러리에서 사용할 수 있다.

 

스토리북에서 Story는 UI 컴포넌트의 렌더링된 상태를 캡쳐한다.

 

각 컴포넌트는 여러개의 스토리를 가질 수 있고 각 스토리는 컴포넌트가 지원하는 상태를 묘사한다.

 

또한 스토리는 Component Story Format (CSF)로 작성된다.


리액트에 스토리북 사용하기

스토리북을 리액트앱에 적용하며 더 자세히 살펴보자.

 

우선 스토리북을 사용하기 위해 CLI를 설치한다.

npm i @storybook/cli -g

 

그리고 이미 생성된 리액트 프로젝트에 스토리북을 추가한다.

npx sb init

 

위 명령어를 사용하면 config 파일이 있는 .storybook 디렉토리와, 스토리를 생성할 stories 디렉토리가 src 안에, 그리고 package.json에 storybook 스크립트가 추가된 것을 확인할 수 있다.

 

모든 것이 설치가 되었으면 npm run storybook 으로 스토리북을 실행해준다.


스토리

예제를 위해 stories 디렉토리 안에 있는 Button.stories.js를 예시로 살펴보자.

import { Button } from './Button';

export default {
  title: "Button",
  component: Button,
}

테스트할 컴포넌트를 import하고 스토리의 title을 적어준다.

 

참고로 title에서 Components/Button 처럼 작성하면 폴더처럼 처리되어서 컴포넌트가 많아졌을때 그룹을 묶기 편리하다.

// 스토리 예제 1
export const Primary = () => <Button primary>Button</Button>;

위처럼 스토리를 작성하면 Button 컴포넌트 아래에 Primary 스토리를 확인할 수 있다.

출처 : https://storybook.js.org/docs/react/get-started/whats-a-story

스토리를 만드는 다른 방법으로는 template을 만들어 args를 사용하는 방법이 있다.

const Template = (args) => <Button {...args} />;

// 스토리 예제 2
export const Primary = Template.bind({});
Primary.args = {
   primary: true,
   label: 'Button',
};

Template을 사용하면 각기 다른 스토리에서 여러번 재사용 가능하다.

 

예제2처럼 args 프로퍼티를 사용하면 두 가지 장점이 있다.

1) 컴포넌트의 콜백이 Actions 탭에 기록된다.

2) Controls 탭에서 동적으로 인자값을 조정할 수 있다.


스토리북은 소스 코드를 바탕으로 유용한 문서도 생성해준다.

 

예를 들어 Button 컴포넌트에 있는 propTypes를 바탕으로 자동으로 Docs 페이지를 생성하는 것을 볼 수 있다.

Button.propTypes = {
  /**
   * Is this the principal call to action on the page?
   */
  primary: PropTypes.bool,
  /**
   * What background color to use
   */
  backgroundColor: PropTypes.string,
  /**
   * How large should the button be?
   */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /**
   * Button contents
   */
  label: PropTypes.string.isRequired,
  /**
   * Optional click handler
   */
  onClick: PropTypes.func,
};

 


Reference

- Storybook 공식문서