ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [React] Story book ๋ฆฌ์•กํŠธ ์Šคํ† ๋ฆฌ๋ถ
    JavaScript/Storybook 2023. 3. 23. 11:03
    728x90
    ๋ฐ˜์‘ํ˜•

     

    ๐Ÿ—’ story book์ด๋ž€?

    story book์ด๋ž€, UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋ถ„๋ฆฌํ•ด ๊ฐœ๋ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ํ…Œ์ŠคํŠธํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ฃผ๋Š” ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

     

     

    story book ํ…œํ”Œ๋ฆฟ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ

    # ์Šคํ† ๋ฆฌ๋ถ ํ…œํ”Œ๋ฆฟ cloneํ•˜๊ธฐ
    $ npx degit chromaui/intro-storybook-react-template taskbox
    
    $ cd taskbox
    
    # dependencies ์„ค์น˜ํ•˜๊ธฐ
    $ yarn

     

     

    story book์œผ๋กœ Task ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑํ•˜๊ธฐ

     

    • title – task๋ฅผ ์„ค๋ช…ํ•ด์ฃผ๋Š” ๋ฌธ์ž์—ด
    • state - ํ˜„์žฌ ์–ด๋–ค task๊ฐ€ ๋ชฉ๋ก์— ์žˆ์œผ๋ฉฐ, ์„ ํƒ๋˜์–ด ์žˆ๋Š”์ง€์˜ ์—ฌ๋ถ€

     

    ๋จผ์ € src/components/Task.js์™€ src/components/Task.stories.js์˜ ๋‘ ํŒŒ์ผ์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

     

    ๐Ÿ“ src/components/Task.js

    import React from 'react';
    
    export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
      return (
        <div className="list-item">
          <input type="text" value={title} readOnly={true} />
        </div>
      );
    }

     

     

    ๐Ÿ“ src/components/Task.stories.js

    import React from 'react';
    
    // ์‚ฌ์šฉํ•  ์ปดํฌ๋„ŒํŠธ import
    import Task from './Task';
    
    export default {
      component: Task, // ์Šคํ† ๋ฆฌ๋ถ์— ๋‚˜ํƒ€๋‚ผ ์ปดํฌ๋„ŒํŠธ
      title: 'Task', // title: ์Šคํ† ๋ฆฌ๋ถ์— ๋‚˜ํƒ€๋‚  ์ด๋ฆ„ [optional]
    };
    
    const Template = (args) => <Task {...args} />;
    
    // Task ์ปดํฌ๋„ŒํŠธ default ๋ฒ„์ „
    export const Default = Template.bind({});
    Default.args = {
      task: {
        id: '1',
        title: 'Test Task',
        state: 'TASK_INBOX',
        updatedAt: new Date(2021, 0, 1, 9, 0),
      },
    };
    
    // Task ์ปดํฌ๋„ŒํŠธ pinned ๋ฒ„์ „
    export const Pinned = Template.bind({});
    Pinned.args = {
      task: {
        ...Default.args.task, // default Task ์ปดํฌ๋„ŒํŠธ์™€ ๊ฐ™์€ ๋‚ด์šฉ
        state: 'TASK_PINNED',
      },
    };
    
    // Task ์ปดํฌ๋„ŒํŠธ archived ๋ฒ„์ „
    export const Archived = Template.bind({});
    Archived.args = {
      task: {
        ...Default.args.task,
        state: 'TASK_ARCHIVED',
      },
    };

     

    ์Šคํ† ๋ฆฌ๋Š” ์ฃผ์–ด์ง„ ์ƒํƒœ์•ˆ์—์„œ ๋ Œ๋”๋ง๋œ ์š”์†Œ(์˜ˆ๋ฅผ ๋“ค์ž๋ฉด, prop๊ฐ€ ํฌํ•จ๋œ ์ปดํฌ๋„ŒํŠธ)๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์ด๋Š” ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ(Functional Component)์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.

     

     

    ๐Ÿ’ก Template.bind({})๋Š” ํ•จ์ˆ˜์˜ ๋ณต์‚ฌ๋ณธ์„ ๋งŒ๋“œ๋Š” ํ‘œ์ค€ JavaScript์˜ ํ•œ ๊ธฐ๋ฒ•์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์ด ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ๊ฐ์˜ ์Šคํ† ๋ฆฌ๊ฐ€ ๊ณ ์œ ํ•œ ์†์„ฑ(properties)์„ ๊ฐ–์ง€๋งŒ ๋™์ผํ•œ ๊ฒฐ๊ณผ๋ฌผ์„ ์‚ฌ์šฉํ•˜๋„๋ก ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

     

    ์ธ์ˆ˜(arguments) ๋˜๋Š” ๊ฐ„๋‹จํžˆ ์ค„์—ฌ์„œ args๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ† ๋ฆฌ๋ถ์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ ๋„ Controls addon์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. args์˜ ๊ฐ’์ด ๋ณ€ํ•˜๋ฉด ์ปดํฌ๋„ŒํŠธ๋„ ํ•จ๊ป˜ ๋ณ€ํ•ฉ๋‹ˆ๋‹ค.

     

    ์Šคํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค ๋•Œ ์šฐ๋ฆฌ๋Š” ๊ธฐ๋ณธ task ์ธ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์˜ˆ์ƒํ•˜๋Š” task์˜ ํ˜•ํƒœ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋ธ๋กœ ํ•˜์—ฌ ๋งŒ๋“ค์–ด์ง‘๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋งํ•˜์ง€๋งŒ exportํ•˜๋Š” ๊ฒƒ์€ ์ฐจํ›„ ์Šคํ† ๋ฆฌ์—์„œ ์ด๋ฅผ ์žฌ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

     

     

    ์Šคํ† ๋ฆฌ๋ถ ํ™˜๊ฒฝ์„ค์ •

    ์Šคํ† ๋ฆฌ๋ถ ํ™˜๊ฒฝ์„ค์ • ํŒŒ์ผ (.storybook/main.js)์„ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

     

    ๐Ÿ“ .storybook/main.js

    module.exports = {
     stories: ['../src/components/**/*.stories.js'],
      staticDirs: ['../public'],
      addons: [
        '@storybook/addon-links',
        '@storybook/addon-essentials',
        '@storybook/preset-create-react-app',
        '@storybook/addon-interactions',
      ],
      features: {
        postcss: false,
      },
      framework: '@storybook/react',
      core: {
        builder: 'webpack4',
      },
    };

    ์œ„์™€ ๊ฐ™์ด .storybook ํด๋”์— ๋ณ€๊ฒฝ์„ ๋งˆ์น˜์…จ๋‹ค๋ฉด, preview.js๋ฅผ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ฉ๋‹ˆ๋‹ค.

     

    ๐Ÿ“ .storybook/preview.js

    import '../src/index.css';
    
    //๐Ÿ‘‡ Configures Storybook to log the actions( onArchiveTask and onPinTask ) in the UI.
    export const parameters = {
      actions: { argTypesRegex: '^on[A-Z].*' },
      controls: {
        matchers: {
          color: /(background|color)$/i,
          date: /Date$/,
        },
      },
    };

     

     

    ๐Ÿ“ src/components/Task.js

    import React from 'react';
    
    export default function Task({ task: { id, title, state }, onArchiveTask, onPinTask }) {
      return (
        <div className={`list-item ${state}`}>
          <label className="checkbox">
            <input
              type="checkbox"
              defaultChecked={state === 'TASK_ARCHIVED'}
              disabled={true}
              name="checked"
            />
            <span
              className="checkbox-custom"
              onClick={() => onArchiveTask(id)}
              id={`archiveTask-${id}`}
              aria-label={`archiveTask-${id}`}
            />
          </label>
          <div className="title">
            <input type="text" value={title} readOnly={true} placeholder="Input title" />
          </div>
    
          <div className="actions" onClick={event => event.stopPropagation()}>
            {state !== 'TASK_ARCHIVED' && (
              // eslint-disable-next-line jsx-a11y/anchor-is-valid
              <a onClick={() => onPinTask(id)}>
                <span className={`icon-star`} id={`pinTask-${id}`} aria-label={`pinTask-${id}`} />
              </a>
            )}
          </div>
        </div>
      );
    }

     

     

    ๊ณต์‹ ๋ฌธ์„œ ์ฐธ์กฐ

    https://storybook.js.org/tutorials/intro-to-storybook/react/ko/get-started/

     

    LIST

    'JavaScript > Storybook' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

    [Storybook] ๋ฆฌ์•กํŠธ ์Šคํ† ๋ฆฌ๋ถ ์‚ฌ์šฉํ•˜๊ธฐ _TypeScript  (0) 2023.04.07
Designed by Tistory.