스토리 파일을 뜯어보며 스토리북의 스토리 이해하기
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
애드온으로 캡처할 수 있습니다.
argTypes
사용법
argTypes
는 컴포넌트의 각 prop에 대해 속성을 설정할 수 있습니다. 여기에는 control
, description
, defaultValue
등의 속성이 포함됩니다.
기본 예시
export default {
title: 'Button',
component: Button,
argTypes: {
label: {
control: 'text', // 텍스트 입력형 컨트롤
description: 'The label text for the button',
defaultValue: 'Click me',
},
onClick: {
action: 'clicked', // 클릭 이벤트를 Storybook의 Actions 애드온에 기록
},
size: {
control: {
type: 'select', // 드롭다운 선택형 컨트롤
options: ['small', 'medium', 'large'],
},
description: 'Sets the size of the button',
},
},
};
주요 옵션 설명
control
: args의 입력 방식을 설정합니다. 이를 통해 사용자 인터페이스에서 prop을 어떻게 조작할 수 있는지를 정의합니다.
text
: 텍스트 입력boolean
: 체크박스select
: 드롭다운 메뉴color
: 색상 선택기- 기타 다양한 컨트롤 유형이 있음
description
: prop의 설명을 추가하여 자동 문서화 시 유용하게 사용됩니다.defaultValue
: 기본값을 지정할 수 있으며, 이는 스토리의 초기 상태를 설정합니다.action
: 함수형 prop을 지정하여 스토리에서 발생하는 이벤트를 Storybook의Actions
애드온으로 캡처할 수 있습니다.
구체적인 예시
export default {
title: 'ToggleSwitch',
component: ToggleSwitch,
argTypes: {
isOn: {
control: 'boolean',
description: 'Determines if the switch is in the "on" position',
defaultValue: false,
},
label: {
control: 'text',
description: 'Label displayed next to the switch',
},
onToggle: {
action: 'toggled', // 이벤트를 기록
},
},
};
export const DefaultSwitch = (args) => <ToggleSwitch {...args} />;
이 예시에서 isOn
prop은 boolean
타입으로 컨트롤되며, label
은 text
컨트롤을 사용합니다. onToggle
은 함수형 prop으로 설정되어 클릭 이벤트가 Storybook의 Actions
패널에 기록됩니다.
argTypes
를 사용하면 좋은 경우
- 사용자 인터페이스 커스터마이징: 컴포넌트의 다양한 prop을 손쉽게 시각적으로 조작할 수 있도록 설정할 때.
- 문서화 강화: 팀 내에서 컴포넌트의 prop에 대한 명확한 설명을 제공하여 문서화를 개선할 때.
- 이벤트 디버깅: 이벤트 핸들러를
action
으로 설정해 특정 이벤트가 어떻게 발생하는지를 디버깅 할 때.
argTypes
는 스토리의 args 설정을 정교하게 조정해 Storybook을 통한 컴포넌트 탐색과 사용을 크게 향상시킬 수 있습니다.
위의 내용을 요약하면 다음과 같다.
argTypes
는 스토리의 인수에 대한 추가적인 옵션을 정리한다.
쉽게 생각해보면, 리액트에서 쓰이는 props
를 상세하게 정의하고 설명하기 위한 옵션이라고 보면 된다.

위의 화면에 대한 설정을 하는 부분이 argTypes
라고 보면 될 듯 하다.
위의 요소를 보면 props
관련된 설명이 굉장히 자세하게 적혀있는 것을 볼 수 있다.
props
의 각 속성에 대해서 정의할 수도 있지만, action
에 대해서 정의할 수도 있다.
액션이라고 말하니 어려운 말 같은데, 쉽게 생각하면 함수이다. 더 쉽게 생각하면 onClick
과 같은 이벤트이다.
사용자가 특정 행동(Action)
을 하였을 때 그때의 과정을 중간에서 살펴보기 위한 설정이라고 보면 된다.
Control
의 경우는 HTML
태그의 <from>
태그에서 사용되는 <input>
태그를 떠올려보면 이해가 쉬울 듯 하다.
input
태그에도 여러 옵션을 줄 수 있는데, text
, radio
, checkbox
등의 옵션을 생각해보면 좋을 듯 하다.
아직까지 내가 이해한 바로는, 유사한거 같기도...?
✅ args
args: { onClick: fn() },
args
옵션은 props
에 전달되는 값이다.
좀 더 자세히 설명하면, 스토리북에서 각 스토리에 기본적으로 전달될 default
속성 값을 정의할 때 사용된다.
당연하게도 default
값인 만큼 각 스토리에서 컴포넌트가 랜더링 될 때 초기 상태로 적용된다.
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 = {
primary: PropTypes.bool,
backgroundColor: PropTypes.string,
size: PropTypes.oneOf(['small', 'medium', 'large']),
label: PropTypes.string.isRequired,
onClick: PropTypes.func,
};
위에서는 Button
컴포넌트를 보면 onClick
이벤트를 외부에서 받아서 주입할 수 있도록 되어 있다. (Button.propTypes
에서 확인 가능)
이에 따라서, 초기 값으로 onClick
이벤트를 정의해줄 필요가 있어서 args: {onClick: fn()}
으로 정한 듯 하다.
추가적으로 onClick
이벤트 핸들러를 삽입하기 전에, 그냥 기본 동작으로 아무것도 안함
을 설정하기 위해 mocking
을 한 것 같기도 하다.
✅ story
마지막으로 제일 중요한 스토리이다. 앞에서 이미 우리는 스토리에 대해 살펴본 적이 있다.
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
로 구분되어서 출력이 된다.
그리고 이들은 사이드바에서 선택해서 각각의 스토리의 경우를 따로 빼서 볼 수도 있다.

위의 Primary
, Secondary
등이 바로 그 화면이다.
코드를 보면 알겠지만 Button
의 props
에 들어갈 값을 정의하고 있다.
이는 앞서 설명했던 args
와 유사하게 사용된다고 봐도 될 듯 하다.
export default
해서 내보내는 부분은 초기값이라고 보면 되고, 밑에 따로 설정하는 부분은 진짜 스토리라고 봐도 될 듯 하다.
🚀 정리
- 스토리는 스토리북에서 컴포넌트가 어떻게 활용되는지를 보여주기 위한
예제
이다. .stories.
파일은 예제가 어 떻게 보여질지,default
값은 어떻게 설정할 지와, 추가적인예제(story)
로 구성된다.- 무언가 복잡해보이지만, 하나씩 뜯어보면 생각보다 별거 없다.
이렇게 정리가 될 듯하다.
다른 컴포넌트에 대한 스토리를 살펴보아도, 이정도 내용이면 이제는 옵션 값에 대해서 이해를 하는 것 정도만 남은 듯 하다.
이는 하나하나 찾아가면서 달달 외우고 쓰기 보다는, 직접 해보면서 그때그때 필요한 옵션을 찾아 사용하는게 맞는 것 같다.
찾아보면서 주변에 어떤 옵션이 있는지도 좀 보고 말이다.
지금까지는 내가 어떻게 스토리를 이해했는지를 서술했다면, 이후에는 내가 어떻게 활용하는 지에 대해서 서술하고자 한다.