๐Ÿ“– Storybook

์ง€์€ยท2022๋…„ 10์›” 28์ผ
1

Node.js Library

๋ชฉ๋ก ๋ณด๊ธฐ
10/14
post-thumbnail

์ปดํฌ๋„ŒํŠธ ํƒ์ƒ‰๊ธฐ(Component Explorer)

Component Driven Development๊ฐ€ ํŠธ๋ Œ๋“œ๊ฐ€ ๋˜๋ฉด์„œ, ์ด๋ฅผ ์ง€์›ํ•˜๋Š” ๋„๊ตฌ ์ค‘ ํ•˜๋‚˜์ธ Component Explorer๊ฐ€ ๋“ฑ์žฅํ–ˆ๋‹ค.
Component Explorer์—๋Š” ๋งŽ์€ UI ๊ฐœ๋ฐœ ๋„๊ตฌ๊ฐ€ ์žˆ๋Š”๋ฐ, ๊ทธ ์ค‘ ํ•˜๋‚˜์ธ Storybook์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์ž.

Storybook

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

  • ๊ฐ๊ฐ์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ๊ตฌ์„ฑํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ๋ณต์žกํ•œ ๊ฐœ๋ฐœ ์Šคํƒ์„ ์‹œ์ž‘ํ•˜๊ฑฐ๋‚˜, ํŠน์ • ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋กœ ๊ฐ•์ œ ์ด๋™ํ•˜๊ฑฐ๋‚˜, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํƒ์ƒ‰ํ•  ํ•„์š” ์—†์ด ์ „์ฒด UI๋ฅผ ํ•œ ๋ˆˆ์— ๋ณด๊ณ  ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค.

Storybook

Storybook์„ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์€ ์ 

  • ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฌธ์„œํ™”(documentation)ํ•˜์—ฌ UI ์ปดํฌ๋„ŒํŠธ์˜ ์žฌ์‚ฌ์šฉ์„ฑ์„ ๋†’์ธ๋‹ค.
  • ์ž๋™์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹œ๊ฐํ™”ํ•ด์ค˜์„œ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ํ…Œ์ŠคํŠธ ์ƒํƒœ๋ฅผ ํ™•์ธํ•˜๊ณ , ๋ฒ„๊ทธ๋ฅผ ์‚ฌ์ „์— ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋„์™€์ค€๋‹ค.
  • Storybook์€ ๋…๋ฆฝ์ ์ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์‹คํ–‰๋˜๋ฏ€๋กœ, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋‹ค์–‘ํ•œ ์ƒํ™ฉ์— ๊ตฌ์• ๋ฐ›์ง€ ์•Š๊ณ  UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง‘์ค‘์ ์œผ๋กœ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค.
    โžก๏ธ ํ…Œ์ŠคํŠธ ๋ฐ ๊ฐœ๋ฐœ ์†๋„๋ฅผ ํ–ฅ์ƒ์‹œ์ผœ์ค€๋‹ค.

Storybook์˜ ์ฃผ์š” ๊ธฐ๋Šฅ

  • UI ์ปดํฌ๋„ŒํŠธ๋“ค์„ ์นดํƒˆ๋กœ๊ทธํ™”ํ•˜๊ธฐ
  • ์ปดํฌ๋„ŒํŠธ์˜ ๋ณ€ํ™”๋ฅผ Stories๋กœ ์ €์žฅํ•˜๊ธฐ
  • ํ•ซ ๋ชจ๋“ˆ ์žฌ ๋กœ๋”ฉ๊ณผ ๊ฐ™์€ ๊ฐœ๋ฐœ ํˆด ๊ฒฝํ—˜ ์ œ๊ณตํ•˜๊ธฐ
  • React๋ฅผ ํฌํ•จํ•œ ๋‹ค์–‘ํ•œ ๋ทฐ ๋ ˆ์ด์–ด ์ง€์›ํ•˜๊ธฐ

Storybook ์„ค์น˜ํ•˜๊ธฐ

  1. ์šฐ์„  React ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ ๋‹ค.
npx create-react-app ํ”„๋กœ์ ํŠธ๋ช…
  1. ์ƒ์„ฑ๋œ ํ”„๋กœ์ ํŠธ ํด๋” ์•ˆ์— ๋“ค์–ด๊ฐ€, Storybook์„ ์„ค์น˜ํ•œ๋‹ค.
npx storybook init

์ด ๋ช…๋ น์–ด๋Š” package.json์„ ๋ณด๊ณ  ์‚ฌ์šฉ ์ค‘์ธ ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋งž๋Š” Storybook ํ™˜๊ฒฝ์„ ์•Œ์•„์„œ ๋งŒ๋“ค์–ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, React๊ฐ€ ์•„๋‹ˆ๋”๋ผ๋„ ๋‹ค์–‘ํ•œ ํ”„๋ก ํŠธ์—”๋“œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ(Vue.js, Angular ๋“ฑ)์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  1. Storybook ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด, ํด๋” ์•ˆ์— /.storybook ํด๋”์™€ /src/stories ํด๋”๊ฐ€ ์ƒ์„ฑ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
  • /.storybook ํด๋” - Storybook ๊ด€๋ จ ์„ค์ • ํŒŒ์ผ(config ํŒŒ์ผ)
  • /src/stories ํด๋” - Storybook ์˜ˆ์‹œ ํŒŒ์ผ


Storybook ์‹คํ–‰ํ•˜๊ธฐ

  1. Storybook์ด ์„ค์น˜๋˜๋ฉด, package.json์˜ scripts์— ๋‹ค์Œ ์Šคํฌ๋ฆฝํŠธ๋“ค์ด ์ถ”๊ฐ€๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
{
  "scripts": {
    "storybook": "start-storybook -p 6006 -s public",
     // localhost:6006์—์„œ Storybook์„ ์‹คํ–‰ํ•œ๋‹ค.
    "build-storybook": "build-storybook -s public"
  }
}
  1. ๋‹ค์Œ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ด Storybook์„ ์‹คํ–‰ํ•œ๋‹ค.
npm run storybook


Storybook ์‚ฌ์šฉํ•˜๊ธฐ

Story ๋งŒ๋“œ๋Š” ๋ฒ•

1. ์ปดํฌ๋„ŒํŠธ๋ช….js ํŒŒ์ผ

src ํด๋” ์•ˆ์— ์ปดํฌ๋„ŒํŠธ๋ช….js ํŒŒ์ผ์„ ๋งŒ๋“ค๊ณ , ์•ˆ์— React ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•˜์—ฌ export ํ•ด์ค€๋‹ค.
ex) ํƒ€์ดํ‹€ story๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค๋ฉด Title.js ํŒŒ์ผ์„ ์ƒ์„ฑํ•˜๊ณ , ์•ˆ์— Title ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.

// Title.js ํŒŒ์ผ
import React from "react";

// Title ์ปดํฌ๋„ŒํŠธ๋Š” props๋กœ textContent์™€ textColor๋ฅผ ๋ฐ›๋Š”๋‹ค.
const Title = ({textContent, textColor}) => {
  return <h1 style={{color: textColor}}>{textContent}</h1>
};

// Title ์ปดํฌ๋„ŒํŠธ๋ฅผ exportํ•ด์ค€๋‹ค.
export default Title;

2. ์ปดํฌ๋„ŒํŠธ๋ช….stories.js ํŒŒ์ผ

๊ฐ™์€ ์œ„์น˜์ธ src ํด๋” ์•ˆ์— ์ปดํฌ๋„ŒํŠธ๋ช….stories.js ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.
ex) Title.stories.js

./storybook ํด๋” ์•ˆ์— ์žˆ๋Š” Storybook ์„ค์ • ํŒŒ์ผ์— ์˜ํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ํŒŒ์ผ๊ณผ ๋˜‘๊ฐ™์€ ํŒŒ์ผ ์ด๋ฆ„์— .stories๋ฅผ ๋ถ™์—ฌ ํŒŒ์ผ์„ ๋งŒ๋“ค๋ฉด ์•Œ์•„์„œ ์Šคํ† ๋ฆฌ๋กœ ์ธ์‹ํ•œ๋‹ค.

1) js ํŒŒ์ผ์—์„œ ์ปดํฌ๋„ŒํŠธ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

// Title.stories.js ํŒŒ์ผ

// Title.js ํŒŒ์ผ์—์„œ Title ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
import Title from "./Title";

export default {
    title: "Folder1/Title", // '/'๋ฅผ ๋„ฃ์–ด ์นดํ…Œ๊ณ ๋ฆฌํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.
    component: Title, // ์Šคํ† ๋ฆฌ๋กœ ๋งŒ๋“ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ช…์‹œํ•ด์ค€๋‹ค.
    argTypes: {       // ์ปดํฌ๋„ŒํŠธ์— ํ•„์š”ํ•œ ์ „๋‹ฌ์ธ์ž(props)์˜ ํƒ€์ž…์„ ์ •ํ•ด์ค€๋‹ค.
        textContent: {control: "text"},
        textColor: {control: "text"}
    }
}

2) ํ…œํ”Œ๋ฆฟ ๋งŒ๋“ค๊ธฐ

// ์ด ํ…œํ”Œ๋ฆฟ์—์„œ args๋ฅผ ์ „๋‹ฌ ๋ฐ›์•„ Title์— props๋กœ ๋‚ด๋ ค์ค€๋‹ค.
const Template = (args) => {
  return <Title {...args} />
}

3) ํ…œํ”Œ๋ฆฟ์„ ์ด์šฉํ•ด ์Šคํ† ๋ฆฌ ๋งŒ๋“ค๊ธฐ

Storybook์—์„œ ํ™•์ธํ•˜๊ณ  ์‹ถ์€ ์ปดํฌ๋„ŒํŠธ๋Š” export const๋ฅผ ์ด์šฉํ•ด ์ž‘์„ฑํ•œ๋‹ค.
ํ…œํ”Œ๋ฆฟ์„ ์ด์šฉํ•˜๋ฉด Storybook์— ์Šคํ† ๋ฆฌ๋กœ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋‹ค.

// Template.bind({})๋Š” ์ •ํ•ด์ง„ ๋ฌธ๋ฒ•์ด๋ผ๊ณ  ์ƒ๊ฐํ•˜๊ณ  ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.
export const RedTitle = Template.bind({});

4) ๋งŒ๋“ค์–ด์ค€ ์Šคํ† ๋ฆฌ์— ์ „๋‹ฌ์ธ์ž(props) ์ž‘์„ฑํ•˜๊ธฐ

RedTitle.args = {
  textContent: "This is Red Title!",
  textColor: "red"
}

5) ๊ฒฐ๊ณผ

Folder1 ์ด๋ผ๋Š” ์นดํ…Œ๊ณ ๋ฆฌ์— Title์ด ๋‹ด๊ฒจ์ ธ ์žˆ๊ณ ,
Title ์•ˆ์—๋Š” ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•ด ๋งŒ๋“  Red Title์ด๋ผ๋Š” ์Šคํ† ๋ฆฌ๊ฐ€ ๋‹ด๊ฒจ์ ธ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

Blue Title์ด๋ผ๋Š” ์Šคํ† ๋ฆฌ๋„ ํ•˜๋‚˜ ๋” ์ถ”๊ฐ€ํ•ด๋ณด์ž.

export const BlueTitle = Template.bind({});

BlueTitle.args = {
  textContent : "I am Blue Title!",
  textColor: "Blue"
}


3. ์ „๋‹ฌ ์ธ์ž๋ฅผ ์ง์ ‘ ๋ฐ›๋Š” Story ๋งŒ๋“œ๋Š” ๋ฒ•

ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ „๋‹ฌ ์ธ์ž๋ฅผ ์ง์ ‘ ๋ฐ›๋„๋ก ํ•˜๋ฉด, ์ „๋‹ฌ ์ธ์ž๋ฅผ ์ง์ ‘ ๋ฐ›๋Š” ์Šคํ† ๋ฆฌ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

// title.stories.js ํŒŒ์ผ
export const PropsTitle = (args) => {
    return <Title {...args} />
}


4. ์ „๋‹ฌ ์ธ์ž๋ฅผ ์ง์ ‘ ๋ฐ›์œผ๋ฉด์„œ Styled Components๋ฅผ ์‚ฌ์šฉํ•œ Story ๋งŒ๋“œ๋Š” ๋ฒ•

  1. Styled Components๋ฅผ ์„ค์น˜ํ•œ๋‹ค.
npm install styled-components
  1. ์ƒˆ๋กœ์šด ์ปดํฌ๋„ŒํŠธ๋ช….js ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.
// Button.js ํŒŒ์ผ

import React from "react";
import styled from 'styled-components';

// Styled Components๋ฅผ ์ด์šฉํ•ด StyledButton ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
const StyledButton = styled.button`
    background: ${(props) => props.color || "white"};
    width: ${(props) => (props.size === "big" ? "200px" : "100px")};
    height: ${(props) => (props.size === "big" ? "80px" : "40px")};
`;

// Button ์ปดํฌ๋„ŒํŠธ๋Š” ์œ„์—์„œ ์ž‘์„ฑํ•œ props์™€ text๋ฅผ ์ „๋‹ฌ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.
const Button = ({color, size, text}) => (

    <StyledButton color={color} size={size}>{text}</StyledButton>
);

// Button ์ปดํฌ๋„ŒํŠธ๋ฅผ exportํ•ด์ค€๋‹ค.
export default Button;
  1. ์ปดํฌ๋„ŒํŠธ๋ช….stories.js ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.
// Button.stories.js ํŒŒ์ผ

// Button.js ํŒŒ์ผ์—์„œ Button ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.
import Button from "./Button";

export default {
    title: "Folder2/Button",
    component: Button,

    argTypes: { // ์ปดํฌ๋„ŒํŠธ์— ํ•„์š”ํ•œ ์ „๋‹ฌ์ธ์ž(props)์˜ ํƒ€์ž…์„ ์ •ํ•ด์ค€๋‹ค.
        color: { control: 'color'},
        size: { control: { type:'radio', options : ['big', 'small'] }},
        text: { control: 'text'}
    }
};

// ํ…œํ”Œ๋ฆฟ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์ „๋‹ฌ์ธ์ž๋ฅผ ์ง์ ‘ ๋ฐ›๋Š”๋‹ค.
export const StorybookButton = (args) => (
    <Button {...args}></Button>
)

์ „๋‹ฌ์ธ์ž๋กœ ๋ฐ›์€ color, size,text๋ฅผ ์ง์ ‘ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๋‹ค.

profile
๊ฐœ๋ฐœ ๊ณต๋ถ€ ๊ธฐ๋ก ๋ธ”๋กœ๊ทธ

0๊ฐœ์˜ ๋Œ“๊ธ€