스토리 파일을 뜯어보며 스토리북의 스토리 이해하기
Storybook
🎯 이 문서를 읽고 난 후의 상태
- 스토리북에서 말하는
스토리
가 무엇인지 안다. 스토리
의 구조를 안다.
🤔 가이드 문서와는 조금 다른 접근
스토리북 가이드가 정말 잘 작성되어 있는 것은 맞다.
하지만 그냥 따라가기만 해서는 재미가 없다고 생각한다.
그리고 나같이 미숙한 사람에게는 이해가 쉽지 않기도 하고 말이다.
그래서 템플릿 코드에 있는 스토리를 한번 분석해보면서 스토리를 이해하고자 한다.
📝 디렉토리 구조 파악

디렉토리를 열어보니 다음과 같은 디렉토리 구조를 갖고 있었다.
아무래도, stories
는 스토리북의 템플릿 데이터를 모아놓은 곳 같았다.
찾아보니 .css
나 .jsx
등은 어디까지나 템플릿 구성을 위한 것이고, 실상은 Button.stories.js
와 같은 .stories.
파일이 중요하다고 한다.
그리고, 스토리북이 알아서 .stories.
파일을 찾아서 보여주기에, 이게 어디에 있든 상관은 없다고 한다.
다만 추후 배포나, 관리의 측면에서 스토리들은 굳이 배포할 필요가 없기에, 따로 모아놓는 것이다.
이게 관리가 더 편하기도 하고..
🤔 스토리란 무엇인가?
본격적으로 스토리를 분석하기 전에, 스토리가 무엇인지부터 이해하고 넘어가야할 듯 하다.
A story captures the rendered state of a UI component.
스토리북 공식 문서에서는 스토리를 위와 같이 정의하고 있다.
이와 관련해서 GPT에게 물어봤을 때 다음과 같은 답변을 받을 수 있었다.
🤖 GPT의 답변
Storybook에서 스토리는 특정 컴포넌트의 단일 상태를 설명하는 예제입니다. 스토리는 사용자가 개별 UI 컴포넌트의 다양한 상태를 시각적으로 확인하고, 테스트하며, 문서화할 수 있도록 합니다. 스토리는 각 컴포넌트가 다양한 props를 통해 어떻게 동작하고 렌더링되는지 보여주는 일종의 "샘플"이라고 볼 수 있습니다.
스토리의 기본 개념
- 독립적인 예제: 스토리는 컴포넌트를 독립적으로 렌더링하여 특정 상태나 props 조합에서 컴포넌트가 어떻게 보이고 동작하는지를 확인할 수 있습니다.
- 테스트 및 문서화: Storybook은 스토리를 기반으로 컴포넌트의 다양한 상태를 문서화하고 UI 테스트를 진행할 수 있는 환경을 제공합니다.
- 인터랙션: 사용자는 Storybook의 컨트롤 패널을 통해 props를 변경하고, 다양한 상태에서 컴포넌트가 어떻게 변하는지를 실시간으로 확인할 수 있습니다.
스토리의 구성 방식
스토리는 .stories.js
, .stories.jsx
, .stories.ts
, .stories.tsx
등의 파일 내에서 정의됩니다. 각 스토리는 컴포넌트를 특정 상태로 설정하여 렌더링하는 함수나 객체로 작성됩니다.
스토리의 기본 예시
// Button.stories.js
import React from 'react';
import { Button } from './Button';
export default {
title: 'Button', // Storybook의 탐색 트리에 표시되는 제목
component: Button, // 렌더링할 컴포넌트
};
export const Primary = () => <Button label="Primary Button" primary />;
export const Secondary = () => <Button label="Secondary Button" />;
위 코드에서 Primary
와 Secondary
는 각각 Button
컴포넌트의 스토리입니다. Storybook에서는 이를 개별적으로 확인할 수 있으며, 각기 다른 props가 적용된 상태를 시각적으로 보여줍니다.
스토리의 특징
- 컴포넌트 상태 설명: 스토리는 컴포넌트가 다양한 props와 상태에서 어떻게 보이고 동작하는지를 명확하게 설명합니다.
- 독립성: 각 스토리는 독립적으로 동작하므로 다른 스토리와의 의존성이 없습니다. 이는 컴포넌트의 특정 상태를 격리된 환경에서 테스트할 수 있도록 합니다.
- 재사용성: 스토리는 UI 테스트와 문서화에 사용되며,
args
와argTypes
를 사용해 props를 쉽게 변경할 수 있습니다.
스토리 작성 시 유용한 사항
- 스토리 이름: 각 스토리는 Storybook UI에 표시될 이름을 가지며, 일반적으로 함수 이름이나 객체의 키 이름이 그 역할을 합니다.
- 스토리 그룹화: 여러 스토리를 작성할 때, 같은 컴포넌트의 스토 리를 한 파일에 모아서 작성할 수 있습니다.
- args 사용: 스토리에서 props를 전달할 때
args
를 사용하면 더 직관적으로 다양한 상태를 설정할 수 있습니다.
스토리 예시 (args 사용)
export const WithArgs = (args) => <Button {...args} />;
WithArgs.args = {
label: 'Button with args',
primary: true,
};
스토리가 중요한 이유
- 디자인 시스템 및 UI 구성 요소 개발: Storybook을 통해 UI 구성 요소를 다양한 시나리오에서 테스트함으로써 보다 안정적이고 일관성 있는 컴포넌트를 개발할 수 있습니다.
- 빠른 피드백: 스토리를 통해 컴포넌트의 다양한 상태를 미리 확인하고 피드백을 받을 수 있어 개발 속도를 높이고 버그를 줄일 수 있습니다.
- 문서화: 스토리 자체가 일종의 문서로 작용하여 팀 내 개발자, 디자이너, 비기술자 모두가 쉽게 컴포넌트를 이해할 수 있습니다.
요약하자면, 스토리는 Storybook에서 개별 컴 포넌트의 상태를 설명하고 시각적으로 확인할 수 있는 독립적인 예제입니다. 이를 통해 개발자는 컴포넌트의 다양한 상태를 손쉽게 확인하고 문서화할 수 있습니다.
쉽게 말하면, 예제
이다. 해당 컴포넌트가 어떻게 사용되는지를 보여주는 예제.
뒤에 이어서 설명하겠지만, 여기에 관련된 설정들은 예제의 환경을 설정해주거나, 초기값을 잡아주거나 하는 것들이다.
컴포넌트에 대한 문서가 스토리북이다보니, 당연하게도 여기에는 컴포넌트에 들어가는 props
가 존재한다.
이 props
가 어떻게 변하는지에 따라, component
의 레이아웃이나 여러 요소들이 달라지게 된다.
스토리에 이런 초기값에 대한 설정들을 넘겨줘서, 예제
로써 문서에서 개발자나 다른 사용자들이 쉽게 이 컴포넌트가 어떻게 동작하는지 파악할 수 있게 하는 역할이라고 보면 될 듯 하다.
📝 스토리 파악
본격적으로 딥다이브 하기에 앞서서 스토리 하나를 파해쳐보기로 했다. 바로 어떤 가이드를 따라하는 것보다는 직접 한번 하나하나 살펴보고 들어가면 좋겠다는 생각에서 였다.
스토리북은 기본적으로 컴포넌트 기반 개발(Component-Driven Development)을 기본적을 채택하고 있다.
어렵게 생각할 것 없이 컴포넌트 하나에서 시작해서, 천천히 위로 올라가면서 하나씩 완성하는 개발 방법론이다.
합성 패턴(Composition pattern)으로 살펴보는 리액트 컴포넌트 설계 핵심
내가 기존에 작성했던 글인데 한번 참고해도 좋을 듯 하다.
어쨋든, 이런 관점에서 처음 진입점을 Button
으로 잡게 되었다.
제일 작은 단위일테니 파악이 쉽지 않을까 하는 생각에서 였다.
import React from 'react';
import PropTypes from 'prop-types';
import './button.css';
/**
* Primary UI component for user interaction
*/
export const Button = ({ primary, backgroundColor, size, label, ...props }) => {
const mode = primary ? 'storybook-button--primary' : 'storybook-button--secondary';
return (
<button
type="button"
className={['storybook-button', `storybook-button--${size}`, mode].join(' ')}
style={backgroundColor && { backgroundColor }}
{...props}
>
{label}
</button>
);
};
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,
};
Button.defaultProps = {
backgroundColor: null,
primary: false,
size: 'medium',
onClick: undefined,
};
버튼의 코드는 위와 같다. 스토리북은 문서이다. 이에 따라서, input/output
등이 명확해야하는 필요가 있다.
이에 대한 좋은 방법은 타입을 지정해주는 것이다.
이에 따라서 위와 같이 별도로 타입을 명세해준 것을 볼 수 있다.
사실 이런 관점에서 개인적으로는 타입스크립트를 통해서 storybook
작성하는 것을 선호하는 편이기도 하다.
import { fn } from '@storybook/test';
import { Button } from './Button';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
export default {
title: 'Example/Button',
component: Button,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
backgroundColor: { control: 'color' },
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onClick: fn() },
};
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary = {
args: {
primary: true,
label: 'Button',
},
};
export const Secondary = {
args: {
label: 'Button',
},
};
export const Large = {
args: {
size: 'large',
label: 'Button',
},
};
export const Small = {
args: {
size: 'small',
label: 'Button',
},
};
그리고 이에 따른 stories
는 위와 같다.
Button
컴포넌트는 그렇다고 쳐도, 이를 어떻게 표현하고 있는지를 이해하는 것이 중요해보인다.
그 전에, 이게 스토리북 화면으로는 어떻게 표시되는지 함꼐 알아보자.

위와 같이 표시되는 것을 볼 수 있다.
이에 따른 컴포넌트를 하나씩 살펴보자.
✅ import문
import { fn } from '@storybook/test';
import { Button } from './Button';
최근의 코드는 위와 같이 사용하는 듯 하다.
스토리북 자체적으로 mocking
을 지원하고 있기에, 이를 위해서 fn
을 import 해오는 부분이다.
함수에 대한 mocking
이 필요없다면, 제외해도 된다.
그리고 우리가 스토리를 작성할 Button
컴포넌트를 import
한 모습이다.
✅ title
export default {
title: 'Example/Button',
주석은 무시하고 하나씩 살펴보자.
title
은 스토리북 사이드바에 어떻게 보여질지를 의미한다.

여기에 EXAMPLE
과 Button
이 보이는가? 이걸 결정하는게 title
이다.
쉽게 말하면 앞에는 디렉토리, 뒤에는 컴포넌트라고 볼 수 있다.
✅ component
component: Button,
Component
는 우리가 스토리북에서 보여줄 컴포넌트이다.
import { Button } from './Button';
아까 import
한 이 컴포넌트를 의미한다.
✅ parameter
parameters: {
layout: 'centered',
},
parameters
는 스토리의 동작을 커스터마이징 하거나, 조정하기 위해서 사용된다.
스토리의 렌더링 방법, 애드온 동작 방식, 특정 환경 설정 등을 제어할 수 있도록 도와주는 객체이다.
parameters의 주요 목적은 스토리별 설정을 Storybook에 제공하여 스토리의 표현이나 동작 방식을 제어하는 것이라고 한다.
예를 들어, 스토리의 레이아웃을 설정하거나 특정 애드온의 동작을 활성화/비활성화하는 등 다양한 옵션을 지정할 수 있다.
여기서는 layout: 'centered'
라는 옵션을 사용했다.
이게 무엇이냐면, 버튼 컴포넌트가 보이는 위치의 레이아웃을 정의하는 속성이다.

만약 이 옵션을 다음과 같이 바꾼다면 화면은 다르게 표현된다.
parameters: {
layout: 'left',
},

이런 느낌의 옵션이다. 개인적으로는 layout: 'centered'
를 유지하는 것을 추천한다.
✅ tags
tags: ['autodocs'],
tags
속성은 Storybook 7부터 도입된 새로운 기능이다.
스토리를 더 쉽게 분류하고 특정 그룹이나 유형에 따라 필터링할 수 있게 해주는 기능이다.
이 속성은 스토리에 메타데이터를 추가해주는 역할을 하며, 다양한 목적에 따라 스토리를 분류하고 관리하는 데 사용된다.
tags
는 각 스토리나 스토리 파일에 추가할 수 있는 문자열 배열로, 특정 키워드나 태그를 부여하여 Storybook의 탐색 및 필터링을 지원하며, 이를 통해 여러 스토리 중에서 특정 속성을 갖는 스토리만을 쉽게 검색할 수 있다.
autodocs
옵션은 자동으로 스토리를 문서에 포함시키는 기능이다.
//tags: ['autodocs'],
만약 tags
를 위와 같이 주석처리하면 다음과 같이 에러가 발생한다.

세부설정은 나중에 뒤에서 알아보기로 하고, 우선은 해당 옵션을 사용하는 것을 권장한다.
✅ argTypes
여기서부터가 storybook의 핵심이 되는 속성이다. 위에까지는 있는 설정 그대로 사용해도 됐다면, 여기서부터는 코딩이 조금 필요한 구간이다.
나도 지금은 배우는 단계이기에, GPT의 힘을 빌렸다. GPT가 설명한 내용은 다음과 같다.
🤖 GPT의 답변
argTypes
는 Storybook에서 스토리의 args(인수)에 대한 추가적인 메타데이터를 정의하는 옵션입니다. 이를 통해 스토리에서 사용할 컴포넌트의 props를 보다 상세히 제어하고 문서화할 수 있습니다. argTypes
를 설정하면 Storybook의 Controls 패널에 표시되는 방식이나 각 prop의 행동을 사용자 정의할 수 있습니다.
argTypes
의 주요 목적
- Controls 패널 커스터마이징: 특정 prop의 표시 방식과 종류를 정의하여 사용자가 Storybook의 UI를 통해 더 직관적으로 컴포넌트를 조작할 수 있습니다.
- 자동 문서화: 각 prop의 설명과 기본값을 설정하여 Storybook의 자동 문서화 기능을 개선할 수 있습니다.
- 이벤트 핸들링: 이벤트 prop을
action
으로 설정하여 스토리에서 발생하는 이벤트를 Storybook의Actions
애드온으로 캡처할 수 있습니다.