React / Storybook үндэс

Storybook үндэс

Storybook бол UI component-уудыг бүтэн аппаас тусдаа харж, тест хийх, баримтжуулах тусгай орчин юм. Апп-г ажиллуулахгүйгээр component-оо бүх "хэлбэр"-ээр нь харж болдог — жишээ нь Button component-н primary, disabled, loading гэсэн бүх төлвийг тусдаа харуулна. Facebook, Airbnb, GitHub зэрэг компаниуд дизайн систем бүтээхдээ Storybook ашигладаг.

Яагаад Storybook хэрэгтэй вэ?

Component-ыг аппын гүнд байрлуулаагүй бол туршихад хэцүү байдаг. Жишээ нь Button component-н disabled + loading төлвийг нэгэн зэрэг харахын тулд тусгай дэлгэц гаргах шаардлагатай болдог.

Storybook-тэй бол:

код
src/components/Button/
├── Button.tsx        ← component
└── Button.stories.tsx ← бүх "хэлбэр" тодорхойлсон story файл

Button.stories.tsx нээхэд браузерт Primary, Secondary, Disabled, Loading гэсэн бүх төлвийг хажуу хажуугаар харж болно — апп ажиллуулах шаардлагагүй.

Storybook суулгах

bash
npx storybook@latest init

Vite + React аппыг автоматаар таньж тохируулна. Суусны дараа:

bash
npm run storybook

http://localhost:6006 хаягаар Storybook нээгдэнэ. src/stories/ хавтаст жишээ story-нууд байгааг харна — тэдгээрийг ажиглаад эхэлж болно.

Анхны Story бичих

Button component-н story:

tsx
// src/components/Button/Button.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import Button from "./Button";

// Файлын мета мэдээлэл — Storybook-д хэрхэн харагдахыг тодорхойлно
const meta: Meta<typeof Button> = {
  title: "UI/Button", // Storybook sidebar-д байрлах зам
  component: Button,
  tags: ["autodocs"], // Props-ыг автоматаар баримтжуулна
  args: {
    // Бүх story-д нийтлэг default утгууд
    onClick: () => {},
  },
};

export default meta;
type Story = StoryObj<typeof Button>;

// Тус тус story — component-н нэг "хэлбэр"
export const Primary: Story = {
  args: {
    label: "Эхлэх",
    variant: "primary",
  },
};

export const Secondary: Story = {
  args: {
    label: "Болих",
    variant: "secondary",
  },
};

export const Disabled: Story = {
  args: {
    label: "Идэвхгүй",
    variant: "primary",
    disabled: true,
  },
};

export const Loading: Story = {
  args: {
    label: "Хадгалж байна...",
    variant: "primary",
    isLoading: true,
  },
};

export const AllVariants: Story = {
  render: () => (
    <div style={{ display: "flex", gap: 12 }}>
      <Button label="Primary" variant="primary" />
      <Button label="Secondary" variant="secondary" />
      <Button label="Danger" variant="danger" />
    </div>
  ),
};

Нарийн component-н Story

Илүү олон props-тэй CourseCard component-н story:

tsx
// src/components/CourseCard/CourseCard.stories.tsx
import type { Meta, StoryObj } from "@storybook/react";
import CourseCard from "./CourseCard";

const meta: Meta<typeof CourseCard> = {
  title: "Course/CourseCard",
  component: CourseCard,
  tags: ["autodocs"],
  parameters: {
    // Story-г хэрхэн харуулах — centered: дунд байрлуулна
    layout: "centered",
  },
};

export default meta;
type Story = StoryObj<typeof CourseCard>;

export const Free: Story = {
  args: {
    title: "JavaScript үндэс",
    lessonCount: 20,
    color: "green",
    isFree: true,
    courseNum: "01",
    onEnroll: () => alert("Бүртгүүллээ!"),
  },
};

export const Pro: Story = {
  args: {
    title: "React үндэс",
    lessonCount: 45,
    color: "blue",
    isFree: false,
    courseNum: "04",
    onEnroll: () => {},
  },
};

export const WithProgress: Story = {
  args: {
    ...Pro.args,
    completedCount: 18,
  },
};

// Бүх курсийн өнгийг нэг дор харуулах
export const AllColors: Story = {
  render: () => (
    <div
      style={{
        display: "grid",
        gridTemplateColumns: "repeat(3, 300px)",
        gap: 16,
      }}
    >
      {[
        { title: "JavaScript", color: "green", num: "01" },
        { title: "TypeScript", color: "purple", num: "02" },
        { title: "React", color: "blue", num: "04" },
        { title: "Next.js", color: "indigo", num: "05" },
        { title: "Go", color: "teal", num: "07" },
        { title: "Python", color: "amber", num: "03" },
      ].map((c) => (
        <CourseCard
          key={c.num}
          title={c.title}
          color={c.color}
          courseNum={c.num}
          lessonCount={20}
          isFree={false}
          onEnroll={() => {}}
        />
      ))}
    </div>
  ),
};

Storybook-д Provider нэмэх

Context ашигладаг component-ыг Story-д ажиллуулахын тулд Provider-г .storybook/preview.tsx-д нэмнэ:

tsx
// .storybook/preview.tsx
import type { Preview } from "@storybook/react";
import { ThemeProvider } from "../src/contexts/ThemeContext";
import "../src/app/globals.css";

const preview: Preview = {
  decorators: [
    // Бүх story-г ThemeProvider-д боодог
    (Story) => (
      <ThemeProvider>
        <Story />
      </ThemeProvider>
    ),
  ],
  parameters: {
    backgrounds: {
      default: "dark",
      values: [
        { name: "dark", value: "#0b1120" },
        { name: "light", value: "#ffffff" },
      ],
    },
  },
};

export default preview;

Storybook нь зөвхөн том компаниудад хэрэгтэй юм биш — нэг хөгжүүлэгч ч component-оо цэгцтэй хадгалах, хуучин харагдах байдлыг дурдахад маш хэрэгтэй. Курс энд дуусахад Storybook нэмж апп-аа бүрэн дүүрэн болгохыг зөвлөж байна.

Дараагийн хичээлд:

Эцсийн төсөл — курсын туршид сурсан бүх ойлголтоо нэгтгэсэн бодит React апп бүтээнэ. Component-уудыг бичих, state удирдах, TypeScript хэрэглэх, тест бичих — бүгдийг нэг дор хэрэглэх цаг боллоо.