본문 바로가기

개발/React.js

React Storybook 사용 방법

**컴포넌트 주도 개발(Component Driven Development)

Next.js Typescript 프로젝트에 적용하는 방법 (최초)

npx create-next-app --typescript start-storybook

Storybook 초기화

npx sb init --builder webpack5
 
  • 스토리북 초기화 명령어를 치면 위와 같은 메시지가 뜨고
  • package.json에는 storybook dev dependencies가 자동으로 추가된다.
{
  // ...
  "scripts": {
    // ...
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "devDependencies": {
    "@storybook/addon-essentials": "^7.6.3",
    "@storybook/addon-interactions": "^7.6.3",
    "@storybook/addon-links": "^7.6.3",
    "@storybook/addon-onboarding": "^1.0.9",
    "@storybook/blocks": "^7.6.3",  
    "@storybook/nextjs": "^7.6.3",
    "@storybook/react": "^7.6.3",
    "@storybook/test": "^7.6.3",
    // ...
  }
}
 
  • stories 디렉토리가 추가되어 샘플 코드가 작성된 것을 볼 수 있다.
  • 다음 명령어를 실행하면 storybook을 볼 수 있다.
npm run storybook
 

Storybook 설정

 
  • .storybook 안에 main.ts와 preview.ts가 있다.
    • main.ts에서 전반적인 스토리북 설정을 할 수 있다.
    • preview.ts를 통해 UI가 어떻게 렌더링될 것인지 설정할 수 있다.
import type { StorybookConfig } from "@storybook/nextjs";

const config: StorybookConfig = {
    stories: [
        // 스토리북에 사용할 .mdx, .stories 파일의 위치
        "../stories/**/*.mdx",
        "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
    ],
    addons: [
        // 적용할 addon
        "@storybook/addon-links",
        "@storybook/addon-essentials",
        "@storybook/addon-onboarding",
        "@storybook/addon-interactions",
    ],
    framework: {
        // 프레임워크 종류
        name: "@storybook/nextjs",
        options: {},
    },
    docs: {
        // docs 관련
        autodocs: "tag",
    },
};
export default config;

Storybook 사용 방법

  • 스토리북은 컴포넌트와 그 하위 스토리의 두 가지 기본 단계로 구성되어 있다.
  • 컴포넌트
    • 스토리(story)
    • 스토리(story)
    • 스토리(story)

예시 1 - Button (Storybook 기본 제공 샘플 코드)

// Button.tsx
interface ButtonProps {
    primary?: boolean;
    backgroundColor?: string;
    size?: "small" | "medium" | "large";
    label: string;
    onClick?: () => void;
}

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>
    );
};
  • 평소 컴포넌트를 만들듯이 만든다.
  • 단, interface ButtonProps처럼 Props에 대한 정확한 정의를 명시해주어야 할 것 같다.
    • 함수 내에 인수 : 타입 으로 선언하면 제대로 인식하지 않는 것 같음.
  • Button.stories.ts → Button.tsx와 짝꿍
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;
type Story = StoryObj<typeof meta>;
 
export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary: Story = {
  ...

// Hyeseung 버튼을 추가하면 버튼 종류가 추가 된다.
export const Hyeseung: Story = {
    args: {
        size: "medium",
        label: "Button",
    },
};
  • Button에 설정된 Props가 바인딩되어 여러가지 버튼의 경우를 시뮬레이션 할 수 있다.
 
  • onClick의 경우에도 actions에서 내부 동작을 살펴볼 수 있다. 이 말인 즉슨 함수도 가상으로 시뮬레이션 해볼 수 있는 것 같다. 아직 해보진 않음…
 

예시 2 - input box

공통으로 사용할 input type text box를 만들어보았다. onChange까지 먹여보려고 했지만 일단 value값 까지만 동작을 하는 것을 목표로 했다.

import React from "react";

interface InputBoxProps {
    value: string;
    onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
}

export const InputBox = ({ value, onChange }: InputBoxProps) => {
    return <input type="text" value={value} />;
};
  • InputBox는 Props Type만 명확하게 해주고 평범하게 만들어주었다.
import { Meta, StoryObj } from "@storybook/react";
import { InputBox } from "./InputBox";

const meta = {
    title: "Example/InputBox",
    component: InputBox,
    tags: ["autodocs"],
    parameters: {
        layout: "fullscreen",
    },
} satisfies Meta<typeof InputBox>;

export default meta;
type Story = StoryObj<typeof InputBox>;

export const DefaultInput: Story = {
    args: {
        value: "안녕",
    },
};
  • stories.ts 설정을 해주었다.
  • onChange를 Props Type만 지정해주고, 컴포넌트에서는 실제 사용하지 않았는데 Props Type 내용이 그대로 스토리북이 올라간다. interface Type에 언급만 해주면 Props 문서로 올라간다.
 

위 예제를 참고하여 컴포넌트를 만들 때 stories.ts 파일을 작동해주면 될 것 같다. 더 나아가 State를 먹일 수도 있는 것 같은데 그건 다음 기회에……..

하려고 했지만 살짝 궁금해서 현재 구현되어 있는 commonInput.tsx 컴포넌트 하나를 불러와서 셋팅을 해볼까 한다. 일단 상태는 빼고… 연관된 로직은 모두 제외하고 storybook에 tailwindcss 먹여진 input text 컴포넌트 띄우는 것만 목표로 했다.


tailwindcss StoryBook에 적용하는 방법

간단하게 설정을 해주면 된다.

  1. tailwind.config.ts
const config: Config = {
    content: [
        ...
        "./app/**/*.{js,ts,jsx,tsx,mdx}",
        // Storybook의 Story 위치가 stories 안에 있을 때 다음과 같이 설정
        "./stories/**/*.{js,ts,jsx,tsx}",
    ],
    ...
  1. index.css에 다음과 같이 tailwind annotation 설정
@tailwind base;
@tailwind components;
@tailwind utilities;
  1. .storybook/preview.ts에 index.css import
    1. storybook과 tailwindcss를 이어주는 너낌
import type { Preview } from "@storybook/react";
import "../stories/index.css"; // 여기 추가

const preview: Preview = {
    parameters: {
        ...
    },
...

위 3단계만 설정해주면

 

요로케 tailwind css가 잘 먹은 것을 볼 수 있슴당!! 쉽쥬?



State 적용하기

다음 기회에…


참고

https://storybook.js.org/blog/get-started-with-storybook-and-next-js/

https://wadoob.tistory.com/5

https://storybook.js.org/tutorials/intro-to-storybook/react/ko/simple-component/