Vite + React + TypeScript 환경에 Storybook v7.0 도입하기 (1)

Hyewon Kang·2023년 7월 25일
3
post-thumbnail

개요

스토리북을 이용하면 리액트로 생성한 컴포넌트의 테스트 및 문서화를 간소화 시켜주는 효과를 얻는다.

정해진 스타일 가이드가 있다면, 디자인팀과의 협업에서 스토리북을 이용해 소통하고 수정사항등을 빠르게 체크하고, Mock 데이터를 이용해 실제 보여질 디자인을 빠르게 확인할 수 있다.


스토리북 설치하기

개발 환경 기술스택

TypeScript(4.9.3) + Vite(4.1.0) + React(18.2.0) + Styled-components(5.3.8) + Storybook(7.0.5)

아래 명령어를 이용해 setup을 해주면 사진과 같이 여러 devDependencies가 추가되고, 스토리북 실행 및 빌드를 위한 스크립트 storybookbuild-storybook이 추가된다.

npx sb init --builder @storybook/builder-vite
11

또한, 다음과 같이 .storybook 폴더와 src/stories 폴더가 생성되는데, src/stories는 샘플을 보여주는 것으로 현 프로젝트에서는 컴포넌트 각각에 stories.tsx를 정의할 것이기 때문에 삭제시켰다.

12

추가로, 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

13

해결을 위해 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

14


Storybook 작성하기

기본적으로 스토리 작성 파일명은 컴포넌트명.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;

0) 파일 생성

src/components/Button 경로에 Button.stories.tsx를 추가한다.

story 파일에서도 JSX.Element, 컴포넌트 형태로 내보낼 것이기 때문에 타입 에러 방지를 위해 tsx 확장자를 이용했다.

1) knobs addon 적용하기

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

2) 기본 설정하기

우선 컴포넌트 스토리에 대한 기본 설정을 작성한다. (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:
};

argTypes 참조

3) 스토리 작성하기

그 다음에는, 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’을 추가하게 된다.

16

설정한 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>
    );
};

17

3-3) Actions 애드온 추가하기

Actions 애드온은 컴포넌트를 통하여 특정 함수가 호출됐을 때 어떤 함수가 호출됐는지, 그리고 함수에 어떤 파라미터를 넣어서 호출했는지에 대한 정보를 확인 할 수 있게 해준다. action(’액션 이름’)

import { action } from '@storybook/addon-actions';

export const Basics = () => {
  ...

  return (
      <Button onClick={action('onClick')} {...props}>
        {label}
      </Button>
  );
};

18

4) 컴포넌트 설명 추가하기

스토리북의 Docs 페이지는 컴포넌트의 Props와 주석에 기반하여 자동으로 문서가 생성된 결과다.

이제 위 페이지에 컴포넌트에 대한 설명을 추가해보자.

먼저 Subtitle을 설정하는 방법이다. 부제목을 설정할 때에는 기본 설정의 parameterscomponentSubtitle 값을 지정할 수 있다.

// 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 페이지의 결과는 아래와 같다.

20

결과

하단 Knobs 패널을 조정하면 해당 상태가 적용된 컴포넌트 UI 결과도 확인할 수 있다.


Storybook v7.0이 2023년 3월에 배포되었는데, 그래서 preview 파일에 decorators를 추가하는 부분이라던가 새로운 버전에 대한 error log들을 쉽게 찾을 수 없어서 조금 시간이 걸렸다. 그러나 어떤 것이든 결국엔 공식문서를 꼼꼼히 읽어보면 찾을 수 있다!

소스 코드 : https://github.com/Dev-TeamOne/classmate-frontend


Reference

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

profile
강혜원의 개발일지 입니다.

1개의 댓글

comment-user-thumbnail
2023년 7월 25일

정리가 잘 된 글이네요. 도움이 됐습니다.

답글 달기