Storybook을 사용해보자(1) - 예제 뜯어보기
📌시작하며
프론트엔드 개발자는 "타 직군과 커뮤니케이션🤝 할 상황이 굉장히 많다"라는 건 아마 모두가 인정하는 사실일 것이다. 하나의 프로덕트를 위해 디자이너와 백엔드 개발자는 물론, 서비스 기획, 프로젝트 매니저 등 정말 많은 사람과 함께 달려가는거다!
(개인적으로 이 과정이 개발자가 가진 매력 중 하나가 아닐 까 생각한다. 마치 소년만화의 주인공이 된 기분!😎)
또한, 개발자는 같은 직군의 개발자와도 협력해 작업하기 때문에 ‘여러명이 작업해도 한 명이 작업한 것과 같은’ 통일성을 갖추는 것도 제품의 통일성과 퀄리티를 높이는 데 중요한 역할을 할 것이다.
이를 위해 문서화, 컴포넌트 관리가 필요해졌고 이를 편리하게 해주는 도구가 Storybook이다.
✅스토리북이란?
스토리북 공식 홈페이지는 스스로를 이렇게 설명한다.
스토리북은 UI 컴포넌트와 페이지를 독립적으로 구축하기 위한 프론트엔드 워크샵입니다. 수천 개의 팀이 UI 개발, 테스트 및 문서화를 위해 사용하고 있습니다. 오픈 소스이며 무료입니다.
계속해서 살펴보면,
- Storybook은 UI를 격리된 환경에서 구축할 수 있는 환경을 제공한다. 이를 통해 전체 앱을 실행할 필요 없이 달성하기 어려운 상태와 경계 조건을 확인할 수 있다.
- Stories는 UI 컴포넌트의 “올바른 상태”를 포착하는 실용적인 방법을 제공한다. Stories를 재사용해 자동 테스트를 구동하는데 사용할 수 있다.
- Stories는 단순히 어떻게 작동해야 하는지에 대해 정적인 디자인만을 보여주는 것이 아니라, UI가 실제로 작동하는 방식을 보여준다. 이를 통해 모든 사람이 현재 프로덕션에 대한 정보를 공유할 수 있다.
✅스토리북을 설치하자
스토리북이 어떤 것인지 위에서 확인을 했으니 실제로 사용해보자.
1
npx storybook@latest init
시작은 아래와 같이 할 수 있다.
1
npm run storybook
install 하면 .storybook
과 .stories
의 폴더가 생성된다. run하면 localhost:6006
페이지를 안내해준다.
✅스토리란?
새로 생긴 파일들을 살펴보기 앞서 먼저 스토리가 무엇인지 살펴보면, 공식문서에서는 이렇게 설명한다.
스토리는 UI 컴포넌트의 렌더링된 상태를 캡처한다. 개발자는 한 컴포넌트당 그 컴포넌트가 지원할 수 있는 모든 상태를 설명하는 스토리를 여러 개 작성한다.
이렇게만 보니 좀 어려워서 예제들을 살펴보면서 이해하고자 한다.
✅Button
stories 폴더를 여니 기본적으로 3개의 컴포넌트가 형성되었다. 이 중에서 버튼을 살펴보자.
1
2
3
4
stories/
├── button.css
├── Button.jsx
└── Button.stories.js
✅Button.stories.ts(tsx)
코드를 하나하나 살펴보자
➡️ 메타데이터 정의하기
Button.stories.ts(tsx)
에서 메타데이터를 정의하고, 해당 메타데이터를 내보내 스토리북을 구성할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./Button" 👈
const meta = {
title: "Example/Button",
component: Button, 👈
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
backgroundColor: { control: "color" },
},
} satisfies Meta<typeof Button>;
export default meta;
Button.tsx
로 만든 컴포넌트를 가져와 메타데이터에서 해당 컴포넌트를 사용함을 알려준다.
여기서 meta 변수는 Button 컴포넌트의 메타데이터를 정의한다! 또한, 기본적으로 제공하는 Meta 타입을 따르게 된다.
➡️ 스토리 정의하기
아래와 같이 Button의 Primary story를 정의할 수 있다. 여기서 args안에 작성된 값은 Button.tsx 파일의 props로 전달되어, button 컴포넌토를 다양하게 바꾸어주는 역할을 한다. 자세한 내용은 아래에 서술한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: {
primary: true,
label: "Button",
},
}
export const Secondary: Story = {
args: {
label: "Button",
},
}
export const Large: Story = {
args: {
size: "large",
label: "Button",
},
}
export const Small: Story = {
args: {
size: "small",
label: "Button",
},
}
✅Button.tsx
이번엔 Button 컴포넌트를 살펴보자!
➡️Props 타입 정의
interface를 이용해 Props의 타입을 정의하고 있다. 예제에는 아래와 같은 내용을 사용하고 있다.
1
2
3
4
5
6
7
interface ButtonProps {
primary?: boolean
backgroundColor?: string
size?: "small" | "medium" | "large"
label: string
onClick?: () => void
}
➡️UI 정의
기존에 알고 있던 컴포넌트 정의 방식을 사용한다. 만들어 둔 css 파일을 불러와준 다음 props를 구조분해 할당하고, 정의 해둔 Type을 적용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import React from "react"
import "./button.css"
export const Button = ({
primary = false,
size = "medium",
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary"
return (
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode👈].join(
" "
)}
{...props}
>
{label}
<style jsx>{`
button {
background-color: ${backgroundColor};
}
`}</style>
</button>
)
}
⚙️ mode 변수의 역할
먼저 primary 값에 따라, mode에 값을 할당한다. 해당 mode는 classname으로 사용된다. 여기서 primary의 기본 값은 false로 주었다.
1
2
3
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary"
⚙️ props를 이용한 스타일링
사이즈를 동적으로 지정해준다. props로 받은 size를 이용해 해당 컴포넌트의 크기를 적절히 변경한다.
1
2
3
4
5
6
7
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode].join(" ")}
{...props}
>
{label}
</button>
⚙️ styled-jsx
이 부분은 조금 낯설었는데, styled-jsx로, Next js에서 사용하는 CSS-in-JS 방식 스타일링 방법이다! 이를 이용해, 버튼 컴포넌트의 배경색을 backgroundColor
props에 지정된 값으로 설정할 수 있다!
1
2
3
4
5
<style jsx>{`
button {
background-color: ${backgroundColor};
}
`}</style>
📩마무리
다음은 args와 parameters 사용법을 정리해봐야겠다.😎