Storybook 이란?
스토리북(Storybook)은 컴포넌트의 다양한 경우를 정리할 수 있도록 도와주는 시각화 도구(tool)입니다.
예를 들어, 버튼 컴포넌트가 있다고 하면 상태(데이터)에 따라서 UI나 액션이 달라질 수 있습니다.
variant 값이 primary
이면 일반 버튼, dashed
이면 점선이 있는 버튼으로 버튼 UI를 표현할 수 있습니다.
스토리북과 같은 도구 없이도 컴포넌트를 개발할 수 있지만, 점점 늘어나는 컴포넌트들을 한 눈에 정리하는 것은 쉽지 않습니다.
스토리북은 컴포넌트들을 시각적으로 정리할 수 있게 도와줄 뿐만 아니라, 컴포넌트의 구현 코드를 문서화해줍니다.
디자이너와 협업할 때 컴포넌트의 UI가 잘 구현되었는지 피드백을 받을 수 있으며,
다른 개발자와 협업할 때는 컴포넌트를 어떻게 구현했는지, 또 컴포넌트 사용방법을 스토리북을 통해 보여줄 수 있습니다.
스토리북은 컴포넌트 기반으로 개발하는 CDD(Component Driven Development) 방법론에서 출발한 것으로 보이며, 아토믹 디자인 패턴을 현업에서 사용하고 있다면 스토리북은 강력한 툴이 될 것 같습니다.
스토리북을 통해 컴포넌트 별로 스토리 파일 (OOO.stories.tsx 또는 OOO.stories.jsx
) 을 만들 수 있으며, 각 파일은 컴포넌트를 import할 수 있습니다. 컴포넌트 안에 여러 개의 스토리를 만들 수 있는데, 스토리는 일종의 시나리오라고 보시면 됩니다.
맨 위에서 설명한 버튼을 예로 들면, 버튼은 variant 값에 따라서 primary 버튼이 될 수도 있고, dashed 버튼이 될 수도 있습니다.
이 버튼 컴포넌트에 대해 2가지 스토리로 만들 수 있는 것입니다. (primary, dashed)
Storybook 설치하기
* Next.js, React, TypeScript 환경에서 설치를 진행했습니다.
// npx
npx storybook@latest init
// 또는 pnpm
pnpm dlx storybook@latest init
pnpm dlx storybook@latest init
../Library/pnpm/store/v3/tmp/dlx-94005 | +702 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
../Library/pnpm/store/v3/tmp/dlx-94005 | Progress: resolved 662, reused 610, downloaded 31, added 702, done
storybook init - the simplest way to add a Storybook to your project.
• Detecting project type. ✓
• Adding Storybook support to your "Next" app
✔ Getting the correct version of 8 packages
✔ We have detected that you're using ESLint. Storybook provides a plugin that gives the best experience with Storybook and helps follow best practices: https://github.com/storybookjs/eslint-plugin-storybook#readme
Would you like to install it? … yes
Configuring Storybook ESLint plugin at .eslintrc.json
✔ Installing Storybook dependencies
. ✓
• Preparing to install dependencies. ✓
...
스토리북 설치를 진행하면, 알아서 프로젝트의 프레임워크를 감지합니다.
저는 Next.js 환경에서 설치를 진행했는데요.
프레임워크 환경에 맞게 의존성 모듈들을 설치되었고, 설정 파일도 생성이 되었습니다.
// package.json
{
"name": "story-test",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"dependencies": {
"@types/node": "20.4.0",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.6",
"eslint": "8.44.0",
"eslint-config-next": "13.4.9",
"next": "13.4.9",
"react": "18.2.0",
"react-dom": "18.2.0",
"typescript": "5.1.6"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.0.26",
"@storybook/addon-interactions": "^7.0.26",
"@storybook/addon-links": "^7.0.26",
"@storybook/blocks": "^7.0.26",
"@storybook/nextjs": "^7.0.26",
"@storybook/react": "^7.0.26",
"@storybook/testing-library": "^0.0.14-next.2",
"eslint-plugin-storybook": "^0.6.12",
"storybook": "^7.0.26"
}
}
scripts에 storybook
과 build-storybook
스크립트가 추가된 것을 확인할 수 있습니다.
각 스크립트는 스토리북을 로컬서버로 실행시키거나 스토리북을 빌드(원격 서버에 배포하기 위해)해주는 역할을 합니다.
main.ts 파일과 preview.ts 파일
스토리북 설치를 완료하면, .storybook
폴더 하위에 .main.ts
와 .preview.ts
파일이 생성됩니다.
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/nextjs";
const config: StorybookConfig = {
// 스토리북에 사용할 .mdx, .stories 파일의 위치
stories: ["../stories/**/*.mdx", "../stories/**/*.stories.@(js|jsx|ts|tsx)"],
// 적용할 addon
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
// 프레임워크 종류
framework: {
name: "@storybook/nextjs",
options: {},
},
// docs 관련
docs: {
autodocs: "tag",
},
};
export default config;
.main.ts
파일에서 스토리북에 대한 전반적인 설정을 할 수 있습니다.
The main configuration defines a Storybook project's behavior, including the location of stories, addons to use, feature flags, and other project-specific settings.
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
const preview: Preview = {
// parameters는 스토리에 대한 메타데이터 정보들, 주로 스토리북 feature와 addon에 대한 설정
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
},
};
export default preview;
preview.ts
는 preview (미리보기) 화면에 대한 설정을 적용할 수 있습니다.
Parameters are a set of static, named metadata about a story, typically used to control the behavior of Storybook features and addons. (Parameters에 대한 설명)
미리보기(Preview)는 컴포넌트 (스토리)를 미리 보여주는 영역이라고 할 수 있는데요.
preview.ts
를 통해 UI가 어떻게 렌더링될 것인지 설정할 수 있습니다.
preview.ts
내부에 CSS를 import하거나 Javascript를 로드할 수도 있는데요.
normalize.css
같은 CSS를 import하면 브라우저 별로 조금씩 다른 CSS를 정리해볼 수 있을 것 같습니다. (크로스 브라우징)
This is loaded in the Canvas UI, the “preview” iframe that renders your components in isolation. Use preview.js for global code (such as CSS imports or JavaScript mocks) that applies to all stories.
Storybook 기본 사용법
* CSF3 (Component Story Format 3), V7.0.26 기준으로 글을 작성하였습니다.
// Button.tsx
interface ButtonProps extends PropsWithChildren {
disabled?: boolean;
variant: 'primary' | 'dashed';
onClick?: () => void;
}
export default function Button({
variant,
disabled,
children,
onClick,
}: ButtonProps) {
return (
<StyledButton variant={variant} disabled={disabled} onClick={onClick}>
{children}
</StyledButton>
);
}
버튼(Button) 컴포넌트를 이용해서 스토리 파일을 만들어보겠습니다.
버튼 컴포넌트는 위와 같은 코드로 구현했습니다.
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import Button from './index';
// 메타 데이터, 제네릭에 Button 컴포넌트의 타입을 넘겨준다.
const meta: Meta<typeof Button> = {
// 사이드바에 표시할 카테고리
title: 'Components/Common/Button',
// 컴포넌트
component: Button,
// 컴포넌트에 대한 문서를 자동으로 생성
tags: ['autodocs'],
argTypes: {},
};
// 메타 데이터를 디폴트로 export
export default meta;
// 스토리 타입, StoryObj의 제네릭에 컴포넌트의 타입을 넘겨준다.
type Story = StoryObj<typeof Button>;
// 하나의 스토리, 스토리는 named export 해준다
// 스토리 이름도 사이드바 카테고리에 표시된다
export const Primary: Story = {
// 컴포넌트에 필요한 arguments, 리액트 컴포넌트에게는 Props
args: {
variant: 'primary',
children: 'Primary',
},
};
// 또 하나의 스토리 (Dashed)
export const Dashed: Story = {
args: {
variant: 'dashed',
children: 'Dashed',
},
};
// Primary 스토리의 args값을 재사용했다.
export const Disabled: Story = {
args: {
...Primary.args,
children: 'Disabled',
disabled: true,
},
};
버튼에 대한 스토리 파일(OOO.stories.tsx</
code>)을 작성합니다.
스토리북을 실행해줍니다. (npm run storybook
또는 pnpm storybook
)
기본적인 설정으로는 6006포트에 스토리북이 실행이 됩니다.
스토리북 화면에 버튼 컴포넌트에 대한 스토리 화면들이 렌더링됩니다.
메타 데이터의 title에 title: 'Components/Common/Button'
과 같이 전달해주었는데요.
/
로 카테고리를 구분합니다.
Components가 최상위 카테고리, Commpon이 다음 카테고리, Button이 그 다음 카테고리로 표시됩니다.
Decorators
컴포넌트에 대한 스토리를 렌더링했지만, 경우에 따라서 스토리를 감싸는 UI 또는 레퍼 컴포넌트가 필요할 수 있습니다.
이런 경우에는, decorators를 사용하면 됩니다.
어떤 스토리는 Redux나 Recoil 같은 상태 관리 라이브러리의 API를 사용할 수 있습니다.
이런 경우, 해당 스토리에 Provider를 전달해줘야 하는데요.
decorators를 이용할 수 있습니다.
// Button.stories.tsx
...
// Primary 스토리
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary',
},
decorators: [(story) => <div style={{ padding: 100 }}>{story()}</div>],
};
위와 같이 decorators에 배열을 전달해주고, 배열 안에 JSX를 리턴하는 함수를 넣어주면 되는데요.
이 함수는 story
를 argument로 받습니다.
story
를 레퍼 컴포넌트 (or JSX) 내부에서 호출해주면 됩니다. (story( )
)
Global Level, Component Level, Story Level
스토리북은 세 단계로 특정 옵션을 제공할 수 있습니다.
Global Level은 전역적인 범위
Component Level은 컴포넌트 범위
Story Level은 스토리 범위 를 의미합니다.
위에서 적용했던 decorators를 세 단계에 적용할 수 있는데요.
// .storybook/preview.ts
import type { Preview } from '@storybook/react';
const preview: Preview = {
decorators: [(story) => <div style={{ padding: 100 }}>{story()}</div>],
};
export default preview;
Global Level에 적용하려면 preview.ts
에 옵션을 적용하면 됩니다.
위와 같이 적용하면 패딩값이 100인 div
태그가 모든 스토리를 감쌀 것입니다.
// Button.stories.tsx
...
const meta: Meta<typeof Button> = {
title: 'Components/Common/Button',
component: Button,
tags: ['autodocs'],
argTypes: {},
decorators: [(story) => <div style={{ padding: 100 }}>{story()}</div>],
};
export default meta;
type Story = StoryObj<typeof Button>;
// Primary 스토리
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary',
}
};
Component Level에 적용하려면 Meta 정보에 옵션을 적용하면 됩니다.
위와 같이 적용하면 패딩값이 100인 div
태그가 컴포넌트 단위의 모든 스토리를 감쌀 것입니다.
// Button.stories.tsx
...
// Primary 스토리
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary',
},
decorators: [(story) => <div style={{ padding: 100 }}>{story()}</div>],
};
Story Level에 적용하려면 Story에만 옵션을 적용하면 됩니다.
위와 같이 적용하면 패딩값이 100인 div
태그가 스토리만 감쌀 것입니다.
참고
'Web' 카테고리의 다른 글
Tailwind-merge 사용 중에 클래스 충돌(class conflict)은 왜 발생할까? (0) | 2024.08.07 |
---|---|
[Web] Discord 채팅채널에 Github Action을 이용해서 Vercel 배포 알림 보내기 (Vercel Hobby Plan) (0) | 2024.05.08 |
[Web] Git 원격저장소의 브랜치 추적하기 (0) | 2023.05.24 |
[Web] Gitlab-runner 명령어 몇 가지 정리해보기 (0) | 2023.04.29 |
[Web] Gitlab-runner 설치 & Runner 등록하기 (Amazon Linux 2 - Centos 계열) (0) | 2023.04.28 |