Storybook - документация компонентов

16 июня 2026
Автор

Олег Марков

Введение

При разработке крупных React-приложений быстро возникает проблема: компоненты становятся сложными, их трудно тестировать изолированно, а документация быстро устаревает. Storybook — это инструмент разработки пользовательских интерфейсов, который позволяет создавать, документировать и тестировать компоненты в полной изоляции от основного приложения.

В этой статье вы узнаете, что такое Storybook и зачем он нужен, как установить и настроить его в React-проекте, научитесь писать истории (stories) для компонентов, работать с аддонами, документировать props с помощью TypeScript, а также интегрировать Storybook в процесс разработки и CI/CD.

Что такое Storybook и зачем он нужен

Storybook — это среда разработки, которая позволяет работать с компонентами в полной изоляции. Каждый компонент отображается в отдельной «истории» (story), где вы можете задавать любые props и наблюдать за поведением компонента без необходимости запускать всё приложение.

Основные преимущества Storybook

  • Разработка в изоляции. Вы создаёте компоненты без зависимости от состояния приложения, маршрутизации или API.
  • Живая документация. Stories — это одновременно документация и демонстрация компонента. Она всегда актуальна, так как тесно связана с кодом.
  • Визуальное тестирование. Вы можете визуально проверять компоненты во всех возможных состояниях: загрузка, ошибка, пустое состояние, заполненное состояние.
  • Совместная работа. Дизайнеры, менеджеры и тестировщики могут просматривать компоненты без необходимости запускать код.
  • Переиспользование компонентов. Storybook стимулирует создание универсальных компонентов, которые легко переиспользовать в разных частях приложения.

Когда стоит использовать Storybook

Storybook особенно полезен, когда:

  • Команда разрабатывает дизайн-систему или библиотеку компонентов.
  • Над проектом работают несколько разработчиков, и важна согласованность UI.
  • Компоненты имеют множество состояний, которые сложно воспроизвести в реальном приложении.
  • Нужна документация, которую могут читать нетехнические члены команды.

Установка и настройка Storybook в React проекте

Автоматическая установка

Storybook предоставляет удобный инициализатор, который автоматически обнаруживает используемый фреймворк и настраивает всё необходимое.

Для существующего React-проекта выполните:

npx storybook@latest init

Команда автоматически:

  • Определит, что вы используете React (с Vite или Webpack).
  • Установит необходимые зависимости.
  • Создаст конфигурационный файл .storybook/main.ts.
  • Создаст файл с настройками предварительного просмотра .storybook/preview.ts.
  • Добавит примеры stories в папку src/stories/.
  • Добавит скрипты в package.json.

После установки запустите Storybook:

npm run storybook

По умолчанию Storybook запускается на порту 6006. Откройте http://localhost:6006 в браузере.

Ручная установка

Если автоматическая установка не подходит, установите зависимости вручную:

npm install --save-dev @storybook/react @storybook/addon-essentials
npm install --save-dev @storybook/react-vite  # для Vite
# или
npm install --save-dev @storybook/react-webpack5  # для Webpack

Создайте конфигурацию .storybook/main.ts:

import type { StorybookConfig } from "@storybook/react-vite";

const config: StorybookConfig = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-onboarding",
    "@storybook/addon-essentials",
    "@chromatic-com/storybook",
    "@storybook/addon-interactions",
  ],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
};

export default config;

Структура проекта с Storybook

Рекомендованная структура проекта:

src/
├── components/
│   ├── Button/
│   │   ├── Button.tsx
│   │   ├── Button.stories.tsx    ← stories рядом с компонентом
│   │   └── Button.test.tsx
│   ├── Input/
│   │   ├── Input.tsx
│   │   └── Input.stories.tsx
│   └── ...
.storybook/
├── main.ts
└── preview.ts

Размещение файлов stories рядом с компонентом — наиболее распространённый подход, так как упрощает навигацию и поиск.

Написание Stories

Формат Component Story Format (CSF)

Начиная со Storybook 6, рекомендуется использовать формат CSF (Component Story Format) на основе ES-модулей. Это стандартный способ написания stories.

Пример базового файла story для кнопки:

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

// Метаданные компонента
const meta: Meta<typeof Button> = {
  title: "UI/Button",
  component: Button,
  tags: ["autodocs"],  // автоматически генерирует документацию
  parameters: {
    layout: "centered",
  },
  argTypes: {
    variant: {
      control: "select",
      options: ["primary", "secondary", "danger"],
    },
    disabled: {
      control: "boolean",
    },
    onClick: { action: "clicked" },
  },
};

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

// Первая история — основное состояние
export const Primary: Story = {
  args: {
    label: "Нажми меня",
    variant: "primary",
  },
};

// Вторая история — вторичный вариант
export const Secondary: Story = {
  args: {
    label: "Вторичная кнопка",
    variant: "secondary",
  },
};

// История для задисабленного состояния
export const Disabled: Story = {
  args: {
    label: "Недоступно",
    disabled: true,
  },
};

// История с большим текстом
export const LongText: Story = {
  args: {
    label: "Кнопка с очень длинным текстом, который может не уместиться",
  },
};

Понимание структуры Story

Каждый файл stories экспортирует:

  1. Default export — метаданные компонента (meta).
  2. Named exports — отдельные истории (stories).

В meta вы указываете:

  • title — путь в навигационном меню Storybook (например, "UI/Button" создаёт группу "UI" с элементом "Button").
  • component — сам React-компонент.
  • argTypes — описание props и элементы управления для них.
  • parameters — дополнительные настройки (layout, backgrounds и т.д.).

Args и ArgTypes

Args — это props компонента в контексте Storybook. Они позволяют динамически менять props через интерфейс Storybook без изменения кода.

// Компонент Badge
interface BadgeProps {
  count: number;
  color: "red" | "blue" | "green";
  size: "small" | "medium" | "large";
}

const meta: Meta<typeof Badge> = {
  component: Badge,
  argTypes: {
    count: {
      control: { type: "number", min: 0, max: 99 },
      description: "Число для отображения в бейдже",
    },
    color: {
      control: "select",
      options: ["red", "blue", "green"],
      description: "Цвет бейджа",
    },
    size: {
      control: "radio",
      options: ["small", "medium", "large"],
      description: "Размер бейджа",
    },
  },
};

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

export const Default: Story = {
  args: {
    count: 5,
    color: "red",
    size: "medium",
  },
};

Типы controls для ArgTypes

Storybook поддерживает разные типы элементов управления:

Тип Описание Когда использовать
"text" Текстовое поле Для строковых значений
"number" Числовое поле Для числовых значений
"boolean" Переключатель Для булевых значений
"select" Выпадающий список Для фиксированного набора вариантов
"radio" Радиокнопки Для небольшого набора вариантов
"color" Выбор цвета Для цветовых значений
"date" Выбор даты Для значений дат
"range" Ползунок Для числовых диапазонов
"object" JSON-редактор Для объектов
"file" Загрузка файла Для файловых props

Stories для составных компонентов

Иногда компонент требует контекста или обёртки для корректной работы. Используйте декораторы:

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

const meta: Meta<typeof Card> = {
  component: Card,
  decorators: [
    (Story) => (
      <div style={{ maxWidth: "400px", padding: "20px" }}>
        <Story />
      </div>
    ),
  ],
};

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

export const Default: Story = {
  args: {
    title: "Заголовок карточки",
    description: "Описание карточки с некоторым текстом",
    imageUrl: "https://via.placeholder.com/400x200",
  },
};

export const WithLongContent: Story = {
  args: {
    title: "Карточка с длинным содержимым",
    description:
      "Это очень длинное описание, которое проверяет, как карточка обрабатывает большое количество текста. " +
      "Текст продолжается и продолжается, чтобы убедиться в корректном отображении.",
  },
};

Play Functions — интерактивные stories

Play functions позволяют симулировать взаимодействие пользователя прямо в story:

import type { Meta, StoryObj } from "@storybook/react";
import { within, userEvent, expect } from "@storybook/test";
import { LoginForm } from "./LoginForm";

const meta: Meta<typeof LoginForm> = {
  component: LoginForm,
};

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

export const FilledForm: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Находим поля ввода
    const emailInput = canvas.getByLabelText("Email");
    const passwordInput = canvas.getByLabelText("Пароль");
    const submitButton = canvas.getByRole("button", { name: "Войти" });

    // Имитируем ввод данных
    await userEvent.type(emailInput, "user@example.com");
    await userEvent.type(passwordInput, "секретный-пароль");

    // Нажимаем кнопку
    await userEvent.click(submitButton);

    // Проверяем результат
    await expect(
      canvas.getByText("Вход выполнен успешно")
    ).toBeInTheDocument();
  },
};

export const EmptyValidation: Story = {
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);
    const submitButton = canvas.getByRole("button", { name: "Войти" });

    // Пытаемся отправить пустую форму
    await userEvent.click(submitButton);

    // Проверяем появление ошибок валидации
    await expect(canvas.getByText("Email обязателен")).toBeInTheDocument();
    await expect(canvas.getByText("Пароль обязателен")).toBeInTheDocument();
  },
};

Конфигурация и настройка

Файл preview.ts

Файл .storybook/preview.ts применяет глобальные настройки ко всем stories:

// .storybook/preview.ts
import type { Preview } from "@storybook/react";
import "../src/index.css"; // Импорт глобальных стилей

const preview: Preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
    backgrounds: {
      default: "light",
      values: [
        {
          name: "light",
          value: "#ffffff",
        },
        {
          name: "dark",
          value: "#333333",
        },
        {
          name: "gray",
          value: "#f5f5f5",
        },
      ],
    },
  },
  // Глобальные декораторы для всех stories
  decorators: [
    (Story) => (
      <ThemeProvider defaultTheme="light">
        <Story />
      </ThemeProvider>
    ),
  ],
};

export default preview;

Настройка для Tailwind CSS

Если вы используете Tailwind CSS, добавьте импорт стилей в preview:

// .storybook/preview.ts
import type { Preview } from "@storybook/react";
import "../src/app/globals.css"; // или путь к вашему CSS с Tailwind

const preview: Preview = {
  parameters: {
    // ... другие параметры
  },
};

export default preview;

Настройка псевдонимов путей

Если вы используете псевдонимы (@/components), настройте их в main.ts:

// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
import path from "path";

const config: StorybookConfig = {
  stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: ["@storybook/addon-essentials"],
  framework: {
    name: "@storybook/react-vite",
    options: {},
  },
  viteFinalConfig: async (config) => {
    config.resolve = config.resolve || {};
    config.resolve.alias = {
      ...config.resolve.alias,
      "@": path.resolve(__dirname, "../src"),
    };
    return config;
  },
};

export default config;

Для Webpack:

// .storybook/main.ts для Webpack
import type { StorybookConfig } from "@storybook/react-webpack5";
import path from "path";

const config: StorybookConfig = {
  webpackFinal: async (config) => {
    if (config.resolve) {
      config.resolve.alias = {
        ...config.resolve.alias,
        "@": path.resolve(__dirname, "../src"),
      };
    }
    return config;
  },
};

export default config;

Работа с аддонами

Essentials (базовый набор аддонов)

@storybook/addon-essentials включает набор наиболее используемых аддонов:

  • Controls — панель для изменения props в реальном времени.
  • Actions — логирование событий (например, клики на кнопки).
  • Backgrounds — переключение фона для предпросмотра.
  • Viewport — эмуляция разных размеров экранов.
  • Docs — автоматическая генерация документации.
  • Outline — отображение контуров элементов.
  • Measure — измерение размеров элементов.

Аддон Actions

Аддон Actions позволяет отслеживать вызовы функций-обработчиков:

import type { Meta, StoryObj } from "@storybook/react";
import { fn } from "@storybook/test";
import { Button } from "./Button";

const meta: Meta<typeof Button> = {
  component: Button,
  args: {
    // fn() создаёт шпиона (spy), который логирует вызовы
    onClick: fn(),
    onFocus: fn(),
    onBlur: fn(),
  },
};

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

export const Primary: Story = {
  args: {
    label: "Нажми меня",
  },
};

Теперь при клике на кнопку в панели "Actions" будет отображаться лог вызовов.

Аддон Controls

Controls автоматически генерирует UI для изменения props, основываясь на TypeScript-типах или PropTypes:

interface InputProps {
  placeholder?: string;
  disabled?: boolean;
  type?: "text" | "email" | "password" | "number";
  size?: "sm" | "md" | "lg";
  error?: string;
}

Storybook автоматически определит типы и создаст соответствующие элементы управления:

  • placeholder → текстовое поле
  • disabled → переключатель
  • type → выпадающий список с вариантами
  • size → выпадающий список с вариантами
  • error → текстовое поле

Аддон Viewport

Позволяет тестировать компонент на разных размерах экранов. По умолчанию включены стандартные разрешения, но вы можете добавить свои:

// .storybook/preview.ts
const preview: Preview = {
  parameters: {
    viewport: {
      viewports: {
        mobile: {
          name: "Mobile",
          styles: { width: "375px", height: "667px" },
        },
        tablet: {
          name: "Tablet",
          styles: { width: "768px", height: "1024px" },
        },
        desktop: {
          name: "Desktop",
          styles: { width: "1440px", height: "900px" },
        },
      },
      defaultViewport: "desktop",
    },
  },
};

Установка дополнительных аддонов

# Аддон для a11y тестирования
npm install --save-dev @storybook/addon-a11y

# Аддон для тестирования взаимодействий
npm install --save-dev @storybook/addon-interactions

# Аддон для визуального тестирования с Chromatic
npm install --save-dev @chromatic-com/storybook

Добавьте аддоны в конфигурацию:

// .storybook/main.ts
const config: StorybookConfig = {
  addons: [
    "@storybook/addon-essentials",
    "@storybook/addon-a11y",
    "@storybook/addon-interactions",
    "@chromatic-com/storybook",
  ],
};

Автоматическая документация с TypeScript

Теги autodocs

Добавьте тег "autodocs" в meta, и Storybook автоматически создаст страницу документации на основе TypeScript-типов и JSDoc-комментариев:

// Input.tsx
interface InputProps {
  /** Текст-заглушка, отображаемый когда поле пустое */
  placeholder?: string;
  /** Тип поля ввода */
  type?: "text" | "email" | "password" | "number";
  /** Блокирует взаимодействие с полем */
  disabled?: boolean;
  /** Текст ошибки валидации */
  error?: string;
  /** Обработчик изменения значения */
  onChange?: (value: string) => void;
}

// Input.stories.tsx
const meta: Meta<typeof Input> = {
  component: Input,
  tags: ["autodocs"],
};

Storybook автоматически создаст таблицу с описанием всех props, включая:

  • Имя prop.
  • Тип prop (из TypeScript).
  • Значение по умолчанию.
  • Описание из JSDoc-комментария.
  • Элемент управления для изменения значения.

MDX документация

Для более подробной документации используйте MDX-файлы:

{/* Button.mdx */}
import { Canvas, Meta, Controls } from "@storybook/blocks";
import * as ButtonStories from "./Button.stories";

<Meta of={ButtonStories} />

# Button

Компонент кнопки — базовый элемент интерактивного интерфейса.
Используется для запуска действий и навигации.

## Когда использовать

- Для отправки форм
- Для запуска важных действий
- Для навигации, когда нужен визуальный акцент

## Использование

<Canvas of={ButtonStories.Primary} />

## Props

<Controls />

## Варианты

### Основная кнопка

<Canvas of={ButtonStories.Primary} />

### Вторичная кнопка

<Canvas of={ButtonStories.Secondary} />

### Опасное действие

<Canvas of={ButtonStories.Destructive} />

Примеры Stories для распространённых компонентов

Story для компонента форм

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

const meta: Meta<typeof FormField> = {
  title: "Forms/FormField",
  component: FormField,
  tags: ["autodocs"],
  argTypes: {
    label: { control: "text" },
    placeholder: { control: "text" },
    error: { control: "text" },
    disabled: { control: "boolean" },
    required: { control: "boolean" },
  },
};

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

export const Default: Story = {
  args: {
    label: "Email адрес",
    placeholder: "введите@email.com",
    type: "email",
  },
};

export const WithError: Story = {
  args: {
    label: "Email адрес",
    placeholder: "введите@email.com",
    type: "email",
    error: "Введите корректный email адрес",
    value: "некорректный-email",
  },
};

export const Disabled: Story = {
  args: {
    label: "Email адрес",
    placeholder: "введите@email.com",
    disabled: true,
    value: "user@example.com",
  },
};

export const Required: Story = {
  args: {
    label: "Email адрес",
    placeholder: "введите@email.com",
    required: true,
  },
};

Story для компонента с данными

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

const mockUser = {
  id: "1",
  name: "Иван Петров",
  email: "ivan@example.com",
  role: "Разработчик",
  avatarUrl: "https://i.pravatar.cc/150?img=3",
  isOnline: true,
};

const meta: Meta<typeof UserCard> = {
  title: "Cards/UserCard",
  component: UserCard,
  tags: ["autodocs"],
};

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

export const Default: Story = {
  args: {
    user: mockUser,
  },
};

export const Offline: Story = {
  args: {
    user: { ...mockUser, isOnline: false },
  },
};

export const WithoutAvatar: Story = {
  args: {
    user: { ...mockUser, avatarUrl: undefined },
  },
};

export const LongName: Story = {
  args: {
    user: {
      ...mockUser,
      name: "Александр Николаевич Краснопольский",
      role: "Старший инженер по разработке программного обеспечения",
    },
  },
};

Story для компонента с загрузкой

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

const mockData = [
  { id: 1, name: "Продукт А", price: 1200, stock: 45 },
  { id: 2, name: "Продукт Б", price: 850, stock: 12 },
  { id: 3, name: "Продукт В", price: 2300, stock: 0 },
];

const meta: Meta<typeof DataTable> = {
  title: "Data/DataTable",
  component: DataTable,
};

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

export const WithData: Story = {
  args: {
    data: mockData,
    isLoading: false,
    error: null,
  },
};

export const Loading: Story = {
  args: {
    data: [],
    isLoading: true,
    error: null,
  },
};

export const Error: Story = {
  args: {
    data: [],
    isLoading: false,
    error: "Не удалось загрузить данные. Проверьте соединение с интернетом.",
  },
};

export const Empty: Story = {
  args: {
    data: [],
    isLoading: false,
    error: null,
  },
};

Тестирование с помощью Storybook

Storybook как основа для тестов

Stories можно переиспользовать в unit-тестах, что устраняет дублирование:

// Button.test.tsx
import { render, screen } from "@testing-library/react";
import { composeStories } from "@storybook/react";
import * as stories from "./Button.stories";

// composeStories применяет все декораторы и параметры из stories
const { Primary, Disabled } = composeStories(stories);

test("рендерит основную кнопку", () => {
  render(<Primary />);
  expect(screen.getByRole("button")).toBeInTheDocument();
  expect(screen.getByText("Нажми меня")).toBeInTheDocument();
});

test("кнопка задисаблена", () => {
  render(<Disabled />);
  expect(screen.getByRole("button")).toBeDisabled();
});

Визуальное тестирование с Chromatic

Chromatic — облачный сервис для визуального регрессионного тестирования, разработанный командой Storybook:

npm install --save-dev chromatic

Добавьте скрипт в package.json:

{
  "scripts": {
    "chromatic": "chromatic --project-token=<ваш-токен>"
  }
}

Запустите тесты:

npm run chromatic

Chromatic сделает скриншот каждой story и сравнит с предыдущей версией. При обнаружении визуальных изменений потребуется подтверждение.

Интеграция с GitHub Actions

# .github/workflows/chromatic.yml
name: "Chromatic"

on: push

jobs:
  chromatic-deployment:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Install dependencies
        run: npm ci

      - name: Run Chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

Организация и масштабирование

Именование и группировка stories

Используйте title в meta для организации stories в дерево навигации:

// Группировка по категориям
title: "UI/Atoms/Button"       // UI > Atoms > Button
title: "UI/Molecules/Card"     // UI > Molecules > Card
title: "UI/Organisms/Header"   // UI > Organisms > Header
title: "Pages/Home"            // Pages > Home
title: "Features/Auth/Login"   // Features > Auth > Login

Глобальные декораторы и провайдеры

Для компонентов, которым нужны провайдеры контекста, настройте глобальные декораторы:

// .storybook/preview.tsx
import type { Preview } from "@storybook/react";
import { ThemeProvider } from "../src/components/ThemeProvider";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { BrowserRouter } from "react-router-dom";

const queryClient = new QueryClient({
  defaultOptions: {
    queries: { retry: false },
  },
});

const preview: Preview = {
  decorators: [
    (Story) => (
      <BrowserRouter>
        <QueryClientProvider client={queryClient}>
          <ThemeProvider>
            <Story />
          </ThemeProvider>
        </QueryClientProvider>
      </BrowserRouter>
    ),
  ],
};

export default preview;

Мокирование API запросов

Для компонентов, выполняющих API-запросы, используйте Mock Service Worker (MSW):

npm install --save-dev msw msw-storybook-addon
// .storybook/preview.ts
import { initialize, mswLoader } from "msw-storybook-addon";

initialize();

const preview: Preview = {
  loaders: [mswLoader],
};

Теперь в stories можно мокировать API:

// UserProfile.stories.tsx
import { http, HttpResponse } from "msw";

const meta: Meta<typeof UserProfile> = {
  component: UserProfile,
};

export const LoadedProfile: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/user/1", () => {
          return HttpResponse.json({
            id: 1,
            name: "Иван Петров",
            email: "ivan@example.com",
          });
        }),
      ],
    },
  },
};

export const LoadingState: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/user/1", async () => {
          // Имитируем медленный запрос
          await new Promise((resolve) => setTimeout(resolve, 30000));
          return HttpResponse.json({});
        }),
      ],
    },
  },
};

export const ErrorState: Story = {
  parameters: {
    msw: {
      handlers: [
        http.get("/api/user/1", () => {
          return new HttpResponse(null, { status: 500 });
        }),
      ],
    },
  },
};

Публикация документации

Сборка статического сайта

Storybook можно собрать как статический сайт для деплоя:

npm run build-storybook

По умолчанию статические файлы сохраняются в папку storybook-static.

Добавьте скрипт в package.json:

{
  "scripts": {
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  }
}

Деплой на GitHub Pages

# .github/workflows/storybook.yml
name: Deploy Storybook

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"

      - name: Install dependencies
        run: npm ci

      - name: Build Storybook
        run: npm run build-storybook

      - name: Deploy to GitHub Pages
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          publish_dir: ./storybook-static

Распространённые проблемы и решения

Storybook не находит компоненты

Убедитесь, что паттерн в stories в конфигурации охватывает ваши файлы:

// main.ts
const config: StorybookConfig = {
  stories: [
    "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",  // CSF формат
    "../src/**/*.mdx",  // MDX документация
  ],
};

Ошибки с CSS модулями

Для Webpack добавьте обработчик CSS модулей:

// main.ts (Webpack)
const config: StorybookConfig = {
  webpackFinal: async (config) => {
    const cssRule = config.module?.rules?.find(
      (rule) => rule && typeof rule === "object" && String(rule.test).includes("css")
    );
    // Настройте CSS modules здесь
    return config;
  },
};

Проблемы с Next.js

Для проектов Next.js используйте специальный фреймворк:

npm install --save-dev @storybook/nextjs
// main.ts
const config: StorybookConfig = {
  framework: {
    name: "@storybook/nextjs",
    options: {},
  },
};

@storybook/nextjs автоматически обрабатывает:

  • Псевдонимы путей (@/components).
  • Компоненты Image и Link из Next.js.
  • Переменные окружения .env.
  • Конфигурацию Tailwind CSS.

Проблемы с абсолютными импортами

// .storybook/main.ts
import path from "path";

const config: StorybookConfig = {
  viteFinalConfig: async (config) => {
    config.resolve = {
      ...config.resolve,
      alias: {
        "@": path.resolve(__dirname, "../src"),
        "~": path.resolve(__dirname, "../"),
      },
    };
    return config;
  },
};

Лучшие практики

1. Документируйте крайние случаи

Всегда создавайте stories для крайних состояний компонента:

export const WithVeryLongText: Story = { ... };
export const WithEmptyData: Story = { ... };
export const WithMaxValues: Story = { ... };
export const WithMinValues: Story = { ... };
export const ErrorState: Story = { ... };
export const LoadingState: Story = { ... };

2. Используйте реалистичные данные

Вместо заглушек типа "Lorem ipsum" используйте данные, близкие к реальным:

// Плохо
args: {
  user: { name: "Test User", email: "test@test.com" }
}

// Хорошо
args: {
  user: {
    name: "Анна Смирнова",
    email: "anna.smirnova@company.ru",
    role: "Продуктовый менеджер"
  }
}

3. Называйте stories описательно

Имена stories должны описывать состояние или вариант использования:

// Плохо
export const Story1: Story = { ... };
export const Story2: Story = { ... };

// Хорошо
export const Default: Story = { ... };
export const WithIcon: Story = { ... };
export const Disabled: Story = { ... };
export const Loading: Story = { ... };

4. Держите stories маленькими

Каждая story должна демонстрировать одно конкретное состояние:

// Плохо — одна большая story
export const AllVariants: Story = {
  render: () => (
    <>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button disabled>Disabled</Button>
    </>
  ),
};

// Хорошо — отдельные stories
export const Primary: Story = {
  args: { variant: "primary", label: "Primary" },
};

export const Secondary: Story = {
  args: { variant: "secondary", label: "Secondary" },
};

export const Disabled: Story = {
  args: { disabled: true, label: "Disabled" },
};

5. Используйте TypeScript для типобезопасных stories

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

// StoryObj обеспечивает типобезопасность для args
type Story = StoryObj<typeof meta>;

// TypeScript подскажет об ошибках в args
export const Primary: Story = {
  args: {
    // Ошибка TypeScript, если props не существует или имеет неверный тип
    unknownProp: "value",
  },
};

Заключение

Storybook — это мощный инструмент, который меняет подход к разработке UI-компонентов. Он позволяет работать с компонентами в изоляции, создавать живую документацию, тестировать все состояния компонента и организовать эффективную совместную работу команды.

В этой статье мы рассмотрели установку и настройку Storybook для React-проектов, написание stories в формате CSF с TypeScript, работу с аддонами (Controls, Actions, Backgrounds, Viewport), автоматическую генерацию документации, play functions для интерактивного тестирования, мокирование API с MSW, а также интеграцию с тестами и CI/CD.

Начните с базовых stories для ваших ключевых компонентов, постепенно добавляя истории для крайних случаев и сложных состояний. Со временем Storybook станет центральным местом для документации и разработки UI в вашем проекте.

Стрелочка влевоStyled Components — стилизация через JSSnapshots тестированиеСтрелочка вправо

Постройте личный план изучения React до уровня Middle — бесплатно!

React — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиПрофилирование React: как найти и устранить узкие местаЧастичное применение: как создавать компоненты без лишнего кодаИменование компонентов в React: соглашения и лучшие практикиЛенивая загрузка: как ускорить React-приложение в разыHOC в React: мастерство композиции компонентовuseMemo: как спасти производительность от тяжелых вычисленийError Boundaries: создаем надежные React-приложенияКонтролируемые компоненты в React: полный контроль над формамиCompound Components в React: создаем гибкие компоненты с мощным APIДокументирование компонентов в React: Storybook, JSDoc и READMEКомпозиция компонентов в React: строим гибкие интерфейсыКомментирование кода в React: когда и как писать комментарииCode Splitting в React: как уменьшить бандл и ускорить загрузку приложенияАсинхронные компоненты в React: новый стандарт работы с даннымиДоступность (a11y) в React: ARIA, семантика и клавиатурная навигация
Zustand — управление состоянием в ReactZod - валидация с TypeScriptYup - валидация схемXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRTK Query - работа с APIRedux Toolkit - современный ReduxRecoil — библиотека управления состоянием от FacebookВиртуализация списков с react-window: как отображать тысячи элементов без лаговReact Toastify - уведомления в ReactReact Testing LibraryСоздание таблиц в React гайд по react-tableReact Spring - анимацииРабота с формами и селектами в ReactReact Query (TanStack Query) - работа с серверомПлагины в React что это и как их использоватьReact PDF - работа с PDF файламиОбзор популярных библиотек для ReactReact Icons - библиотека иконок для ReactReact Hook Form — валидация форм в ReactReact Dropzone — загрузка файловПодключение Bootstrap к React-приложениюReact Beautiful DnD - перетаскивание элементовАнимация при монтировании компонентов в ReactМокирование APIMobX — реактивное управление состоянием в ReactМикрофронтенды с React (micro-frontends)Загрузка и индикаторыАнимация списков в ReactJotai - атомарное состояниеБесконечная прокруткаFramer Motion - библиотека анимацийEmotion — библиотека CSS-in-JSДинамические стили в ReactE2E тестирование с CypressCSSTransition - переходыCSS-in-JS — плюсы и минусыКонтекст vs Redux — когда что использоватьИспользование Chart.js в ReactAxios с ReactТестирование асинхронных компонентовОбработка ошибок API
useState в React что это и как использоватьuseTransition - плавные переходы между состояниямиuseSyncExternalStore — работа с внешними сторамиuseRef в React — создание ссылок на DOM и значенияuseOptimistic — оптимистичные обновления UIuseLayoutEffect в React — эффект до отрисовкиuseInsertionEffect — внедрение стилей до мутаций DOMuseImperativeHandle в React — настройка ref дочернего компонентаuseId — генерация уникальных идентификаторовuseFormStatus - отслеживание статуса отправки формыuseDeferredValue — отложенное обновление состоянияuseDebugValue — отладка кастомных хуковuseCallback в React — мемоизация функцийuseReducer — альтернатива useState для сложной логикиuseMemo в React: как и когда оптимизировать тяжелые вычисленияuseEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextКак предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиСерверные компоненты React (RSC) — подробный разбор и практикаКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьКак использовать элементы в ReactКак использовать React DOM в проектеЧто такое компоненты в React и как их применятьРабота с children в ReactПорталы в React: рендер компонентов вне иерархии DOMFragment в React: группировка элементов без лишних узлов DOMCSS Modules в ReactConcurrent Mode — конкурентный режим в React
Открыть базу знаний

Лучшие курсы по теме

изображение курса

React и Redux Toolkit

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий