Документирование компонентов в React: Storybook, JSDoc и README

16 июня 2026
Автор

Олег Марков

Документирование компонентов в React

Хорошая документация компонента — это не просто список пропсов. Это живой ресурс, который помогает команде быстро понять, как использовать компонент, какие у него варианты и ограничения. В этой статье рассмотрим несколько подходов: от минималистичного JSDoc до полноценного Storybook.

Почему важно документировать компоненты

Без документации каждый новый разработчик вынужден читать исходный код, чтобы понять, как работает компонент. Для простых компонентов это занимает минуты, для сложных — часы. Умноженное на размер команды и количество компонентов, это существенные потери.

Хорошая документация:

  • Ускоряет онбординг
  • Предотвращает неправильное использование
  • Показывает доступные варианты и граничные случаи
  • Служит живым примером использования

Документирование через TypeScript и JSDoc

Самый доступный уровень документации — типы TypeScript в сочетании с JSDoc-комментариями. Они работают прямо в IDE без дополнительных инструментов.

Документирование пропсов через интерфейс

/**
 * Универсальная кнопка с поддержкой вариантов стиля и состояний загрузки.
 *
 * @example
 * 

tsx

  • // Основное использование
  • *
  • // С вариантом и состоянием загрузки
  • Удалить
  • </Button>
  • */
    interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
    /**
    * Визуальный вариант кнопки.
    * - `primary` — основное действие
    * - `secondary` — второстепенное действие
    * - `danger` — деструктивное действие (удаление, сброс)
    * @default 'primary'
    */
    variant?: 'primary' | 'secondary' | 'danger';
    
    /**
    * Размер кнопки.
    * @default 'md'
    */
    size?: 'sm' | 'md' | 'lg';
    
    /**
    * Показывает спиннер и блокирует кнопку во время асинхронной операции.
    * @default false
    */
    isLoading?: boolean;
    
    /**
    * Иконка слева от текста. Принимает React-элемент (например, из lucide-react).
    * @example `leftIcon={<PlusIcon size={16} />}`
    */
    leftIcon?: React.ReactNode;
    }
    
    

function Button({ variant = 'primary', size = 'md', isLoading = false, leftIcon, children, disabled, ...props }: ButtonProps) { return (

<button
  {...props}
  disabled={disabled || isLoading}
  className={cn(buttonVariants({ variant, size }))}
>
  {isLoading ? <Spinner size="sm" /> : leftIcon}
  {children}
</button>

); }


### Документирование хуков

tsx /**

  • Управляет состоянием асинхронного запроса. *
  • @template T Тип возвращаемых данных
  • @param asyncFn - Асинхронная функция для выполнения
  • @returns Объект с данными, состоянием загрузки, ошибкой и функцией execute *
  • @example
  • ```tsx
  • function UserProfile({ userId }: { userId: string }) {
  • const { data: user, isLoading, error, execute } = useAsync(
  • () => fetchUser(userId)
  • ); *
  • useEffect(() => { execute(); }, [userId]); *
  • if (isLoading) return ;
  • if (error) return ;
  • if (!user) return null; *
  • return
    {user.name}
    ;
  • }
  • */
    function useAsync<T>(asyncFn: () => Promise<T>) {
    const [data, setData] = useState<T | null>(null);
    const [isLoading, setIsLoading] = useState(false);
    const [error, setError] = useState<Error | null>(null);
    
    const execute = async () => {
     setIsLoading(true);
     setError(null);
     try {
       const result = await asyncFn();
       setData(result);
     } catch (err) {
       setError(err instanceof Error ? err : new Error('Unknown error'));
     } finally {
       setIsLoading(false);
     }
    };
    
    return { data, isLoading, error, execute };
    }
    

Storybook: живая документация

Storybook — стандарт де-факто для документирования UI-компонентов. Он позволяет разрабатывать и демонстрировать компоненты в изоляции.

Установка Storybook

npx storybook@latest init

Написание историй (stories)

Каждый файл .stories.tsx описывает варианты использования компонента:

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
import { PlusIcon, TrashIcon } from 'lucide-react';

// Метаданные компонента
const meta: Meta<typeof Button> = {
  title: 'UI/Button',
  component: Button,
  // Автоматически генерирует таблицу с пропсами из TypeScript-типов
  tags: ['autodocs'],
  argTypes: {
    variant: {
      description: 'Визуальный стиль кнопки',
      control: { type: 'select' },
    },
    isLoading: {
      description: 'Состояние загрузки',
    },
  },
};

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

// Базовый вариант
export const Default: Story = {
  args: {
    children: 'Нажми меня',
  },
};

// Все варианты
export const Primary: Story = {
  args: {
    variant: 'primary',
    children: 'Основное действие',
  },
};

export const Secondary: Story = {
  args: {
    variant: 'secondary',
    children: 'Второстепенное',
  },
};

export const Danger: Story = {
  args: {
    variant: 'danger',
    children: 'Удалить',
    leftIcon: <TrashIcon size={16} />,
  },
};

// Состояние загрузки
export const Loading: Story = {
  args: {
    isLoading: true,
    children: 'Сохранение...',
  },
};

// Задокументированный сценарий использования
export const WithIcon: Story = {
  args: {
    leftIcon: <PlusIcon size={16} />,
    children: 'Добавить элемент',
  },
};

// История для демонстрации всех размеров
export const AllSizes: Story = {
  render: () => (
    <div style={{ display: 'flex', gap: 8, alignItems: 'center' }}>
      <Button size="sm">Маленький</Button>
      <Button size="md">Средний</Button>
      <Button size="lg">Большой</Button>
    </div>
  ),
};

Документирование сложных взаимодействий

Для компонентов с состоянием используйте play функцию:

import { within, userEvent } from '@storybook/testing-library';
import { expect } from '@storybook/jest';

export const FormSubmission: Story = {
  render: () => (
    <LoginForm onSuccess={() => {}} />
  ),
  play: async ({ canvasElement }) => {
    const canvas = within(canvasElement);

    // Симулируем заполнение формы
    await userEvent.type(
      canvas.getByLabelText('Email'),
      'user@example.com'
    );
    await userEvent.type(
      canvas.getByLabelText('Пароль'),
      'password123'
    );

    // Проверяем что кнопка стала активной
    const submitButton = canvas.getByRole('button', { name: 'Войти' });
    await expect(submitButton).not.toBeDisabled();
  },
};

README для компонентов

Для сложных или переиспользуемых компонентов создавайте README.md рядом с компонентом:

components/
  DataTable/
    index.tsx
    DataTable.tsx
    useDataTable.ts
    README.md

Структура README для компонента:

# DataTable

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

## Быстрый старт

\

tsx import { DataTable } from '@/components/DataTable';

const columns = [ { key: 'name', header: 'Имя', sortable: true }, { key: 'email', header: 'Email' }, { key: 'role', header: 'Роль', filterable: true }, ];

function UsersPage() { const { data } = useUsers();

return (

<DataTable
  data={data}
  columns={columns}
  pageSize={20}
/>

); } ```

Пропсы

Проп Тип По умолчанию Описание
data T[] Массив данных для отображения
columns Column<T>[] Конфигурация колонок
pageSize number 10 Строк на странице
onRowClick (row: T) => void Обработчик клика по строке

Рецепты

Кастомный рендер ячейки

```tsx const columns = [ {

key: 'status',
header: 'Статус',
render: (value: string) => (
  <StatusBadge status={value} />
),

}, ]; ```

Ограничения

  • Виртуализация не поддерживается — не используйте для списков > 1000 строк
  • Серверная сортировка настраивается через проп onSort ```

Документирование через PropTypes (для JS-проектов)

Если проект на JavaScript без TypeScript, используйте PropTypes:

import PropTypes from 'prop-types';

function UserAvatar({ user, size, showStatus }) {
  // ...
}

UserAvatar.propTypes = {
  /** Объект пользователя */
  user: PropTypes.shape({
    id: PropTypes.string.isRequired,
    name: PropTypes.string.isRequired,
    avatarUrl: PropTypes.string,
  }).isRequired,
  /** Размер аватара в пикселях */
  size: PropTypes.oneOf([24, 32, 40, 48, 64]),
  /** Показывать индикатор статуса онлайн */
  showStatus: PropTypes.bool,
};

UserAvatar.defaultProps = {
  size: 40,
  showStatus: false,
};

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

/**
 * Контекст темы приложения.
 *
 * Предоставляет текущую тему и функцию для её переключения.
 * Должен оборачивать всё приложение или секцию, которой нужен доступ к теме.
 *
 * @example
 * 

tsx

  • // В root layout
  • </ThemeProvider> *
  • // В компоненте
  • const { theme, toggleTheme } = useTheme();
  • ``` */ const ThemeContext = createContext(undefined);

/**

  • Хук для доступа к контексту темы.
  • Выбрасывает ошибку если используется вне ThemeProvider. */ function useTheme(): ThemeContextValue { const context = useContext(ThemeContext); if (!context) { throw new Error('useTheme должен использоваться внутри ThemeProvider'); } return context; } ```

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

Для TypeScript-проектов можно использовать react-docgen-typescript или typedoc:

# Установка typedoc
npm install --save-dev typedoc typedoc-plugin-markdown

# Генерация из JSDoc-комментариев
npx typedoc --plugin typedoc-plugin-markdown --out docs src/components

Это создаст Markdown-документацию из JSDoc-аннотаций в TypeScript-файлах.

Итоги

Уровни документации по возрастанию затрат:

  1. TypeScript типы — минимум, всегда делайте это
  2. JSDoc-комментарии к пропсам — добавляйте к переиспользуемым компонентам
  3. Примеры использования в JSDoc — для нетривиальных компонентов
  4. README.md — для сложных компонентов в shared/UI-библиотеке
  5. Storybook — для команд с дизайн-системой или UI-библиотекой

Начните с хорошо типизированных интерфейсов и JSDoc. Storybook добавляйте тогда, когда команда растёт и необходимо показывать состояния компонентов нон-разработчикам (дизайнерам, QA).

Стрелочка влевоCompound Components в React: создаем гибкие компоненты с мощным APIКомпозиция компонентов в React: строим гибкие интерфейсыСтрелочка вправо

Постройте личный план изучения 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 ₽
Подробнее

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