2021/05/17 업데이트
⚠️ Notice ⚠️
해당 포스트는 Storybook 5버전을 기준으로 작성된 포스트입니다. 최신 버전에 대한 튜토리얼은 스토리북 문서를 찾아보시는 걸 추천드립니다.
지난 글에서 간단하게 Storybook이 무엇이고 어떤 기능을 제공하는지 알아보았습니다.
이번글에서는 React.js에서 Storybook을 어떻게 적용하고 사용하는지 간단한 투두리스트를 만들면서 알아보려고 합니다.
먼저 간단하게 create-react-app을 이용하여 프로젝트를 생성하겠습니다.
$ create-react-app storybook-todo
글로벌로 storybook-cli를 설치합니다. 이것을 이용하면 복잡한 설정이나 설치해야할 것을 자동으로 해줍니다.
// npm 설치
$ npm install --save -g @storybook/cli
// yarn 설치
$ yarn add global @storybook/cli
프로젝트의 루트폴더에서 다음 명령어를 이용하면 필요한 의존성을 자동으로 설치해주고 package.json
에 scripts
실행 및 빌드 명령어도 추가됩니다.
$ getstorybook init
프로젝트를 시작합니다.9009
포트(default)에 프로젝트가 열립니다.
$ npm storybook
$ yarn storybook
Storybook을 이용하여 컴포넌트를 테스트할 때, Storybook에 나오는 테스트 케이스를 작성할 때는 test.stories.js라고 파일명을 짓습니다.
.stories.js 라고 된 모든 파일을 불러오기 위해서 필요한 모듈을 설치해줍니다.
$ yarn add -d require-context.macro
아까 명령어를 통해 Storybook을 프로젝트에 추가했다면 루트폴더에 .storybook
이라는 폴더가 생겼을 겁니다.
config.js
에 들가서 아래와 같이 설정해줍니다.
.storybook/config.js
import { configure } from '@storybook/react';
import requestContext from 'require-context.macro';
import '../src/util.scss';
// *.stories.js으로 끝나는 모든 파일을 import 합니다.
const req = requestContext('../src/components', true, /\.stories\.js$/);
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
실제 예제에서는 TastItem
, TaskList
와 TaskInput
가 있지만, 다 작성하기에는 많기 때문에 이 글에서는 TastItem
을 중점적으로 설명하겠습니다.
먼저 예제로 투두리스트에 들어갈 TaskItem
을 만들어 보겠습니다.
/src/components/Task 폴더에 TaskItem폴더를 만들고, TaskItem.js
파일을 만들어 아래 내용을 작성합니다.
./src/components/Task/TaskItem/TaskItem.js
import React from 'react';
import PropTypes from 'prop-types';
import { FaRegStar, FaStar, FaRegTrashAlt } from 'react-icons/fa'; // 아이콘 관련 라이브러리
import './TaskItem.scss';
const Task = ({
task: { idx, content, archive, pinned }, // 과제 정보
onPinTask, // 과제 핀 설정 함수
onArchiveTask, // 과제 완료 설정 함수
onRemoveTask, // 과제 삭제 함수
}) => {
const onArchive = (e) => {
onArchiveTask();
e.stopPropagation();
}
const onPin = (e) => {
onPinTask();
e.stopPropagation();
}
const onRemove = (e) => {
onRemoveTask();
e.stopPropagation();
}
return (
<div className={'Task-container'} onClick={onArchive}>
<div className={'Task'}>
<div className={`Task-content`}>
{archive ? (
<>
<span className={'Task-content-fin'}>{ content }</span>
<span className={'Task-content-fin-icon'}>finish</span>
</>
) : (
<span>{content}</span>
)}
</div>
<div className={'Task-pin'} onClick={onPin}>
{pinned ?
<FaStar className={'Task-pin-iconDone'} /> :
<FaRegStar className={'Task-pin-icon'} />}
</div>
<div className={'Task-remove'} onClick={onRemove}>
<FaRegTrashAlt className={'Task-remove-icon'} />
</div>
</div>
</div>
);
};
// (PropTypes 생략)
export default Task;
TaskItem
폴더에 TaskItem.stories.js
이라는 파일을 만들어 줍니다.
아까 위에서 설정하였기 때문에 자동으로 import가 됩니다.
import React from 'react';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions'; // 액션 에드온
import TaskItem from './TaskItem';
// Test Data
export const task = {
idx: 1,
content: '오늘할일',
archive: false,
pinned: false,
};
// 이렇게 액션을 사용하면 함수를 직접 선언 하지 않아도 이벤트가 발생하였슬 때,
// 액션에 정의한 함수가 발생합니다.
export const actions = {
onPinTask: action('onPinTask'),
onArchiveTask: action('onArchiveTask'),
onRemoveTask: action('onRemoveTask'),
};
// 스토리 추가
storiesOf('TaskItem', module) // Storybook에 표시될 폴더명
// 데코레이터를 이용하면 아래와 같이 테스트할 스토리의 래핑 컴포넌트를 작성할 수 있습니다.
.addDecorator(story => <div style={{ padding: '0 20rem' }}>{story()}</div>)
// add('스토리명', 스토리 랜더링)
.add('default', () => <TaskItem task={task} {...actions} />)
.add('archived', () => <TaskItem task={{...task, archive: true}} {...actions} />)
.add('pinned', () => <TaskItem task={{...task, pinned: true}} {...actions} />)
.add('archived and pinned', () =>
<TaskItem task={{...task, archive: true, pinned: true}} {...actions} />);
이제 storybook을 켜서 어떻게 되었는 지 확인해보겠습니다.
Story에 추가한 4가지 케이스가 모두 있고 어떻게 작동하지는 보입니다.
이렇게 만든 여러가지 컴포넌트들을 조합하고 props로 값을 넘겨주면서 하나의 페이지로 만들수 있습니다.
Storybook을 이용하여 Story를 작성하는 법을 알아보았습니다.
잘 활용한다면 컴포넌트를 설계할 때, 재사용가능한 컴포넌트로 만들고 조합하여 사용할 수 있을 것 같습니다.
다음글에서는 Storybook에서 지원하는 snapshot testing 에 대해 알아보겠습니다. snapshot testing를 사용하면 손쉽게 테스트를 진행할 수 있습니다.
예제 레퍼지토리) https://github.com/wlsdud2194/storybook-tutorials
Ps. 글에 오류나 수정사항이 있다면 댓글 부탁드립니다 :)
안녕하세요 next와 storybook으로 앱을 개발하고 있습니다.
다름이 아니라 현재 글에서는 react-icons를 사용하고 있는데, 제가 사용하고 있는 storybook에서는 react-icons를 포함한 외부 모듈 연동이 안되더라구요.
혹시 react-icons를 import할 때 다른 설정을 하신 적이 있나요?