Storybook μ UI μ»΄ν¬λνΈλ₯Ό λ 립μ μΈ νκ²½μμ ν μ€νΈνκ³ κ°λ°ν μ μλ λκ΅¬λ‘ , νλ‘ νΈμλ νλ μμν¬μ ν¨κ» μ¬μ©λ©λλ€. νμ΄μ§ λ¨μλ‘λ λ§μ΄ μ¬μ©λμ§λ§ , μ»΄ν¬λνΈ λ¨μλ‘ μ¬μ©λ λ λ€μν μνλ μ‘°ν©μμ ν μ€νΈκ° κ°λ₯ν΄μ κ°λ°μ μλλ λΉ¨λΌμ§κ³ , νΉμ μν©λ§λ€ νμΈλ κ°λ₯νλ©° μ¬μ¬μ©λλ μ»΄ν¬λνΈλ₯Ό μμ±ν λ νΉν νΈλ¦¬ν©λλ€. μ΄λ‘ μΈν΄ λμμΈνκ³Ό κ°λ°ν μ¬μ΄μ νμ μ΄ νΈλ¦¬ν΄μ§λλ€.
monorepo λ‘ μ ννκ² λλ©΄μ μ¬μ¬μ©νλ μ»΄ν¬λνΈλ₯Ό λ§λ€μ΄μΌ νκ³ κ·Έλ¬λ€λ³΄λ μ»΄ν¬λνΈλ₯Ό λ λ§μ΄ ν μ€νΈν΄μΌ νμ΅λλ€. λν API κ° λμ΄κ°λ©΄μ κ·Έ API λ₯Ό μ¬μ©νλ νμ΄μ§μ UI ν μ€νΈλ₯Ό μ§νν΄μΌ νλ κ²½μ°λ μκ²Όμ΅λλ€. μ΄ κ²½μ°μ μ§μ κ°λ°μλ²μμλ ν μ€νΈκ° κ°λ₯νμ§λ§ , λͺ¨λ κ²½μ°μ λ°μ΄ν°λ₯Ό μμ±ν΄μ ν μ€νΈνλλ° ν° λΆνΈν¨μ΄ μμμ΅λλ€. λν, api λ‘λ© μ€ / μλ² μλ¬ λ±λ±μ μ°°λμ μκ°μ΄λΌ ν μ€νΈνκΈ° μ΄λ €μ΄ λΆλΆμ΄ μμμ΅λλ€. λμμΈν μμ μ΄λ° λ°μ΄ν° μΈν μμ μ΄λ €μμ λλΌλ κ²½μ°κ° λ§μκΈ° λλ¬Έμ ν μ€νΈκ° νλ λΆλΆμ΄ μμμ΅λλ€.
μ΄λ° λ¨μ λ€μ μ κ±°ν΄μ€ λΆλΆμ΄ storybook μ΄μμ΅λλ€. μ²μμλ λ¨μ mockingμΌλ‘ μ§νν΄μ 볡μ‘ν λΆλΆκ³Ό μνλλλ‘ μ§νλμ§ μλ λΆλΆμ΄ μμμ§λ§ , msw μ ν ν ν μ€νΈ handler μ κ°μ΄ μ¬μ©νλ©΄μ νΈλ¦¬νκ² μ‘°μμ΄ κ°λ₯νμ΅λλ€. κ²λ€κ° λμμΈ ν μμ λ°μ΄ν°λ₯Ό μ§μ λ£μ νμ μμ΄ storybook νμ΄μ§μμ ν μ€νΈκ° κ°λ₯ν΄μ UI ν μ€νΈ μκ°μ΄ νκΈ°μ μΌλ‘ λ¨μΆλμμ΅λλ€. μλ²μ μν₯μ λ°μ§ μκ³ ν μ€νΈνλ λΆλΆλ ν° μ₯μ μ΄μμ΅λλ€. μ΄λ‘ μΈν΄ λμμΈνκ³Ό νμ μ μ견μ λλκΈ° νΈλ¦¬νκ³ , κ²μλ νμΈ λΆλΆμμ μκ°μ΄ λ§μ΄ μ€μ΄λ€μ΄μ ν¨μ¨μ μΌλ‘ κ°λ°μ΄ κ°λ₯νμ΅λλ€.
μ΄μ μ μ¬μ©νλ λ²μ μ 7λ²μ μ΄μλλ° , 8λ²μ μ΄ μ λ°μ΄νΈ λλ©΄μ λμ± μ½κ² μ€νμ΄ κ°λ₯ν΄μ§ κ² κ°λ€. 곡μ λ¬Έμμ λ°λΌ μλ λͺ λ Ήμ΄λ‘ μ€μΉν΄μ€λ€.
shell
npx storybook@latest init
vite μμλ κ·Έλ₯ react μμμλ λ€λ₯΄κ² μ€μ μ λ³κ²½ν λΆλΆμ΄ μ‘΄μ¬νλ€. ν΄λΉ λΆλΆμ μ°Έκ³ λ§ν¬ μμ μμ ν μ μλ€.
κ·Έ ν , script μ storybook μ μΆκ°ν΄μ€λ€.
json
"storybook": "storybook dev -p 6006", # port λ 컀μ€ν
κ°λ₯νλ° , 6006μ λ§μ΄ μ¬μ©νλ€.
"build-storybook": "storybook build"
κ·Έ ν , μμ°μ€λ½κ² μ€ννλ©΄ storybookμ΄ μ€νλλ€.

storybook μμ msw λ₯Ό μ¬μ©νκΈ° μν΄μλ msw-storybook-addon μ΄ νμνλ€.
bash
yarn add -D msw-storybook-addon
κ·Έ ν , preview.ts μμ addon μ€μ μ μΆκ° ν msw λ₯Ό μ°κ²°ν΄μ€λ€.
ts
import { initialize, mswDecorator } from 'msw-storybook-addon';
initialize();
export const decorators = [mswDecorator];
μ€μ ν storybook μ μ€ννλ €κ³ νλ©΄ μλμ κ°μ μλ¬κ° λ±μ₯νλ©΄μ μ€νλμ§ μλλ€.

νλ©΄μ λμ€λ λ§ν¬ λ₯Ό λ°λΌκ°κ² λλ©΄ μλ¬μ μμΈμ΄ λμ€κ² λλλ° , μμ½νλ©΄ yarn λ²μ 1 μμ λ°μνλ λ¬Έμ μλ€. yarn λ²μ μ΄ μ΅μ μΈ κ²½μ° ( 4 μ΄μ ) μμλ λ¬Έμ κ° μμ§λ§ , κ·Έ μ΄νμ λ²μ μμλ μ€λ₯κ° λ°μνλ€. ν΄κ²°νκΈ° μν΄μλ package.json μ μλ μ½λλ₯Ό μΆκ°ν΄μ£Όλ©΄ λλ€.
json
"resolutions": {
"jackspeak": "2.1.1"
}
κ·Έ ν μ€ννλ©΄ , storybookμ΄ μ€νμ λμ§λ§ μλμ κ°μ μλ¬κ° λ±μ₯νλ€.

ν΄λΉ μλ¬μ λν΄μλ μ°Ύμλ΄λ κ·Έ μ€λ₯μ λν΄μ λμ€μ§ μμμ κ΅μ₯ν μ λ₯Ό λ¨Ήμλ€. λλ체 λκ° λ¬Έμ μΌ μ§ λͺ°λΌμ νμ°Έ μ°Ύλ λμ€ , addon μ d.ts νμΌμ λ€μ΄κ°λ©΄μ κ·Έ λ΅μ μ μ μμλ€.

μ΄μ κ²μκΈμμ msw v2 μμ μ€λ₯λ‘ μΈν΄μ v1μΌλ‘ λ€μ΄κ·Έλ μ΄λ νμλλ° , ν΄λΉ λΆλΆμμ μ€λ₯κ° λ°μνλ€. msw-storybook-addon μ 2.0.2 λ₯Ό μ¬μ©νκ³ μμλλ° ν΄λΉ λ²μ μμλ msw v2 λ₯Ό μ¬μ©νλ©΄μ λ¬Έλ²μ΄ λ°λμ΄ msw/browser λ₯Ό μ¬μ©νμ§λ§ , μ€μ λ‘ μ μ©λ msw v1 μμλ κ·Έλ₯ msw λ₯Ό μ¬μ©νκ³ μμ΄μ ν΄λΉ λΆλΆμμ μΆ©λμ΄ μμλ€. λ°λΌμ msw-storybook-addon μμ λ²μ 1.10.0 μΌλ‘ λ€μ΄κ·Έλ μ΄λλ₯Ό ν΄μ£Όμλ€. κ·Έ ν , μ€νμ νκ² λλ©΄ μ μμ μΌλ‘ storybook μ΄ μ€νλλ λͺ¨μ΅μ λ³Ό μ μλ€.

mswκ° μ°κ²°λ λͺ¨μ΅
λ²νΌ μ»΄ν¬λνΈμ λν κ°λ¨ν story λ₯Ό μμ±νμ΅λλ€.
tsx
// Button.stories.tsx
import { Meta, StoryFn } from '@storybook/react';
import Button from './Button';
export default {
title: 'Components/Button',
component: Button,
args: {
size: 'l',
color: 'outlined',
children: 'λ²νΌ',
},
} as Meta<typeof Button>;
const Template: StoryFn<typeof Button> = (args) => {
return <Button {...args} />;
};
export const Outlined = Template.bind({});
export const Blue = Template.bind({});
Blue.args = {
color: 'blue',
};
default
title : storybook μ’μΈ‘μ λνλλ title μ μ€μ ν μ μμ΅λλ€. / λ₯Ό μ΄μ©ν΄μ ν΄λ κ΅¬μ‘°λ‘ μμ±ν μ μμ΅λλ€.component : μμ±ν storybook μ component λ₯Ό λΆλ¬μ¬ μ μμ΅λλ€.args : μ»΄ν¬λνΈμ λ€μ΄κ° props λ€μ μ€μ ν μ μμ΅λλ€. κΈ°λ³ΈμΌλ‘ μ€μ ν μλ μμ§λ§ , μλμμ story Template μμ μ ν μλ μμ΅λλ€.Template : rendering ν μ»΄ν¬λνΈλ₯Ό μ μν©λλ€. μ΄ν , props μ λ°λΌμ λμμΈμ΄ λ€λ₯΄κ² ꡬνλ©λλ€.
bind : Template ν¨μλ₯Ό μ arg λ₯Ό ν λΉν΄μ renderingνλ μν μ ν©λλ€. arg λ bind λ΄λΆμμ μ μν μλ μμ§λ§ , λ°λ‘ λΉΌμ μ μν μλ μμ΅λλ€.
μμ±ν storybookμ νμΈν΄λ³΄λ©΄ , μ μ¬μ§μ²λΌ 보μ λλ€. μ’μΈ‘μ μ€μ ν κΈ°λ³Έ story λ€μ΄ , μ°μΈ‘μλ μ€μ ν args μ λ°λΌμ μ»΄ν¬λνΈκ° λ λλ§λ©λλ€. κ·Έλ¦¬κ³ κ·Έ μλμ μ€μ ν μ μλ controlsκ° μλλ° , μ¬κΈ°μ λ μνμ λ°λΌμ argsλ₯Ό μμ ν μ μμ΅λλ€. μμ ν μ 보μ λ°λΌμ μ¦μ λ λλ§λκ³ , μ°μΈ‘ μλ¨μμ νμΈν μ μμ΅λλ€. μ΄ controls λ₯Ό ν΅ν΄μ μν© / μνμ λ°λΌμ λ λλ§λλ λμμΈμ νμΈν μ μμ΅λλ€.
κΈ°λ³Έ νμ΄μ§μ λ²νΌμ μΆκ°νκ³ , λ²νΌμ λλ₯΄λ©΄ λ°μ΄ν°λ₯Ό μμ²ν΄μ νμΆν΄μ£Όλ μ»΄ν¬λνΈλ₯Ό μμ±νμ΅λλ€. κ·Έλ¦¬κ³ ν΄λΉ λ²νΌμ λλ μ λ , 1. μ μ νμΆ 2. λ‘λ© μ€ 3. μλ¬ λ°μ μ 3κ°μ§λ₯Ό storybook μΌλ‘ μμ±νμ΅λλ€.
ts
import { Meta, StoryFn } from "@storybook/react";
import App from "./App";
import { rest } from "msw";
import { GET_CHAMPION_DATA_RESPONSE, TFT_CHAMP_URL } from "@fixtures/tft";
export default {
title: "Pages/App",
component: App,
} as Meta<typeof App>;
const Template: StoryFn<typeof App> = () => <App />;
export const Default = Template.bind({});
Default.storyName = "κΈ°λ³Έ μν";
Default.parameters = {
msw: {
handlers: [
rest.get(TFT_CHAMP_URL, (_, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ : })
);
}),
],
},
};
= .({});
. = ;
. = {
: {
: [
rest.(, {
(ctx.());
}),
],
},
};
= .({});
. = ;
. = {
: {
: [
rest.(, {
(ctx.());
}),
],
},
};
msw μ°λμ parameter μ msw μμ±μΌλ‘ μ°κ²°ν μ μμ΅λλ€. ν΄λΉ handler μ κ°κ° μνλ μνλ₯Ό μ°κ²°ν΄ μ£Όλ©΄ λ©λλ€.
μ μ : status λ 200, λ°μ΄ν°λ μ€μ ν mock λ°μ΄ν°λ‘ μΆλ ₯λ©λλ€.

λ‘λ© μ€ : μλ΅μ λλ μ΄λ₯Ό 무μ νμΌλ‘ μ€μ ν΄μ λ‘λ© μνλ₯Ό μΆλ ₯ν©λλ€.

μλ¬ : status λ₯Ό 400 μΌλ‘ μ€μ ν΄μ μλ¬ μνλ₯Ό μ λ°ν©λλ€.

storybook μμ±νμ λ λ²μ κ³Ό νμ¬ μ λ²μ κ°μ μ°¨μ΄κ° μμ΄μ ν΄λΉ dependency ν΄κ²°μ μκ°μ΄ λ§μ΄ κ±Έλ Έλ κ² κ°μ΅λλ€. λν storybook μμ±μ μν΄ μ»΄ν¬λνΈλ₯Ό ꡬμ±νλλ°λ μκ°μ΄ κ±Έλ €μ μ΅λν λ¨μνκ² μμ±νμ΅λλ€. μ€λλ§μ μμ±ν΄μ ν·κ°λ¦¬κ³ μ΄λ €μ΄ λΆλΆμ΄ λ§μ΄ μμμ§λ§ , λ€μλ²μλ μ‘°κΈ λ μμΈν νμ΄μ§λ₯Ό μΆκ°ν΄μ μ λ²μ μΌλ‘λ μμ±ν΄λ³΄κ³ μΆμ λ§μλ λ€μμ΅λλ€.
μλͺ»λ λΆλΆμ΄ μμΌλ©΄ μλ €μ£Όμλ©΄ κ°μ¬λλ¦¬κ² μ΅λλ€. μ μμ μ΄ μλ repo λ μ¬κΈ°λ₯Ό νμΈν΄μ£ΌμΈμ !