ํ”„๋กœ์ ํŠธ/WITHBEE_TREVEL

Next.js + Storybook์ด์šฉํ•ด์„œ ์ปดํฌ๋„ŒํŠธ ๊ฐœ๋ฐœํ•˜๊ธฐ (pnpm ์‚ฌ์šฉ)

2-doooo-2 2024. 11. 12. 22:47
728x90
๋ฐ˜์‘ํ˜•

 

๐Ÿท๏ธ๋“ค์–ด๊ฐ€๋ฉฐ 

ํ•ด๋‹น ๊ธ€์€ "next": "14.2.6", "@storybook/nextjs": "8.4.1" ๋ฒ„์ „์„ ๋ฐ”ํƒ•์œผ๋กœ ํ•œ ํ”„๋กœ์ ํŠธ์ž…๋‹ˆ๋‹ค. 

 

๐Ÿค—Storybook ์ด๋ž€? 

Storybook is a frontend workshop for building UI components and pages in isolation. Thousands of teams use it for UI development, testing, and documentation. It's open source and free.

๊ณต์‹๋ฌธ์„œ์—์„œ ์ฐพ์•„๋ณด๋ฉด "์Šคํ† ๋ฆฌ๋ถ์€ UI ๊ตฌ์„ฑ ์š”์†Œ์™€ ํŽ˜์ด์ง€๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์ œ์ž‘ํ•˜๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์ž‘์—… ํ™˜๊ฒฝ์ž…๋‹ˆ๋‹ค. ์ˆ˜์ฒœ ๊ฐœ์˜ ํŒ€์ด UI ๊ฐœ๋ฐœ, ํ…Œ์ŠคํŠธ, ๋ฌธ์„œํ™”๋ฅผ ์œ„ํ•ด Storybook์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์œผ๋ฉฐ, ์˜คํ”ˆ ์†Œ์Šค๋กœ ๋ฌด๋ฃŒ๋กœ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค " ๋ผ๊ณ  ๋‚˜์™€์žˆ๋‹ค. 

 

โ“Storybook ์–ธ์ œ,์™œ ์‚ฌ์šฉํ•˜๋Š”๊ฐ€? 

๊ทธ๋ ‡๋‹ค! ์Šคํ† ๋ฆฌ๋ถ์€ ๋ถ„๋ฆฌ ํ™˜๊ฒฝ์—์„œ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค. ๋˜ํ•œ UI ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋ฌธ์„œํ™” ํ•  ์ˆ˜๋„ ์žˆ๊ณ , ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ๊ฐœ๋ฐœํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๋‹ค. 

๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— Storybook์€ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋น ๋ฅด๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐœ๋ฐœํ•  ๋•Œ ๋งค์šฐ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉ๋œ๋‹ค. ๋˜ํ•œ ํ…Œ์ŠคํŠธ ์ž๋™ํ™” ๋ฐ ๋ฌธ์„œํ™”์—๋„ ์ ํ•ฉํ•˜์—ฌ, ํŠนํžˆ ํŒ€ ๋‹จ์œ„ ํ˜‘์—… ์‹œ ๊ฐœ๋ฐœ ํšจ์œจ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. 

 

๊ทธ๋ž˜์„œ ์ด๋ฒˆ ํŒŒ์ด๋„ ํ”„๋กœ์ ํŠธ์—์„œ ๊ณตํ†ต์œผ๋กœ ๋“ค์–ด๊ฐˆ ๋ชจ๋‹ฌ์ด๋‚˜ ๋ฒ„ํŠผ์ด ๋งŽ์•„ ์Šคํ† ๋ฆฌ๋ถ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. 

 

โœ…Next.js์— Storybook ์‚ฌ์šฉํ•˜๊ธฐ 

storybook ์…‹ํŒ…ํ•˜๊ธฐ 

์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” pnpm์‚ฌ์šฉํ•ด์„œ ๋ช…๋ น์–ด๊ฐ€ ๋‹ค๋ฅด๋ฉด ํ•ด๋‹น ์‚ฌ์ดํŠธ๋ฅผ ๋“ค์–ด๊ฐ€์„œ ์ž์‹ ์˜ ํ”„๋กœ์ ํŠธ์— ๋งž๋Š” ํŒจํ‚ค์ง€ ๋ช…๋ น์–ด๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. 

https://storybook.js.org/docs/get-started/frameworks/nextjs?renderer=react

pnpm dlx storybook@latest init

ํ•ด๋‹น ๋ช…๋ น์–ด๋Š” run๊นŒ์ง€ ํ•œ๊บผ๋ฒˆ์— ์‹คํ–‰๋˜๊ณ , ์„ค์น˜ํ•˜๊ณ  ๋‚˜์„œ๋Š” ์‹คํ–‰ ๋ช…๋ น์–ด๋งŒ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. 

 

pnpm storybook

์„ค์น˜ ํ›„์—๋Š” ํ•ด๋‹น ๋ช…๋ น์–ด๋งŒ ์น˜๋ฉด ์Šคํ† ๋ฆฌ๋ถ์ด ์‹คํ–‰๋œ๋‹ค. ๋‹ค์Œ์€ ์‹คํ–‰ํ™”๋ฉด์ด๋‹ค.

์œ„์˜ ์ด๋ฏธ์ง€๋Š” ์Šคํ† ๋ฆฌ๋ถ์˜ ๊ธฐ๋ณธ ์…‹ํŒ…์ด ์•„๋‹Œ ํ˜„์žฌ ์ง„ํ–‰์ค‘์ธ ํ”„๋กœ์ ํŠธ์—์„œ ํ™œ์šฉํ•œ ์Šคํ† ๋ฆฌ๋ถ์ด๋‹ค.

 

storybook UI๊ตฌ์กฐ 

์‹คํ–‰ ๋ช…๋ น์–ด ํ›„์—๋Š” .storybookํด๋”์™€ storiesํด๋”๊ฐ€ ์ƒ๊ธด ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

main.ts

import type { StorybookConfig } from "@storybook/nextjs";

import { join, dirname } from "path";

/**
 * This function is used to resolve the absolute path of a package.
 * It is needed in projects that use Yarn PnP or are set up within a monorepo.
 */
function getAbsolutePath(value: string): any {
  return dirname(require.resolve(join(value, "package.json")));
}
const config: StorybookConfig = {
  stories: [
    "../stories/**/*.mdx",
    "../stories/**/*.stories.@(js|jsx|mjs|ts|tsx)",
  ],
  addons: [
    getAbsolutePath("@storybook/addon-onboarding"),
    getAbsolutePath("@storybook/addon-essentials"),
    getAbsolutePath("@chromatic-com/storybook"),
    getAbsolutePath("@storybook/addon-interactions"),
  ],
  framework: {
    name: getAbsolutePath("@storybook/nextjs"),
    options: {},
  },
  staticDirs: ["../public"],
};
export default config;

main.ts ์—๋Š” storybook์„ ์œ„ํ•œ config ์„ค์ •๋“ค์ด ๋‹ด๊ฒจ์žˆ๋‹ค. pnpm dlx storybook@latest init ์„ ํ†ตํ•ด์„œ ๊ธฐ๋ณธ์œผ๋กœ ์„ค์ •๋˜๋Š” stories์™€ addons ์„ธํŒ…์ด ๋˜์–ด์žˆ๋‹ค. 

 

์ฃผ์š” ํ•จ์ˆ˜, ์†์„ฑ๋“ค 

getAbsolutePath ํ•จ์ˆ˜

  • ํŒจํ‚ค์ง€์˜ package.json ํŒŒ์ผ์„ ๊ธฐ์ค€์œผ๋กœ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• 
  • ๋ชจ๋…ธ๋ ˆํฌ๋กœ ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋Š” ํ”„๋กœ์ ํŠธ์˜ ํŒจํ‚ค์ง€ ์œ„์น˜๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ์ฐพ๊ธฐ ์œ„ํ•ด ์ ˆ๋Œ€ ๊ฒฝ๋กœ๊ฐ€ ํ•„์š”
  • require.resolve๋กœ ํŒจํ‚ค์ง€๋ฅผ ์ฐพ๊ณ , dirname์œผ๋กœ ํ•ด๋‹น ํŒจํ‚ค์ง€์˜ ์ƒ์œ„ ๋””๋ ‰ํ„ฐ๋ฆฌ ๊ฒฝ๋กœ๋ฅผ ๋ฐ˜ํ™˜

stories ์†์„ฑ 

  • Storybook์—์„œ ์‚ฌ์šฉํ•  ์Šคํ† ๋ฆฌ ํŒŒ์ผ์˜ ๊ฒฝ๋กœ๋ฅผ ์ง€์ •
  • ../stories ํด๋” ์•„๋ž˜์˜ ๋ชจ๋“  mdx ํŒŒ์ผ๊ณผ ๋‹ค์–‘ํ•œ ํ™•์žฅ์ž(js, jsx, mjs, ts, tsx)๋ฅผ ๊ฐ€์ง„ ์Šคํ† ๋ฆฌ ํŒŒ์ผ์„ ํฌํ•จํ•˜๋„๋ก ์„ค์ •

addons ์†์„ฑ 

Storybook ๊ธฐ๋Šฅ์„ ํ™•์žฅํ•˜๋Š” ์• ๋“œ์˜จ๋“ค์ด ํฌํ•จ๋˜์–ด ์žˆ๋‹ค. 
์˜ˆ์‹œ

  • @storybook/addon-onboarding: Storybook ์‹œ์ž‘ ๊ฐ€์ด๋“œ ์ œ๊ณต
  • @storybook/addon-essentials: ๊ธฐ๋ณธ์ ์ธ ์Šคํ† ๋ฆฌ๋ถ ๊ธฐ๋Šฅ ์ถ”๊ฐ€
  • @storybook/addon-interactions: ์ƒํ˜ธ์ž‘์šฉ ํ…Œ์ŠคํŠธ ์ง€์›

framework ์†์„ฑ 

  • Storybook์ด ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ง€์ •
  • Next.js์šฉ Storybook์„ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด @storybook/nextjs ํ”„๋ ˆ์ž„์›Œํฌ๋ฅผ ์ ˆ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ง€์ •

 

preview.ts

import type { Preview } from "@storybook/react";

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

preview.ts๋Š” Storybook์—์„œ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋ฉฐ, ๊ฐ ์Šคํ† ๋ฆฌ์˜ ๊ธฐ๋ณธ ๋งค๊ฐœ๋ณ€์ˆ˜์™€ ์†์„ฑ์„ ์ •์˜ํ•œ๋‹ค. 

 

 controls ์†์„ฑ 

  • ์ปดํฌ๋„ŒํŠธ์˜ props๋ฅผ Storybook์—์„œ ์กฐ์ž‘
  • color: ๋ฐฐ๊ฒฝ์ƒ‰์ด๋‚˜ ํ…์ŠคํŠธ ์ƒ‰์ƒ์ฒ˜๋Ÿผ ์ปฌ๋Ÿฌ ๊ด€๋ จ props๋ฅผ ์ž๋™ ๊ฐ์ง€ํ•˜์—ฌ ์ƒ‰์ƒ ์„ ํƒ๊ธฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • date: Date๋กœ ๋๋‚˜๋Š” props๋ฅผ ๊ฐ์ง€ํ•ด ๋‚ ์งœ ์„ ํƒ๊ธฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

matchers  ์†์„ฑ 

  • color
    • /(background|color)$/i ์ •๊ทœ ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•ด prop ์ด๋ฆ„์ด background๋‚˜ color๋กœ ๋๋‚˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€
    • ํ•ด๋‹น props๋Š” ์ปฌ๋Ÿฌ ์„ ํƒ ์ปจํŠธ๋กค์ด ์ ์šฉ๋˜์–ด Storybook์—์„œ ์ƒ‰์ƒ์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Œ 
  • date
    • /Date$/i ์ •๊ทœ ํ‘œํ˜„์‹์„ ํ†ตํ•ด prop ์ด๋ฆ„์ด Date๋กœ ๋๋‚˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์ž๋™์œผ๋กœ ๊ฐ์ง€
    • ๊ฐ์ง€๋œ props๋Š” ๋‚ ์งœ ์„ ํƒ ์ปจํŠธ๋กค์ด ์ ์šฉ๋˜์–ด Storybook์—์„œ ๋‚ ์งœ๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ์Œ

 

storybook ์ ์šฉํ•˜๊ธฐ

Button.stories.tsx

import type { Meta, StoryObj } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
  },
  args: { onClick: action('clicked') },
};

export default meta;

type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: {
    primary: true,
    label: '์„ ํƒ ์™„๋ฃŒ',
  },
};

export const LargePrimary: Story = {
  args: {
    primary: true,
    size: 'large',
    label: '๊ทธ๋ฃน ์„ ํƒ ์™„๋ฃŒ',
  },
};

export const SmallPrimary: Story = {
  args: {
    primary: true,
    size: 'small',
    label: '๋ถˆ๋Ÿฌ์˜ค๊ธฐ',
  },
};

import type { Meta, StoryObj } from '@storybook/react'

  • Meta: ์Šคํ† ๋ฆฌ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์„ค์ •. ์ฃผ๋กœ ์Šคํ† ๋ฆฌ์˜ ์ œ๋ชฉ, ์ปดํฌ๋„ŒํŠธ, ๊ธฐํƒ€ ์„ค์ •๋“ค์ด ํฌํ•จ
  • StoryObj: ๊ฐœ๋ณ„ ์Šคํ† ๋ฆฌ์˜ ์†์„ฑ ํƒ€์ž…์„ ์ •์˜. ๊ฐ ์Šคํ† ๋ฆฌ์—์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์–ด๋–ป๊ฒŒ ๋ Œ๋”๋งํ• ์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Œ

import { action } from '@storybook/addon-actions';

  • @storybook/addon-actions์—์„œ action ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ ธ์˜ด. ์ด ํ•จ์ˆ˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ๋กœ๊ทธ๋ฅผ ๊ธฐ๋กํ•˜๊ฑฐ๋‚˜ ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ
  • ๋ฒ„ํŠผ ํด๋ฆญ๊ณผ ๊ฐ™์€ ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ ํ•˜๋Š” ๋ฐ ์œ ์šฉ. ์—ฌ๊ธฐ์„œ๋Š” onClick ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ ํ•˜์—ฌ Storybook์˜ "Actions" ํŒจ๋„์— ์ถœ๋ ฅ

 

 

const meta: Meta<typeof Button> = { ... }; 

  • meta ๊ฐ์ฒด, ์Šคํ† ๋ฆฌ์—์„œ ์ปดํฌ๋„ŒํŠธ์˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ •์˜ - ์ฃผ์š” ํ•ญ๋ชฉ
    • parameters: Storybook์—์„œ ์„ค์ •ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ์ถ”๊ฐ€ ์˜ต์…˜. layout: 'centered'๋กœ ์„ค์ •ํ•˜๋ฉด ๋ฒ„ํŠผ์ด ํ™”๋ฉด ์ค‘์•™์— ์œ„์น˜
    • tags: ์ž๋™ ๋ฌธ์„œํ™” ๊ธฐ๋Šฅ์ธ autodocs ํƒœ๊ทธ๊ฐ€ ์ถ”๊ฐ€๋˜์–ด, Storybook์˜ ์ž๋™ ๋ฌธ์„œํ™” ์‹œ์Šคํ…œ์— ์˜ํ•ด ์ด ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•œ ์„ค๋ช…์ด ์ž๋™์œผ๋กœ ์ƒ์„ฑ
    • argTypes: ๊ฐ ์Šคํ† ๋ฆฌ์—์„œ ์‚ฌ์šฉ๋  argType์„ ์ •์˜ํ•˜๋Š”๋ฐ ์‚ฌ์šฉ. argTypes๋Š” ์Šคํ† ๋ฆฌ์˜ ์ œ์–ด๋ฅผ ์œ„ํ•œ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ
    • args: Button ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ๋ณธ ์ธ์ž ๊ฐ’์„ ์„ค์ •. ์—ฌ๊ธฐ์„œ๋Š” onClick ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ action('clicked')๊ฐ€ ํ˜ธ์ถœ๋˜๋„๋ก ์„ค์ •ํ•˜์—ฌ ๋ฒ„ํŠผ ํด๋ฆญ์„ ์ถ”์ 

์—ฌ๋Ÿฌ ์Šคํ† ๋ฆฌ๋“ค(Primary,Secondary, LargePrimary๋“ฑ); 

  • ๊ฐ ์Šคํ† ๋ฆฌ๋Š” Button ์ปดํฌ๋„ŒํŠธ์˜ ๋‹ค์–‘ํ•œ ๋ณ€ํ˜•์„ ๋ณด์—ฌ์ฃผ๋Š” ์˜ˆ์‹œ
  • args๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฒ„ํŠผ์˜ ์†์„ฑ์„ ์„ค์ •, ์Šคํƒ€์ผ๊ณผ ํ…์ŠคํŠธ ๋“ฑ์„ ๋ฐ”๊ฟ”์„œ ์‹œ์—ฐ 

 

Button.tsx ์ปดํฌ๋„ŒํŠธ 

"use client";
import React from "react";
import { motion } from "framer-motion";
import styles from "./button.module.css";

export interface ButtonProps {
  primary?: boolean;
  size?: "small" | "medium" | "large" | "xlarge";
  label: string;
  onClick?: () => void;
  className?: string;
}

export const Button = ({
  primary = false,
  size = "medium",
  label,
  ...props
}: ButtonProps) => {
  const mode = primary ? styles.primary : styles.secondary;

  return (
    <motion.button
      type="button"
      className={[styles[size], mode, styles.button].join(" ")}
      transition={{
        duration: 1,
        ease: "easeInOut",
        repeat: Infinity,
        repeatType: "mirror",
      }}
      {...props}
    >
      {label}
    </motion.button>
  );
};

export interface ButtonProps { ... } 

  • ButtonProps
    • primary: ๋ฒ„ํŠผ ์Šคํƒ€์ผ (๊ธฐ๋ณธ๊ฐ’์€ false)
    • size: ๋ฒ„ํŠผ ํฌ๊ธฐ (small, medium, large, xlarge ์ค‘ ์„ ํƒ ๊ฐ€๋Šฅ, ๊ธฐ๋ณธ๊ฐ’์€ medium)
    • label: ๋ฒ„ํŠผ์— ํ‘œ์‹œํ•  ํ…์ŠคํŠธ

export const Button = ({ ... }: ButtonProps) => { ... } 

  • ์ด ์ปดํฌ๋„ŒํŠธ๋Š” ButtonProps ํƒ€์ž…์„ ๋ฐ›์œผ๋ฉฐ, ๊ธฐ๋ณธ๊ฐ’์„ ์„ค์ •ํ•˜์—ฌ props๋ฅผ ๊ตฌ์กฐ ๋ถ„ํ•ด ํ• ๋‹น
  • primary, size, label์„ ๊ธฐ๋ณธ๊ฐ’์„ ์ง€์ •ํ•˜๊ณ , props๋ฅผ ๋‚˜๋จธ์ง€๋กœ ๋ฐ›์•„์„œ ์‚ฌ์šฉ

return ( ... )

  • className: ๋ฒ„ํŠผ์— ์ ์šฉํ•  CSS ํด๋ž˜์Šค๋ฅผ ์„ค์ •. styles[size]๋กœ ๋ฒ„ํŠผ ํฌ๊ธฐ๋ฅผ, mode๋กœ ๋ฒ„ํŠผ ์Šคํƒ€์ผ์„ ์„ค์ •ํ•˜๊ณ , styles.button์„ ํ•ญ์ƒ ์ถ”๊ฐ€
  • ...props: ๋‚˜๋จธ์ง€ props๋ฅผ ๋ฒ„ํŠผ ์—˜๋ฆฌ๋จผํŠธ์— ์ „๋‹ฌ. ์ด๋กœ ์ธํ•ด onClick๊ณผ ๊ฐ™์€ ๋‹ค๋ฅธ props๊ฐ€ ์ „๋‹ฌ๋  ์ˆ˜ ์žˆ์Œ
  • label: ๋ฒ„ํŠผ์˜ ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œ

 

๐Ÿค—๋งˆ์น˜๋ฉฐ

ํ•œ๋‹ฌ๋™์•ˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•˜๋ฉด์„œ ์ด ์ •๋„ ๋ณผ๋ฅจ์˜ ํ”„๋กœ์ ํŠธ์—์„œ ์Šคํ† ๋ฆฌ๋ถ์„ ์‚ฌ์šฉํ•˜๋Š”๊ฒŒ ๋งž๋‚˜? ์˜คํžˆ๋ ค ์‹œ๊ฐ„ ๋‚ญ๋น„๊ฐ€ ์•„๋‹๊นŒ๋ผ๋Š” ์˜๊ตฌ์‹ฌ์ด ๋“ค์—ˆ์ง€๋งŒ ์ทจ์ค€์ƒ ์ž…์žฅ์—์„œ ๋‹ค์–‘ํ•œ ๊ฒฝํ—˜์„ ์Œ“๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค๊ณ  ํŒ๋‹จํ–ˆ๋‹ค. ์•„์ง ์Šคํ† ๋ฆฌ๋ถ์„ ํ™œ์šฉํ•œ ๋ฒ”์œ„๊ฐ€ ๊ทธ๋ ‡๊ฒŒ ๋„“์ง€ ์•Š์•„ ํ”„๋กœ์ ํŠธ๋ฅผ ๊ณ„์† ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋” ํšจ๊ณผ์ ์œผ๋กœ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•์„ ์ฐพ๊ณ ์žํ•œ๋‹ค!!

 

์ฐธ๊ณ ์ž๋ฃŒ 

728x90
๋ฐ˜์‘ํ˜•