Skip to content

๐ŸŽฏstorybook ๊ณต์‹๋ฌธ์„œ ๊ณต๋ถ€ํ•˜๊ธฐ & storybook ์ž˜ ์จ๋ณด์ž

Notifications You must be signed in to change notification settings

jhy979/storybook-study

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

5 Commits
ย 
ย 
ย 
ย 

Repository files navigation

storybook ๊ณต์‹ ๋ฌธ์„œ ์ •๋ณตํ•˜๊ธฐ

๐ŸŽฏ ๋“ค์–ด๊ฐ€๊ธฐ ์ „์—..

๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ์ผ์ • ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ TaskBox๋ฅผ ๋งŒ๋“ค๊ณ  ์žˆ์–ด์š”. ์ด๋ฅผ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ ์ž‘์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‹ค๋งŒ, ๋„ˆ๋ฌด TaskBox ํ”„๋กœ์ ํŠธ์— ๊ตญํ•œ๋˜๊ธฐ ๋ณด๋‹ค๋Š” ์ œ๊ฐ€ ๋ชฐ๋ž๋˜ ๋ถ€๋ถ„๋“ค์„ ์œ„์ฃผ๋กœ ์ ์œผ๋ฉด์„œ ๊ณต๋ถ€ํ•˜๊ณ ์ž ํ•ด์š”.

1. ์„ค์น˜ํ•ด๋ด…์‹œ๋‹ค

npx create-react-app taskbox

cd taskbox

npx -p @storybook/cli sb init

2. --watchAll ๋ช…๋ น์–ด๊ฐ€ ์žˆ๋„ค์š”?

script์— ๋ช…๋ น์–ด๋ฅผ ์ถ”๊ฐ€ํ•˜๋ ค๊ณ  ํ•˜๋‹ˆ ๊ณต์‹๋ฌธ์„œ์—์„œ ๋‹ค์Œ์„ ๋งํ•˜๊ณ  ์žˆ์—ˆ์–ด์š”.

# ํ„ฐ๋ฏธ๋„์—์„œ ํ…Œ์ŠคํŠธ ์‹คํ–‰
yarn test --watchAll

# 6006๋ฒˆ ํฌํŠธ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‹คํ–‰
yarn storybook

# 3000๋ฒˆ ํฌํŠธ๋กœ App ์‹คํ–‰
yarn start

--watchAll ์ด๋ž€?

  • --watchAll ๋ช…๋ น์–ด๋Š” webpack ๋นŒ๋“œํ•  ๋–„์—๋„ ๋ณธ์ ์ด ์žˆ์—ˆ์–ด์š”.
  • ํŒŒ์ผ์— ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ๋‹ค๋ฉด ๋ชจ๋“  ํ…Œ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์‹œ๋„ํ•ด์š”.
  • ๋งŒ์•ฝ ๋ณ€๊ฒฝ๋œ ํŒŒ์ผ์— ๋Œ€ํ•ด์„œ๋งŒ ์žฌ์‹คํ–‰ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด --watch ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์„ธ์š”.

๐ŸŽฏ ๊ฐ„๋‹จํ•œ ์ปดํฌ๋„ŒํŠธ ๋งŒ๋“ค๊ธฐ

1. ํ”„๋ก ํŠธ์—”๋“œ App์˜ 3๊ฐ€์ง€ ์–‘์ƒ

  1. ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ (Jest)

  2. ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœ (Storybook)

  3. ์•ฑ ๊ทธ ์ž์ฒด (App)

๐Ÿ‘‰ ์œ„์˜ script ๋ช…๋ น์–ด์—์„œ ๋ณด์•˜๋˜ ์„ธ ์นœ๊ตฌ๋“ค์ด ๋ชจ๋‘ ์žˆ๋„ค์š”.

2. CDD

CDD๋ฅผ ๊ฒฝํ—˜ํ•ด๋ณธ์ ์ด ์žˆ์–ด์š”.

Component Driven Development๋ผ ํ•˜์—ฌ, Bottom-up ๋ฐฉ์‹์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ์‹์ด์ฃ .

storybook์€ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ ์ปดํฌ๋„ŒํŠธ์˜ UI๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— CDD์— ๊ต‰์žฅํžˆ ์ ํ•ฉํ•œ ๋ฐฉ์‹์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์–ด์š”.

Component Driven User Interfaces

3. ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ storybook

import React from 'react';

import Task from './Task';

export default {
  component: Task,
  title: 'Task',
};

const Template = (args) => <Task {...args} />;

export const Default = Template.bind({});
Default.args = {
  task: {
    id: '1',
    title: 'Test Task',
    state: 'TASK_INBOX',
    updatedAt: new Date(2018, 0, 1, 9, 0),
  },
};

export const Pinned = Template.bind({});
Pinned.args = {
  task: {
    ...Default.args.task, // ์ด๋Ÿฐ ์‹์œผ๋กœ ์žฌ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์•„์š”!
    state: 'TASK_PINNED',
  },
};

๐Ÿ‘† ์œ„๋Š” Task ์ปดํฌ๋„ŒํŠธ๋ฅผ ์œ„ํ•œ Task.stories.js ํŒŒ์ผ์ด์˜ˆ์š”.

component: storybook์„ ํ•˜๊ณ ์ž ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ

title: storybook ์•ฑ์˜ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ฐธ์กฐํ•˜๋Š” ๋ฐฉ๋ฒ•

args: storybook์„ ๋‹ค์‹œ ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ ๋„ Controls addon์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ˆ˜์ •

export๋ฅผ ์™œ ํ• ๊นŒ?

์œ„์˜ ํŒŒ์ผ์—์„œ Default, Pinned ์ฒ˜๋Ÿผ ๊ฐ๊ฐ์˜ storybook์„ exportํ•˜๊ณ  ์žˆ์–ด์š”. ์™œ ๊ทธ๋Ÿด๊นŒ์š”?

export: ์ฐจํ›„ ์Šคํ† ๋ฆฌ์—์„œ ์ด๋ฅผ ์žฌ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

๐Ÿ‘‡ ์•„๋ž˜๋Š” Task๋ฅผ map ๋Œ๋ฉด์„œ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” TaskList๋ผ๋Š” ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์Šคํ† ๋ฆฌ์˜ˆ์š”.

// TaskStories๋ฅผ ๊ฐ€์ ธ์˜ด์œผ๋กœ์จ ํŽธํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์ฃ . (์ตœ๋Œ€ํ•œ ์žฌํ™œ์šฉ)
import * as TaskStories from './Task.stories';

export const Default = Template.bind({});
Default.args = {
  // ์ž‘์„ฑํ•ด๋‘” story๋ฅผ ๋‹ค์‹œ ๊ฐ€์ ธ์™€์„œ args๋ฅผ ์žฌ์‚ฌ์šฉํ•˜๋Š” ๋ชจ์Šต์ž…๋‹ˆ๋‹ค.
  tasks: [
    { ...TaskStories.Default.args.task, id: '1', title: 'Task 1' },
    { ...TaskStories.Default.args.task, id: '2', title: 'Task 2' },
    { ...TaskStories.Default.args.task, id: '3', title: 'Task 3' },
    { ...TaskStories.Default.args.task, id: '4', title: 'Task 4' },
    { ...TaskStories.Default.args.task, id: '5', title: 'Task 5' },
    { ...TaskStories.Default.args.task, id: '6', title: 'Task 6' },
  ],
};

๐Ÿ‘‡ ์•„ ์ฐธ๊ณ ๋กœ, propTypes๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

(stories์—์„œ ์ •์˜ํ–ˆ๋˜ ๊ฒƒ์„ ์ฐ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ตฌ๋‚˜...)

import Task from './Task';

import PropTypes from 'prop-types';

TaskList.propTypes = {
  loading: PropTypes.bool,
  tasks: PropTypes.arrayOf(Task.propTypes.task).isRequired, // ์•„์ฃผ ํŽธํ•˜๊ฒŒ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉ!
  onPinTask: PropTypes.func,
  onArchiveTask: PropTypes.func,
};

4. Action

UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ๋งŒ๋“ค ๋•Œ, ์ปดํฌ๋„ŒํŠธ์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ํ™•์ธํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค˜์š”.

  • ์ข…์ข…, ์•ฑ์˜ ์ปจํ…์ŠคํŠธ์—์„œ ํ•จ์ˆ˜์™€ state์— ์ ‘๊ทผํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ์–ด์š”.

    • ์ด๋Ÿฐ ๊ฒฝ์šฐย action()์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ผ์›Œ ๋„ฃ์–ด ์ฃผ์„ธ์š”.
    • ์˜ˆ์‹œ๋Š” ์กฐ๊ธˆ ์•„๋ž˜ redux-store๋ฅผ ๋งŒ๋“œ๋Š” ๊ณณ์—์„œ ํ™•์ธ ๊ฐ€๋Šฅํ•ด์š”.
  • actions์€ ํด๋ฆญ์ด ๋˜์—ˆ์„๋•Œ Storybook UI์˜ actions ํŒจ๋„์— ๋‚˜ํƒ€๋‚  ์ฝœ๋ฐฑ์„ ์ƒ์„ฑํ• ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

  • ์˜ˆ๋ฅผ ๋“ค์–ด ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์—ˆ์„ ๋•Œ, ๋ฒ„ํŠผ ํด๋ฆญ์ด ์„ฑ๊ณต์ ์ด์—ˆ๋Š”์ง€ ํ…Œ์ŠคํŠธ UI์—์„œ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์˜ˆ์š”.

// in .storybook/preview.js

import '../src/index.css';

export const parameters = {
	// click ๋“ฑ์˜ ์ด๋ฒคํŠธ ๋“ฑ์„ ์žก์•„์„œ actions ํŒจ๋„์— ๋ณด์—ฌ์ค๋‹ˆ๋‹ค.
  // preview์˜ parameters๋Š” ๋ฏธ๋ฆฌ ๋ชจ๋“  ์•ก์…˜์„ ์ง€์ •? ํ•ด์ฃผ๋Š” ๋Š๋‚Œ์ด์˜ˆ์š”.
  actions: { argTypesRegex: '^on[A-Z].*' },
};

๋งค๊ฐœ๋ณ€์ˆ˜(parameters)๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ Storybook์˜ ๊ธฐ๋Šฅ๊ณผ ์• ๋“œ์˜จ์˜ ๋™์ž‘์„ ์ œ์–ดํ•˜๊ธฐ ์œ„ํ•˜์—ฌ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๋˜ํ•œ ์ด๋ ‡๊ฒŒ ๋ชจํ‚น์„ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์–ด์š”.

// redux store๋ฅผ ์œ„ํ•œ ์ดˆ๊ฐ„๋‹จ ๋ชจํ‚น ์˜ˆ์‹œ
const store = {
  getState: () => {
    return {
      tasks: TaskListStories.Default.args.tasks,
    };
  },
  subscribe: () => 0,
  dispatch: action('dispatch'), // dispatch๋ผ๋Š” ์•ก์…˜์ด ๋ฐœ์ƒํ•œ๋‹ค~
};

5. ์ž๋™ํ™”๋œ ํ…Œ์ŠคํŠธ

๐Ÿ˜€ ์šฐ๋ฆฌ๋Š” ์„œ๋ฒ„๋‚˜ ํ”„๋ŸฐํŠธ์—”๋“œ ์•ฑ ์ „์ฒด(npm run start)๋ฅผ ์‹คํ–‰ํ•˜์ง€ ์•Š๊ณ  ์„ฑ๊ณต์ ์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ์–ด์š”. (storybook ๐Ÿ‘๐Ÿ‘)

โš ๏ธ ํ•˜์ง€๋งŒ ๋ˆ„๊ตฐ๊ฐ€ ๊ฐ ํ…Œ์ŠคํŠธ๋ฅผ ์ผ์ผ์ด ํด๋ฆญํ•˜์—ฌ ์˜ค๋ฅ˜๋‚˜ ๊ฒฝ๊ณ  ์—†์ด ๋ Œ๋”๋ง ๋˜๋Š”์ง€ ์‚ดํŽด๋ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ์ž๋™ํ™”ํ•  ์ˆ˜๋Š” ์—†์„๊นŒ์š”?

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ (feat. snapshot test)

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ(Snapshot)๋Š” ์ฃผ์–ด์ง„ ์ž…๋ ฅ์— ๋Œ€ํ•ด ์ปดํฌ๋„ŒํŠธ์˜ "์–‘ํ˜ธํ•œ" ์ถœ๋ ฅ ๊ฐ’์„ ๊ธฐ๋กํ•œ ๋‹ค์Œ, ํ–ฅํ›„ ์ถœ๋ ฅ ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ์ปดํฌ๋„ŒํŠธ์— ํ”Œ๋ž˜๊ทธ๋ฅผ ์ง€์ •ํ•˜๋Š” ๋ฐฉ์‹์„ ๋งํ•ฉ๋‹ˆ๋‹ค.

  • ์ด๋Š” ์ƒˆ๋กœ์šด ๋ฒ„์ „์˜ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด๊ณ  ๋ฐ”๋€ ๋ถ€๋ถ„์„ ๋น ๋ฅด๊ฒŒ ํ™•์ธํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— Storybook์„ ๋ณด์™„ํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ ํ•˜๋Š” ๋ฐฉ๋ฒ•: Storyshots ์• ๋“œ์˜จ(addon)์„ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ ์Šคํ† ๋ฆฌ์— ๋Œ€ํ•œ ์Šค๋ƒ…์ƒท์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค!

  1. ๋‹ค์Œ์˜ development dependencies๋ฅผ ์ถ”๊ฐ€ํ•ด์ฃผ์„ธ์š”.
yarn add -D @storybook/addon-storyshots react-test-renderer
  1. ์„ค์น˜ ํ›„ ๋‹ค์Œ ํŒŒ์ผ ์ƒ์„ฑํ•ด์ฃผ์„ธ์š”.
// src/storybook.test.js

import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();
  1. ์Šค๋ƒ…์ƒท ์ƒ์„ฑ ์™„๋ฃŒ!

Task์Šคํ† ๋ฆฌ๋ฅผ ์œ„ํ•œ ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝย Task์˜ ๊ตฌํ˜„์„ ๋ณ€๊ฒฝํ•˜๊ฒŒ ๋˜๋ฉด, ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ํ™•์ธํ•˜๋ผ๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ํ‘œ์‹œ๋  ๊ฑฐ์˜ˆ์š”.

๐ŸŽฏ ๋ณตํ•ฉ์  ์ปดํฌ๋„ŒํŠธ ์กฐํ•ฉํ•˜๊ธฐ

1. ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ

์Šคํ† ๋ฆฌ์— ์ž„์˜์˜ ๋ž˜ํผ(wrapper)๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ•œ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.

export default {
  component: TaskList,
  title: 'TaskList',
	// ์ด๋Ÿฐ ์‹์œผ๋กœ ๋ž˜ํ•‘ ๊ฐ€๋Šฅ!
  decorators: [(story) => <div style={{ padding: '3rem' }}>{story()}</div>],
};

์œ„์˜ ์˜ˆ์‹œ์—์„œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ key๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ธฐ๋ณธ ๋‚ด๋ณด๋‚ด๊ธฐ์—์„œ ๋ Œ๋”๋ง ๋œ ์ปดํฌ๋„ŒํŠธ์— padding์„ ์ถ”๊ฐ€ํ–ˆ์—ˆ์–ด์š”.

๋˜ํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” โ€œprovidersโ€(React context๋ฅผ ์„ค์ •ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ปดํฌ๋„ŒํŠธ)์—์„œ ์Šคํ† ๋ฆฌ๋ฅผ ๊ฐ์‹ธ ์ค„ ๋•Œ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

typescript์—์„œ๋„ decorator๊ฐ€ ์กด์žฌํ•˜๋Š”๋ฐ ์ด์™€ ๋น„์Šทํ•œ ๋Š๋‚Œ์ธ ๊ฒƒ ๊ฐ™์•„์š”. ํŠน์ • ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์ „์— ๋ฏธ๋ฆฌ ์–ด๋–ค ์ž‘์—…์„ ํ•˜๊ฒŒ ํ•ด์ฃผ๋„๋ก ํ•˜๋Š” ๊ฒƒ์ด decorator๊ฐ€ ์•„๋‹๊นŒ ์‹ถ์Šต๋‹ˆ๋‹ค.

2. Jest๋ฅผ ์‚ฌ์šฉํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (Unit test)

Storybook ์Šคํ† ๋ฆฌ, ์ˆ˜๋™ ํ…Œ์ŠคํŠธ, ์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ๋Š” UI ๋ฒ„๊ทธ๋ฅผ ํ”ผํ•˜๋Š” ๋ฐ ํฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

import React from 'react';
import ReactDOM from 'react-dom';
import '@testing-library/jest-dom/extend-expect';

import { WithPinnedTasks } from './TaskList.stories'; // ๋งŒ๋“  ์Šคํ† ๋ฆฌ ๊ฐ€์ ธ์˜ค๊ธฐ

it('renders pinned tasks at the start of the list', () => {
  const div = document.createElement('div'); // div ํƒœ๊ทธ ๋งŒ๋“ค๊ณ 

  ReactDOM.render(<WithPinnedTasks {...WithPinnedTasks.args} />, div); // div ์•ˆ์— ์Šคํ† ๋ฆฌ ์‚ฌ์šฉํ•˜๊ธฐ

	// ํ•€ ์ฒ˜๋ฆฌ๋œ 6๋ฒˆ์งธ task๊ฐ€ ๊ฐ€์žฅ ์œ„์— ์žˆ๊ธฐ๋ฅผ ๊ธฐ๋Œ€ํ•ด์š”.
  const lastTaskInput = div.querySelector('.list-item:nth-child(1) input[value="Task 6 (pinned)"]');
  expect(lastTaskInput).not.toBe(null);

  ReactDOM.unmountComponentAtNode(div);
});

๐ŸŽฏ ๋ฐฐํฌ

yarn add -D chromatic

๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” chromatic์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐฐํฌํ–ˆ์–ด์š”.

์ €๋Š” ์Šคํ† ๋ฆฌ๋ถ์„ ๋ฐฐํฌํ•  ๋•Œ netlify๋ฅผ ์ด์šฉํ•ด์„œ ๋ฐฐํฌํ–ˆ์—ˆ๋Š”๋ฐ์š”,

๐Ÿค” ์™œ ๊ณต์‹๋ฌธ์„œ์—์„œ๋Š” ๋ฐฐํฌ๋ฅผ chromatic์œผ๋กœ ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ–ˆ์–ด์š”.

  • ๋‹ต์€ ๊ธˆ๋ฐฉ ๋‚˜์™”์Šต๋‹ˆ๋‹ค.

  • ์ง์ ‘ ๋ฐฐํฌํ•ด๋ณด๋‹ˆ chromatic์€ push๋  ๋•Œ๋งˆ๋‹ค ์ƒˆ๋กญ๊ฒŒ build๋ฅผ ์ง„ํ–‰ํ•˜์—ฌ ui ๋ณ€๊ฒฝ์ ๋“ค์„ ํ•œ ๋ˆˆ์— ๋ณด๊ธฐ ์‰ฌ์› ์–ด์š”. ๋˜, ์–ด๋–ค build version์œผ๋กœ๋“  ๋ฐ”๋กœ ๋Œ์•„๊ฐˆ ์ˆ˜ ์žˆ์–ด์„œ ์ข‹์•˜์Šต๋‹ˆ๋‹ค.

์Šคํ† ๋ฆฌ๋ถ ๊ณต์‹ํŽ˜์ด์ง€

๋ฐฐํฌ๋Š” ์ œ๊ฐ€ ์ •๋ฆฌํ•  ๊ฒŒ ์—†์–ด์š”.. ๊ทธ๋ƒฅ ๊ณต์‹๋ฌธ์„œ ๋”ฐ๋ผ๊ฐ€๋Š” ๊ฒƒ์ด best๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ญ๋‹ˆ๋‹ค!

๐ŸŽฏ ํ…Œ์ŠคํŠธ

์ˆ˜๋™ ํ…Œ์ŠคํŠธ

๊ฐœ๋ฐœ์ž๊ฐ€ ์ปดํฌ๋„ŒํŠธ์˜ ์ •ํ™•์„ฑ์„ ์ˆ˜๋™์œผ๋กœ ํ™•์ธํ•˜์—ฌ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œ ํ•  ๋•Œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ชจ์Šต์ด ์˜จ์ „ํ•œ์ง€ ์ ๊ฒ€ํ•˜๋Š”๋ฐ ๋„์›€์ด ๋ฉ๋‹ˆ๋‹ค.

์Šค๋ƒ…์ƒท ํ…Œ์ŠคํŠธ

Storyshots์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋ Œ๋”๋ง ๋œ ๋งˆํฌ์—…์„ ์บก์ฒ˜ํ•ฉ๋‹ˆ๋‹ค. ๋ Œ๋”๋ง ์˜ค๋ฅ˜์™€ ๊ฒฝ๊ณ ๋ฅผ ์œ ๋ฐœํ•˜๋Š” ๋งˆํฌ์—…์˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์„ ํŒŒ์•…ํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค๋‹ˆ๋‹ค.

๋‹จ์œ„ ํ…Œ์ŠคํŠธ

Jest๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ณ ์ •๋œ ์ž…๋ ฅ๊ฐ’์„ ์ฃผ์—ˆ์„ ๋•Œ ์ปดํฌ๋„ŒํŠธ์˜ ์ถœ๋ ฅ ๊ฐ’์ด ๋™์ผํ•˜๊ฒŒ ์œ ์ง€๋˜๋Š”์ง€๋ฅผ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ๋Šฅ์  ํ’ˆ์งˆ์„ ํ…Œ์ŠคํŠธํ•˜๋Š”๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

โš ๏ธ ํ•˜์ง€๋งŒ ์ด๊ฑธ๋กœ๋Š” ๋ถ€์กฑํ•˜๋‹ค๊ตฌ์š”!

์‹œ๊ฐ์  ํšŒ๊ท€ ํ…Œ์ŠคํŠธ(Visual regression test)๋Š” ์™ธ๊ด€์ƒ์˜ ๋ณ€ํ™”๋ฅผ ํฌ์ฐฉํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ์–ด์š”. (์ด๊ฑฐ ์šฐํ…Œ์„ธ์—์„œ๋„ ๋ณธ ๊ธฐ์–ต์ด ์žˆ๋Š”๋ฐ..!!)

์ƒˆ๋กœ ๋ Œ๋”๋ง ๋œ UI ์ฝ”๋“œ์˜ ์ด๋ฏธ์ง€์™€ ๊ธฐ์ค€ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ตํ•˜๋Š” ๊ฒƒ์— ์˜์กดํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์ผ UI ๋ณ€๊ฒฝ์ด ์žˆ๋‹ค๋ฉด ์šฐ๋ฆฌ๋Š” ๊ทธ์— ๋Œ€ํ•œ ์•Œ๋ฆผ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด์š”.

๋ฐฐํฌํ–ˆ์—ˆ์ฃ ? ๋ฐฐํฌ๋œ chromatic ํŽ˜์ด์ง€๋กœ ๊ฐ€๋ด…์‹œ๋‹ค. ์งœ์ž”! ๋ณ€๊ฒฝ์ ๋“ค์„ ๋ฐ”๋กœ ๋ณผ ์ˆ˜ ์žˆ์–ด์š”.

image

์—…๋ฐ์ดํŠธ๋กœ ์ธํ•ด ์‹ค์ˆ˜๋กœ ๋ฒ„๊ทธ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ๋Š” ๊ฒ€ํ† ๊ฐ€ ๋๋‚˜๋ฉด UI ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ž์‹ ์žˆ๊ฒŒ ๋ณ‘ํ•ฉ(merge) ํ•  ์ค€๋น„๊ฐ€ ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šดย ๋ณ€๊ฒฝ ์‚ฌํ•ญ์ด ๋งˆ์Œ์— ๋“œ์‹ ๋‹ค๋ฉด ๋ณ€๊ฒฝ์„ ์ˆ˜๋ฝํ•ด์ฃผ์‹œ๊ณ , ์•„๋‹ˆ๋ผ๋ฉด ์ด์ „ ์ƒํƒœ๋กœ ๋˜๋Œ๋ ค์ฃผ์„ธ์š”.


๐ŸŽฏ ์• ๋“œ์˜จ

Addons | Storybook

์• ๋“œ์˜จ ๋ชฉ๋ก๋“ค์ธ๋ฐ์š”, ์ฐธ ๋งŽ์ฃ ?

Addons ์ค‘์—์„œ ๊ฐ€์žฅ ์ž์ฃผ ์“ฐ์ด๋Š” Controls์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ด…์‹œ๋‹ค!

  • Controls๋Š” ๋””์ž์ด๋„ˆ์™€ ๊ฐœ๋ฐœ์ž๊ฐ€ ์ปดํฌ๋„ŒํŠธ์— ์ „๋‹ฌ๋˜๋Š” ๊ฐ’๋ฅผย ๋ฐ”๊พธ์–ด๋ณด๋ฉฐย ์‰ฝ๊ฒŒ ์ปดํฌ๋„ŒํŠธ์˜ ํ–‰๋™์„ ์‚ดํŽด๋ณผ ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค.

  • ๊ฒŒ๋‹ค๊ฐ€.. ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.****

image

๐Ÿค” ๊ทธ๋ž˜๋„ ์™œ ์“ฐ๋Š”์ง€ ์™€๋‹ฟ์ง€๊ฐ€ ์•Š์ฃ ?

๋น ๋ฅด๊ฒŒ ์—ฃ์ง€ ์ผ€์ด์Šค๋ฅผ ์žฌํ˜„ํ•˜๊ณ  ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Task์ปดํฌ๋„ŒํŠธ์—ย ๋Œ€๋Ÿ‰์˜ ๋ฌธ์ž์—ด์„ ์ถ”๊ฐ€ํ•œ๋‹ค๋ฉด ์–ด๋–ค ์ผ์ด ๋ฒŒ์–ด์งˆ๊นŒ์š”?

image

  • ์–ด... ๋ง์ค„์ž„ํ‘œ๊ฐ€ ๋‚˜์™€์•ผํ•  ๊ฒƒ ๊ฐ™์€๋ฐ ๊ทธ๋ƒฅ ์งค๋ ค๋ฒ„๋ฆฌ๋„ค์š” ใ… ใ… 

Controls๋Š” ์ปดํฌ๋„ŒํŠธ์— ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์ž…๋ ฅ์„ ๋น ๋ฅด๊ฒŒ ์‹œ๋„ํ•ด๋ณผ ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค๋‹ˆ๋‹ค.

์œ„์˜ ๊ธด ๋ฌธ์ž์—ด๊ณผ ๊ฐ™์€ ๊ฒฝ์šฐ๋„ ์—ฃ์ง€์ผ€์ด์Šค๊ฒ ์ฃ . ์ด๋Š” UI ๋ฌธ์ œ์ ๋“ค์„ ๋ฐœ๊ฒฌํ•˜๋Š” ์ผ์„ ์ค„์—ฌ์ค๋‹ˆ๋‹ค.

  • ์ด์ œ ์ด๋Ÿฌํ•œ ์—ฃ์ง€ ์ผ€์ด์Šค๋ฅผ ์žฌํ˜„ํ•˜๊ณ  ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

About

๐ŸŽฏstorybook ๊ณต์‹๋ฌธ์„œ ๊ณต๋ถ€ํ•˜๊ธฐ & storybook ์ž˜ ์จ๋ณด์ž

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published