React+Typescript에서 스토리북 시작하기

고기호·2024년 8월 12일
1

Storybook

목록 보기
1/1

Storybook이란?


Storybook

프론트엔드 개발 환경에서 UI 컴포넌트들을 독립적으로 개발하고 테스트할 수 있게 해주는 오픈 소스 도구이다. 이를 통해 개발자는 컴포넌트를 앱의 나머지 부분과 격리된 상태에서 작업할 수 있으며, 다양한 상태와 변형을 시각적으로 확인하면서 개발을 진행할 수 있다. Storybook은 문서화, 테스트, 디자인 시스템 구축 등을 돕는 다양한 애드온과 함께 사용되어 개발 워크플로우를 크게 향상시켜 준다.

장점

개인적으로 가장 큰 장점은 개발자 또는 비개발자가 컴포넌트를 시각적으로 바로 확인을 할 수 있게 하는 것이다. 개발자 입장에서는 코드를 읽지 않고 빠르게 어떤 컴포넌트인지 확인할 수 있고, 비개발자 입장에서는 코드를 몰라도 어떤 컴포넌트인지 확인을 할 수 있다.

기본 세팅하기


설치

npx storybook@latest init

이 명령어로 다음 폴더 및 파일들이 생성된다.

  • .story 폴더 : preview.ts와 main.ts로 전역적인 설정 세팅을 할 수 있다.
  • src/stories 폴더 : 예제 스토리 코드

전역 설정


main.ts

스토리북의 동작을 정의하며, 스토리 파일의 위치, 사용할 애드온, 기능 플래그, 그리고 프로젝트별 설정 등을 포함한다.

webpackFinal로 webpack설정 추가하기

스토리북은 자체적으로 번들러 설정이 돼있고 이것을 이용해서 스토리북 UI를 렌더링한다.
그런데 여기서 트러블 슈팅이 발생한다...

많이 보던 에러다.

웹팩 설정에서 이 옵션으로 해결했는데 에러가 뜬 이유는 아마 스토리북의 자체적인 웹팩 설정에서 저 옵션이 없기 때문에 발생하는 듯 하다.

import type { StorybookConfig } from '@storybook/react-webpack5';

const config: StorybookConfig = {
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: [
    '@storybook/addon-webpack5-compiler-swc',
    '@storybook/addon-onboarding',
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@chromatic-com/storybook',
    '@storybook/addon-interactions',
  ],
  framework: {
    name: '@storybook/react-webpack5',
    options: {},
  },
  webpackFinal: async (config) => {
    // webpackFinal 옵션은 스토리북에서 자체저으로 사용하는 웹팩 설정을 변경할 수 있는 옵션이다.
    // webpack 옵션은 기존의 설정을 무시하고 사용자 옵션으로 덮어쓰기 때문에 주의해야 한다.
    config.module?.rules?.push({
      test: /\.(js|jsx|ts|tsx)$/,
      exclude: /node_modules/,
      use: [
        {
          loader: 'babel-loader',
          options: {
            presets: [
              '@babel/preset-env',
              ['@babel/preset-react', { runtime: 'automatic' }],
              '@babel/preset-typescript',
            ],
          },
        },
      ],
    });

    config.resolve?.extensions?.push('.ts', '.tsx', '.js', '.jsx');

    return config;
  },
};
export default config;

이렇게 webpackFinal 옵션을 이용해서 스토리북 웹팩에 옵션을 추가해줄 수 있다.

preview.tsx

스토리북의 전역적인 글로벌 설정, 데코레이터, 파라미터 등을 설정하는 데 사용한다.

// 모든 스토리에 적용할 설정을 하는 파일이다.
import React from 'react';
import type { Preview } from '@storybook/react';
import GlobalStyles from '../src/styles/GlobalStyles';
import { MemoryRouter } from 'react-router-dom';

const preview: Preview = {
  // parameters : 스토리의 전역적인 설정을 담당하는 객체이다.
  parameters: {
    // controls : 특정 패턴을 가진 props를 컨트롤 가능하게 만든다.
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    // actions : "on"으로 시작하는 모든 props를 액션으로 등록
    actions: { argTypesRegex: '^on.*' },
    // backgrounds : 스토리의 배경색을 변경할 수 있다.
    backgrounds: {
      default: 'var(--grey-grey100)',
    },
    // layout : 스토리 배치를 변경할 수 있다.
    layout: 'centered',
    // tags : 스토리를 스토리북 UI에서 포함하거나 제외할 수 있다(필터링 기능).
    tag: ['autodocs'],
  },
  // decorators : 전역적으로 스토리에 추가적인 렌더링을 적용할 수 있다.
  decorators: [
    (Story) => (
      <>
        {/* MemoryRouter는 메모리 내에서 URL을 관리하는 라우터이다. 브라우저의 표시줄을 변경하지 않으므로 테스트나 스토리북에서 사용하기 좋다.*/}
        <MemoryRouter>
          <Story />
          {/* GlobalStyles로 전역 스타일 적용 */}
          <GlobalStyles />
        </MemoryRouter>
      </>
    ),
  ],
};

export default preview;

GlobalStyles를 설정할 때 JSX 문법을 사용하기 때문에 확장자를 TSX또는 JSX로 바꿔줘야한다.


컴포넌트 단위 스토리 만들어보기

import { Meta, StoryObj } from '@storybook/react/*';

import Button from './Button';

// 스토리의 메타데이터를 정의한다.
const meta = {
  // 스토리의 제목을 정의한다.
  title: 'Atoms/Modal/Button',
  // 스토리의 기본 컴포넌트를 정의한다.
  component: Button,
  // 스토리의 액션, 배경색, 배치 등을 정의한다.
  parameters: {},
  // 스토리를 스토리북의 UI에서 필터링할 수 있다.
  tags: ['autodocs'],
  // 컴포넌트의 props를 정의한다.
  argTypes: {
    children: {
      control: 'text',
    },
    variant: {
      control: 'radio',
      options: ['confirm', 'close'],
    },
    type: {
      control: 'radio',
      options: ['button', 'submit'],
    },
    onClick: { action: 'clicked' },
  },
} satisfies Meta<typeof Button>; // satisfies는 Button 컴포넌트가 Meta를 만족하는지 검사한다.

export default meta;

type Story = StoryObj<typeof meta>; // StoryObj는 meta를 만족하는 Story를 만든다.

export const Confirm: Story = {
  args: {
    children: '확인',
    variant: 'confirm',
    type: 'button',
    onClick: () => console.log('clicked'),
  },
};

export const Close: Story = {
  args: {
    children: '닫기',
    variant: 'close',
    type: 'button',
    onClick: () => console.log('clicked'),
  },
};

meta의 argTypes와 Story의 args가 헷갈릴 수 있는데, argTypes경우 컴포넌트의 기본 props를 정의하는 것이고, args의 경우 해당 컴포넌트에서 파생된 스토리들의 props를 덮어씌운다는 느낌으로 이해하면 좋다.


reference

https://reactrouter.com/en/main/router-components/memory-router
https://storybook.js.org/docs

profile
웹 개발자 고기호입니다.

0개의 댓글