스토리북을 이용하면 리액트로 생성한 컴포넌트의 테스트 및 문서화를 간소화 시켜주는 효과를 얻는다.
정해진 스타일 가이드가 있다면, 디자인팀과의 협업에서 스토리북을 이용해 소통하고 수정사항등을 빠르게 체크하고, Mock 데이터를 이용해 실제 보여질 디자인을 빠르게 확인할 수 있다.
개발 환경 기술스택
TypeScript(4.9.3) + Vite(4.1.0) + React(18.2.0) + Styled-components(5.3.8) + Storybook(7.0.5)
아래 명령어를 이용해 setup을 해주면 사진과 같이 여러 devDependencies가 추가되고, 스토리북 실행 및 빌드를 위한 스크립트 storybook
과 build-storybook
이 추가된다.
npx sb init --builder @storybook/builder-vite
또한, 다음과 같이 .storybook 폴더와 src/stories 폴더가 생성되는데, src/stories는 샘플을 보여주는 것으로 현 프로젝트에서는 컴포넌트 각각에 stories.tsx를 정의할 것이기 때문에 삭제시켰다.
추가로, main.cjs 파일을 통해 스토리북 실행 시, 스토리 파일을 찾을 수 있도록 해야한다. 그래서 .storybook/main.cjs
에서 stories의 경로를 아래와 같이 수정했다. (src/components
경로의 모든 *.stories.@(ts|tsx|js|jsx)
)
module.exports = {
stories: ['../src/components/**/*.stories.@(js|jsx|ts|tsx)'],
...
};
이제 이를 실행시키기 위해 yarn storybook
을 이용했으나 다음과 같은 에러를 바로 마주했다.
start-storybook: command not found
해결을 위해 Issue #311를 참고해서 node_modules, yarn.lock 파일을 지우고 설치하고를 반복했으나 .. 해결이 되지 않았다. 여기서 좀 애를 먹었는데 stackoverflow에서 storybook을 automigrate 하는 코드를 찾아 이를 이용해 해결할 수 있었다.
해당 설명에 따르면 npx sb init
이후에는 반드시 sb를 새 버전으로 automigrate 시켜주어야 한다. migration 여부를 묻는 항목들은 전부 Y로 응답했고, 해결할 수 있었다.
npx sb@next automigrate
yarn storybook # 실행
'start-storybook' is not recognized as an internal or external command
기본적으로 스토리 작성 파일명은 컴포넌트명.stories.js
로 작성해주어야 한다. 해당 포맷 방식은 ./storybook/main.js
에서 수정 가능하다. 스토리 작성은 컴포넌트 코드를 만든 후에 이루어진다.
Button 컴포넌트를 예로 작업해보자. 아래는 프로젝트에서 작성한 버튼 컴포넌트 코드다.
// src/components/Button/Button
import React, { ButtonHTMLAttributes } from 'react';
import styled from 'styled-components';
import { ButtonSize } from './types';
export interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: 'text' | 'contained' | 'outlined';
size?: ButtonSize;
underline?: boolean;
}
function Button({
variant = 'contained',
size = 'medium',
disabled = false,
underline = true,
children,
...rest
}: Props) {
switch (variant) {
case 'text':
return (
<TextButton size={size} disabled={disabled} underline={underline} {...rest}>
{children}
</TextButton>
);
case 'outlined':
return (
<OutlinedButton size={size} disabled={disabled} {...rest}>
{children}
</OutlinedButton>
);
case 'contained':
default:
return (
<ContainedButton size={size} disabled={disabled} {...rest}>
{children}
</ContainedButton>
);
}
}
export default Button;
src/components/Button
경로에 Button.stories.tsx
를 추가한다.
story 파일에서도 JSX.Element, 컴포넌트 형태로 내보낼 것이기 때문에 타입 에러 방지를 위해 tsx 확장자를 이용했다.
Knobs
는 component의 props를 storybook 화면에서 입력을 통해 값을 바꾸며 바로 반영시켜줄 수 있는 애드온이다. 아래와 같이 설치 후,
yarn add @storybook/addon-knobs --dev
.storybook/main.js
파일에 addons에 설치된 라이브러리를 추가해주면 끝이다!
// .storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-mdx-gfm',
'@storybook/addon-knobs', // 추가
],
framework: {
name: '@storybook/react-vite',
options: {},
},
features: {
storyStoreV7: true,
},
docs: {
autodocs: true,
},
core: {
builder: '@storybook/builder-vite',
},
typescript: {
reactDocgen: 'react-docgen',
},
};
추가로, knobs에서 사용가능한 옵션들은 아래에서 확인 가능하다.
knobs 종류
- text: 텍스트를 입력 할 수 있습니다.
- boolean: true/false 값을 체크박스로 설정 할 수 있습니다.
- number: 숫자를 입력 할 수 있습니다. 1~10과 같이 간격을 설정 할 수도 있습니다.
- color: 컬러 팔레트를 통해 색상을 설정 할 수 있습니다.
- object: JSON 형태로 객체 또는 배열을 설정 할 수 있습니다.
- array: 쉼표로 구분된 텍스트 형태로 배열을 설정 할 수 있습니다.
- select: 셀렉트 박스를 통하여 여러가지 옵션 중에 하나를 선택 할 수 있습니다.
- radios: Radio 버튼을 통하여 여러가지 옵션 중에 하나를 선택 할 수 있습니다.
- options: 여러가지 옵션을 선택 하는 UI 를 커스터마이징 할 수 있습니다 (radio, inline-radio, check, inline-check, select, multi-select)
- files: 파일을 선택 할 수 있습니다.
- date: 날짜를 선택 할 수 있습니다.
- button: 특정 함수를 실행하게 하는 버튼을 만들 수 있습니다.
Knobs Addon | Storybook: Frontend workshop for UI development
우선 컴포넌트 스토리에 대한 기본 설정을 작성한다. (title, component, argTypes)
title
: 컴포넌트 명으로, /
를 이용해 카테고리 분류가 가능하다.component
: 파라미터에 아무것도 지정하지 않은 단순한 컴포넌트 형태를 가져온다.argTypes
: 컴포넌트에 필요한 인수와 타입 작성여기서는 knobs를 적용했다.
// src/components/Button/Button.stories.tsx
import { withKnobs, text, boolean } from '@storybook/addon-knobs';
import Button from './Button';
export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
argTypes:
};
그 다음에는, Basic 이란 이름으로 스토리를 생성해보자.
💡 기본적으로 스토리를 만들 때
export const
를 사용하여default
라는 이름으로 내보낼 수 없다. default란 이름을 사용하기 위해서는 스토리를 만들고, 해당 스토리의 멤버 변수로 story 객체를 설정하면 이름을 변경할 수 있다.
hello.story = { name: ‘Default’ }
// src/components/Button/Button.stories.tsx
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import Button from './Button';
export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
};
export const Basic = () => {
return <Button>hello</Button>;
};
여기서 또 하나의 문제가 발생했다. 해당 프로젝트에서는 styled-components를 이용하면서 theme을 정의해놓고 색상 등을 가져다 쓰는데, stories에서 theme에 접근할 수 없는 문제가 있었다. Cannot read properties of undefined (reading 'primary1')
3-1) 스토리북 내부에 styled-components theme 적용시키기 (Decorator)
위를 해결하기 위해 스토리북에서 styled-components
를 사용하기 위한 세팅이 추가적으로 필요하다.
preview.js
는 모든 stories에 global하게 적용될 css 등을 세팅할 수 있는 곳이다. 따라서, ThemeProvider
를 통해 theme을 감싸주고, GlobalStyle까지 적용시킬 수 있다. (혹은 특정 컴포넌트만 ThemeProvider
로 래핑하는 것도 가능하다.) Storybook 7.0 버전에 맞춰 preview.js→ preview.jsx
로 수정한 후 아래와 같이 theme을 적용한다.
// .storybook/preivew.jsx
import React from 'react';
import { ThemeProvider } from 'styled-components';
import { theme } from '../src/styles/theme';
export default {
parameters: {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
decorators: [
(Story) => (
<ThemeProvider theme={theme}>
<Story />
</ThemeProvider>
),
],
};
https://storybook.js.org/docs/react/writing-stories/decorators
3-2) knobs 애드온 추가하기
Button이라는 컴포넌트는 variant, disabled, underline, size 등 여러 props를 가진다. 이러한 props를 스토리북 상에서 사용자가 조절할 수 있도록 하기 위해 knobs를 추가할 수 있다.
const label = text('label', 'Button');
위의 코드를 예로 들면, text 형식으로 사용자의 입력을 받을 것이고, label이라는 필드에 defaultValue로 ‘Button’을 추가하게 된다.
설정한 text, boolean, select와 같은 옵션들을 이용해 작성하면 아래와 같이 조절할 수 있는 패널이 등장하고, 해당 옵션 선택의 결과가 UI 상에 바로 보여진다.
// src/components/Button/Button.stories.tsx
import { withKnobs, text, boolean, select } from '@storybook/addon-knobs';
import Button from './Button';
export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
};
export const Basics = () => {
const label = text('label', 'Button');
const props = {
disabled: boolean('disabled', false),
size: select('size', ['large', 'medium', 'small'], 'medium'),
};
return (
<div>
<Button {...props}>{label}</Button>
<Button variant={'text'} {...props}>
{label}
</Button>
<Button variant={'outlined'} {...props}>
{label}
</Button>
</div>
);
};
3-3) Actions 애드온 추가하기
Actions 애드온은 컴포넌트를 통하여 특정 함수가 호출됐을 때 어떤 함수가 호출됐는지, 그리고 함수에 어떤 파라미터를 넣어서 호출했는지에 대한 정보를 확인 할 수 있게 해준다. action(’액션 이름’)
import { action } from '@storybook/addon-actions';
export const Basics = () => {
...
return (
<Button onClick={action('onClick')} {...props}>
{label}
</Button>
);
};
스토리북의 Docs 페이지는 컴포넌트의 Props와 주석에 기반하여 자동으로 문서가 생성된 결과다.
이제 위 페이지에 컴포넌트에 대한 설명을 추가해보자.
먼저 Subtitle을 설정하는 방법이다. 부제목을 설정할 때에는 기본 설정의 parameters
에 componentSubtitle
값을 지정할 수 있다.
// src/components/Button/Button.stories.tsx
export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
parameters: {
componentSubtitle: '버튼 컴포넌트', // 컴포넌트 부제목
},
};
부제목 밑에 설명을 넣으려면, 컴포넌트 파일에서 컴포넌트 코드 바로 윗 부분에 주석을 작성하면 된다. 주석 작성 시에는 /**
를 이용해 작성한다.
// src/components/Button/Button.stories.tsx
export default {
title: 'Button',
component: Button,
decorators: [withKnobs],
parameters: {
componentSubtitle: '버튼 컴포넌트',
},
};
/**
* 버튼은 동작(또는 일련의 동작)을 의미합니다. 버튼을 클릭하면 해당 비즈니스 로직이 트리거됩니다.
*
* 총 세 가지 유형의 버튼을 제공합니다.
* - `contained button` : 주요 작업을 표시합니다. 하나의 섹션에서 최대 하나를 가집니다.
* - `text button` : 일반적인 작업에 주로 사용됩니다.
* - `outlined button` : 클릭 시 다른 페이지로 옮겨지는 등 보조적인 동작에 사용됩니다.
*/
export const Basics = () => {
...
}
이를 통한 Docs 페이지의 결과는 아래와 같다.
하단 Knobs 패널을 조정하면 해당 상태가 적용된 컴포넌트 UI 결과도 확인할 수 있다.
Storybook v7.0이 2023년 3월에 배포되었는데, 그래서 preview 파일에 decorators를 추가하는 부분이라던가 새로운 버전에 대한 error log들을 쉽게 찾을 수 없어서 조금 시간이 걸렸다. 그러나 어떤 것이든 결국엔 공식문서를 꼼꼼히 읽어보면 찾을 수 있다!
소스 코드 : https://github.com/Dev-TeamOne/classmate-frontend
https://storybook.js.org/tutorials/intro-to-storybook/react/ko/get-started/
https://storybook.js.org/addons/storybook-addon-knobs-color-options
https://velog.io/@velopert/start-storybook
https://tech.youha.info/26d0fe3a-c718-4943-8276-233c64a4b023
정리가 잘 된 글이네요. 도움이 됐습니다.