Создание компонента component - практическое руководство

05 марта 2026
Автор

Олег Марков

Введение

Создание компонента (component) — одна из базовых задач во фронтенд‑разработке. Почти любой современный интерфейс строится из независимых, переиспользуемых блоков, которые можно свободно комбинировать: кнопки, модальные окна, формы, карточки, таблицы и так далее.

Когда вы только начинаете, может казаться, что компонент — это просто кусок кода с версткой. Но как только приложение начинает расти, становится важным то, как именно вы проектируете компоненты: какие входные параметры они принимают, как управляют состоянием, как общаются с другими частями системы.

В этой статье я покажу вам на конкретных примерах, как:

  • спроектировать компонент до того, как вы напишете код
  • реализовать его на примере React (как самого популярного подхода к компонентам)
  • разделить логику и представление
  • настроить управление состоянием
  • обеспечить переиспользуемость и предсказуемое поведение
  • протестировать и документировать компонент

Все примеры будут на JavaScript с использованием React, но сами принципы одинаково полезны и для Vue, Svelte, Angular или Web Components. Смотрите, я буду пояснять не только «как сделать», но и «почему именно так».


Что такое компонент и из чего он состоит

Концепция компонента

Компонент — это самодостаточная часть интерфейса, у которой есть:

  1. Явное назначение (что он делает)
  2. Граница ответственности (что он не делает)
  3. Публичный интерфейс (как его использовать)
  4. Внутреннее устройство (как он это делает)

Давайте сформулируем это более структурно.

Основные элементы компонента

У любого компонента есть несколько ключевых частей:

  • Входные данные (props, параметры)
  • Внутреннее состояние (state, реактивные переменные)
  • Шаблон / разметка (view, JSX, template)
  • Логика (обработчики событий, вычисления)
  • Выходные события (колбэки, кастомные события)
  • Статическое поведение (стили, размеры, визуальное оформление)

Как видите, у компонента есть и «что снаружи видно», и «что внутри происходит». Очень важно не смешивать эти уровни без необходимости.


Планирование компонента перед реализацией

Прежде чем мы начнем писать код, имеет смысл коротко «спроектировать» компонент. Пусть это будет простой, но показательный пример — кнопка Button.

Шаг 1. Определяем назначение

Допустим, нам нужна кнопка:

  • которая вызывает действие по клику
  • может быть в нескольких визуальных вариантах (primary, secondary, danger)
  • может быть заблокирована (disabled)
  • может показывать индикатор загрузки (loading)

Слишком простые решения часто заканчиваются тем, что через пару месяцев компонент перестает устраивать, и вы начинаете «наращивать костыли». Лучше сразу выделить ключевые сценарии.

Шаг 2. Формируем публичный интерфейс

Здесь я размещаю список возможных входных параметров:

  • variant — тип кнопки (primary, secondary, danger)
  • disabled — флаг отключения
  • loading — флаг загрузки
  • onClick — обработчик клика
  • children или label — текст содержимого

То есть внешний интерфейс примерно такой:

Button(props):

  • variant: "primary" | "secondary" | "danger"
  • disabled: boolean
  • loading: boolean
  • onClick: function
  • children: ReactNode

Сейчас нам важно именно это — как компонент будет выглядеть для потребителя.

Шаг 3. Решаем, нужно ли внутреннее состояние

Для Button внутреннее состояние не нужно: все данные приходят снаружи. Это управляемый компонент, поведение которого полностью задается через props.

Хорошее правило:
Если можно обойтись без внутреннего состояния — обходитесь. Это делает компонент проще и предсказуемее.


Создание простого функционального компонента (React пример)

Теперь вы увидите, как это выглядит в коде. Начнем с минимальной версии кнопки.

Базовый каркас компонента

// Импортируем React, чтобы использовать JSX и функциональные компоненты
import React from "react";

// Определяем компонент Button как функцию
function Button(props) {
  // Деструктурируем нужные пропсы для удобства
  const { variant = "primary", disabled = false, onClick, children } = props;

  // Формируем CSS класс на основе варианта кнопки
  const className = `btn btn-${variant}`;

  return (
    // Передаем вычисленный класс и флаг disabled в HTML кнопку
    <button
      type="button"
      className={className}
      disabled={disabled}
      onClick={onClick} // Вызываем обработчик, который передал родитель
    >
      {children /* Отображаем содержимое, переданное в компонент */}
    </button>
  );
}

// Экспортируем компонент, чтобы можно было использовать его в других файлах
export default Button;

Обратите внимание: здесь нет внутреннего состояния. Вся «умность» снаружи. Это очень хороший старт для большинства UI-компонентов.

Использование компонента Button

Давайте посмотрим, что происходит в следующем примере:

// Импортируем компонент Button
import Button from "./Button";

function App() {
  // Обработчик клика по кнопке
  const handleSave = () => {
    // Здесь может быть логика сохранения данных
    console.log("Сохранение...");
  };

  return (
    <div>
      {/* Кнопка с основным стилем */}
      <Button variant="primary" onClick={handleSave}>
        Сохранить
      </Button>

      {/* Отключенная кнопка без обработчика */}
      <Button variant="secondary" disabled>
        Недоступно
      </Button>
    </div>
  );
}

export default App;

Здесь мы уже видим базовый принцип: компонент — это «чёрный ящик» с понятным набором входных параметров. Вы не думаете о том, как он устроен внутри, только о том, какие props передать.


Добавление гибкости и расширяемости компонента

Очень быстро появляется необходимость расширить поведение компонента. Давайте разберемся на примере с состоянием загрузки.

Поддержка состояния loading

Смотрите, я покажу вам, как это работает, если мы добавим состояние загрузки, контролируемое снаружи:

import React from "react";

function Button(props) {
  const {
    variant = "primary",
    disabled = false,
    loading = false, // Новый пропс - состояние загрузки
    onClick,
    children,
    ...rest // Остальные пропсы пробрасываем "как есть"
  } = props;

  const className = `btn btn-${variant}`;

  // Оборачиваем исходный обработчик клика
  const handleClick = (event) => {
    // Если кнопка в состоянии disabled или loading - пропускаем клик
    if (disabled || loading) {
      return;
    }

    // Если обработчик передан - вызываем его
    if (typeof onClick === "function") {
      onClick(event);
    }
  };

  return (
    <button
      type="button"
      className={className}
      disabled={disabled || loading} // Блокируем кнопку при загрузке
      onClick={handleClick}
      {...rest} // Дополнительные HTML атрибуты типа aria-label и так далее
    >
      {loading ? (
        // Показываем индикатор загрузки, если loading === true
        <span className="spinner">
          {/* Здесь может быть иконка или SVG */}
          Загрузка...
        </span>
      ) : (
        // В обычном состоянии показываем переданное содержимое
        children
      )}
    </button>
  );
}

export default Button;

Мы добавили:

  • новый проп loading
  • защиту от кликов в состоянии loading
  • условный рендеринг содержимого

Так компонент стал чуть умнее, но остался управляемым — состояние по‑прежнему задается родителем.

Пример использования с внешним состоянием

import React, { useState } from "react";
import Button from "./Button";

function SaveForm() {
  // Внешнее состояние загрузки, управляется родителем
  const [isSaving, setIsSaving] = useState(false);

  const handleSave = async () => {
    // Устанавливаем состояние загрузки в true
    setIsSaving(true);

    try {
      // Эмулируем асинхронный запрос
      await new Promise((resolve) => setTimeout(resolve, 2000));
      console.log("Данные сохранены");
    } finally {
      // В любом случае возвращаем состояние загрузки в false
      setIsSaving(false);
    }
  };

  return (
    <div>
      {/* Передаем состояние загрузки в кнопку */}
      <Button variant="primary" loading={isSaving} onClick={handleSave}>
        Сохранить
      </Button>
    </div>
  );
}

export default SaveForm;

Такой подход важен: компонент остается «глупым» в хорошем смысле — он не знает, откуда берется loading. Ему просто говорят: «Сейчас мы загружаем данные, веди себя вот так».


Управляемые и неуправляемые компоненты

Теперь давайте перейдем к следующему шагу и рассмотрим более сложный тип компонента — например, текстовое поле ввода Input. Здесь часто возникает вопрос: где хранить введенное пользователем значение?

Управляемый компонент

Управляемый компонент получает значение и колбэк для изменения этого значения. Он ничего не хранит внутри.

function Input({ value, onChange, ...rest }) {
  // Обработчик изменения значения поля ввода
  const handleChange = (event) => {
    // Если обработчик передан - вызываем его с новым значением
    if (typeof onChange === "function") {
      onChange(event.target.value);
    }
  };

  return (
    <input
      {...rest} // Пробрасываем остальные HTML атрибуты
      value={value} // Значение приходит снаружи
      onChange={handleChange}
    />
  );
}

export default Input;

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

import React, { useState } from "react";
import Input from "./Input";

function LoginForm() {
  // Состояние для логина
  const [login, setLogin] = useState("");

  return (
    <div>
      {/* Управляемое поле ввода */}
      <Input
        type="text"
        placeholder="Логин"
        value={login} // Привязываем значение к состоянию
        onChange={setLogin} // Передаем сеттер как обработчик изменений
      />
    </div>
  );
}

export default LoginForm;

Обратите внимание: компонент Input не знает, где и как хранится значение. Он только «транслирует» изменения наружу.

Неуправляемый компонент

Иногда удобнее, чтобы компонент сам хранил свое состояние. Давайте посмотрим пример:

import React, { useState } from "react";

function InputUncontrolled({ defaultValue = "", onChange, ...rest }) {
  // Локальное состояние значения поля
  const [value, setValue] = useState(defaultValue);

  const handleChange = (event) => {
    const newValue = event.target.value;

    // Обновляем локальное состояние
    setValue(newValue);

    // Если передан внешний обработчик, уведомляем о смене значения
    if (typeof onChange === "function") {
      onChange(newValue);
    }
  };

  return (
    <input
      {...rest}
      value={value} // Используем внутреннее состояние
      onChange={handleChange}
    />
  );
}

export default InputUncontrolled;

Здесь компонент сам контролирует свое значение, но при этом может уведомлять родителя. Такой подход полезен, когда:

  • родителю не нужно постоянно знать текущее значение
  • вы хотите упростить использование компонента «из коробки»

Смешанный подход

Во многих реальных библиотеках используется паттерн: если передан проп value, компонент работает как управляемый; если нет — как неуправляемый.

import React, { useState } from "react";

function SmartInput({ value, defaultValue = "", onChange, ...rest }) {
  // Локальное состояние используется, только если value не передан
  const [innerValue, setInnerValue] = useState(defaultValue);

  // Вычисляем, какое значение сейчас отображать
  const isControlled = value !== undefined;
  const currentValue = isControlled ? value : innerValue;

  const handleChange = (event) => {
    const newValue = event.target.value;

    // Если компонент неуправляемый - обновляем локальное состояние
    if (!isControlled) {
      setInnerValue(newValue);
    }

    // В любом случае вызываем внешний обработчик, если он есть
    if (typeof onChange === "function") {
      onChange(newValue);
    }
  };

  return (
    <input
      {...rest}
      value={currentValue}
      onChange={handleChange}
    />
  );
}

export default SmartInput;

Такой паттерн делает компонент гибким, но требует аккуратной реализации, чтобы не было неожиданных переходов между режимами.


Разделение логики и представления: контейнер и презентационный компонент

Когда компонент начинает «обрастать» логикой (запросы к API, валидация, сложные вычисления), полезно разделять:

  • презентационный компонент — отвечает за верстку и внешний вид
  • контейнерный компонент — отвечает за бизнес‑логику и данные

Давайте разберемся на примере компонента UserCard, который показывает информацию о пользователе.

Презентационный компонент UserCardView

// Компонент отвечает только за отображение данных пользователя
function UserCardView({ user, loading, error, onReload }) {
  // Если идет загрузка - показываем скелетон или текст
  if (loading) {
    return <div className="user-card user-card--loading">Загрузка пользователя...</div>;
  }

  // Если произошла ошибка - показываем сообщение и кнопку перезагрузки
  if (error) {
    return (
      <div className="user-card user-card--error">
        <p>Не удалось загрузить данные пользователя</p>
        <button type="button" onClick={onReload}>
          Повторить
        </button>
      </div>
    );
  }

  // Если данных нет - можно показать заглушку
  if (!user) {
    return <div className="user-card user-card--empty">Нет данных пользователя</div>;
  }

  // Основной вариант отображения
  return (
    <div className="user-card">
      <h2 className="user-card__name">{user.name}</h2>
      <p className="user-card__email">{user.email}</p>
      <p className="user-card__role">{user.role}</p>
    </div>
  );
}

export default UserCardView;

Этот компонент не знает:

  • откуда берутся данные user
  • как устроен запрос к серверу
  • как обрабатывается ошибка на уровне приложения

Его задача одна — красиво и корректно отобразить то, что ему передали.

Контейнерный компонент UserCard

Теперь давайте посмотрим на контейнер:

import React, { useEffect, useState } from "react";
import UserCardView from "./UserCardView";

// Функция-обертка для запроса пользователя
async function fetchUser(userId) {
  // Здесь вы можете использовать fetch, axios или любую другую библиотеку
  const response = await fetch(`/api/users/${userId}`);

  // Проверяем статус ответа
  if (!response.ok) {
    // Если ответ не 2xx - выбрасываем ошибку
    throw new Error("Ошибка загрузки пользователя");
  }

  // Парсим JSON и возвращаем объект пользователя
  return response.json();
}

function UserCard({ userId }) {
  // Локальное состояние для данных пользователя
  const [user, setUser] = useState(null);
  // Флаг загрузки
  const [loading, setLoading] = useState(false);
  // Объект ошибки
  const [error, setError] = useState(null);

  const loadUser = async () => {
    setLoading(true);  // Включаем индикатор загрузки
    setError(null);    // Сбрасываем предыдущую ошибку

    try {
      const data = await fetchUser(userId); // Загружаем данные
      setUser(data);                        // Сохраняем пользователя
    } catch (err) {
      setError(err);                        // Сохраняем ошибку
    } finally {
      setLoading(false);                    // В любом случае снимаем флаг загрузки
    }
  };

  // Загружаем пользователя при первом рендере и при изменении userId
  useEffect(() => {
    loadUser();
  }, [userId]);

  return (
    <UserCardView
      user={user}
      loading={loading}
      error={error}
      onReload={loadUser} // Передаем колбэк для перезагрузки
    />
  );
}

export default UserCard;

Так мы отделили:

  • бизнес‑логику (контейнер)
  • интерфейс (view)

Этот паттерн особенно полезен, когда вы создаете компоненты, которые могут переиспользоваться в разных контекстах (например, карточка пользователя в разных разделах приложения).


Переиспользуемость и API компонента

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

Явные и предсказуемые пропсы

Старайтесь:

  • давать понятные, самодокументирующиеся имена пропсам
  • не передавать в пропс «много всего сразу»
  • избегать «магических» значений (строк, чисел без описания)

Например, вместо:

// Неудачный пример
<Card mode="1" />

Лучше:

// Более понятный пример
<Card variant="outlined" />

Хорошей практикой будет использовать перечисления (enum) или PropTypes (в React), чтобы явно задать возможные значения.

Проброс дополнительных атрибутов

Очень полезный прием — передавать «остальные» пропсы дальше к корневому элементу. Вы уже видели его ранее:

function Button({ variant = "primary", ...rest }) {
  const className = `btn btn-${variant}`;

  return (
    <button
      type="button"
      className={className}
      {...rest} // Позволяет передать onMouseEnter, data-атрибуты и так далее
    />
  );
}

Так компонент остается гибким: вы можете добавлять поведение «точечно», не изменяя сам компонент.

Слоты и переопределение частей компонента

Иногда вам нужно не просто контролировать текст, а полностью менять, что будет внутри. В React это делается через children и дополнительные «слоты» (рендер‑пропсы).

Пример: компонент Modal, который принимает заголовок, тело и футер.

function Modal({ isOpen, title, children, footer }) {
  if (!isOpen) {
    // Если модальное окно закрыто - ничего не рендерим
    return null;
  }

  return (
    <div className="modal">
      <div className="modal__overlay" />

      <div className="modal__content">
        <div className="modal__header">
          <h2 className="modal__title">{title}</h2>
        </div>

        <div className="modal__body">
          {children /* Основное содержимое модального окна */}
        </div>

        {footer && (
          <div className="modal__footer">
            {footer /* Кастомный футер, если передан */}
          </div>
        )}
      </div>
    </div>
  );
}

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

<Modal
  isOpen={isOpen}
  title="Удаление записи"
  footer={
    <div>
      <Button variant="secondary" onClick={handleCancel}>
        Отмена
      </Button>
      <Button variant="danger" onClick={handleConfirm}>
        Удалить
      </Button>
    </div>
  }
>
  <p>Вы уверены, что хотите удалить эту запись</p>
</Modal>

Такой подход делает компонент модального окна универсальным и расширяемым.


Работа со стилями в компонентах

Стили — важная часть компонента. Давайте коротко разберем основные подходы.

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

Самый простой вариант — обычные CSS классы:

function Badge({ color = "gray", children }) {
  const className = `badge badge-${color}`;

  return <span className={className}>{children}</span>;
}

CSS:

/* Базовый стиль бейджа */
.badge {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 4px;
  font-size: 12px;
}

/* Вариант цвета - серый */
.badge-gray {
  background-color: #f0f0f0;
  color: #333;
}

/* Вариант цвета - зеленый */
.badge-green {
  background-color: #d1fae5;
  color: #065f46;
}

Плюсы:

  • понятно новичкам
  • легко переопределять стили
  • не привязаны к конкретной технологии

Минусы:

  • глобальность (конфликты имен)
  • сложнее поддерживать большие системы

CSS Modules / CSS-in-JS / Tailwind

В реальных проектах используются более продвинутые техники, но принцип для компонента один:

  • стили должны быть привязаны к компоненту или к дизайн‑системе
  • поведение компонента должно быть предсказуемым, независимо от места использования

Главное — не смешивать ответствености компонента с «магическими» стилями, которые приходят извне и полностью меняют поведение.


Обработка событий и использование колбэков

Компонент редко существует сам по себе — он должен «общаться» с внешним миром. Для этого используются колбэки.

Базовый паттерн колбэка

function Toggle({ checked, onChange }) {
  const handleClick = () => {
    // Инвертируем текущее значение и сообщаем наружу
    if (typeof onChange === "function") {
      onChange(!checked);
    }
  };

  return (
    <button type="button" onClick={handleClick}>
      {/* Показываем состояние в UI */}
      {checked ? "Включено" : "Выключено"}
    </button>
  );
}

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

import React, { useState } from "react";
import Toggle from "./Toggle";

function Settings() {
  const [notificationsEnabled, setNotificationsEnabled] = useState(false);

  return (
    <div>
      <Toggle
        checked={notificationsEnabled}
        onChange={setNotificationsEnabled}
      />
    </div>
  );
}

Компонент Toggle не знает, что он включает или выключает — он просто сообщает о смене значения. Это безопасный и гибкий паттерн.

События с дополнительными данными

Иногда нужно передавать не просто новое значение, а дополнительный контекст. Покажу вам, как это реализовано на практике.

function ListItem({ id, label, onSelect }) {
  const handleClick = () => {
    if (typeof onSelect === "function") {
      // Передаем ID и сам объект события (по желанию)
      onSelect({ id });
    }
  };

  return (
    <li onClick={handleClick}>
      {label}
    </li>
  );
}

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

function List({ items, onSelectItem }) {
  return (
    <ul>
      {items.map((item) => (
        <ListItem
          key={item.id}
          id={item.id}
          label={item.label}
          onSelect={onSelectItem}
        />
      ))}
    </ul>
  );
}

// В родителе
<List
  items={items}
  onSelectItem={({ id }) => {
    console.log("Выбран элемент", id);
  }}
/>

Очень важно не «привязывать» компонент к конкретной бизнес‑логике. Он не должен сам, например, ходить в API при клике (если это не строго его предназначение).


Тестирование компонента

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

Что обычно тестируют

  • корректный рендер (что показывается по умолчанию)
  • рендер в разных состояниях (variant, disabled, loading)
  • реакция на события (клик, ввод)
  • корректную работу колбэков
  • отсутствие побочных эффектов

Пример теста (React Testing Library)

Давайте посмотрим, как это выглядит на примере кнопки.

// Импортируем необходимые функции для тестирования
import { render, screen, fireEvent } from "@testing-library/react";
import Button from "./Button";

test("отображает текст кнопки", () => {
  // Рендерим компонент Button с текстом "Сохранить"
  render(<Button>Сохранить</Button>);

  // Проверяем, что текст появился в документе
  const buttonElement = screen.getByText("Сохранить");
  expect(buttonElement).toBeInTheDocument();
});

test("вызывает обработчик клика", () => {
  // Создаем мок-функцию для отслеживания вызова
  const handleClick = jest.fn();

  // Рендерим компонент с обработчиком
  render(<Button onClick={handleClick}>Нажать</Button>);

  // Ищем кнопку по тексту
  const buttonElement = screen.getByText("Нажать");

  // Эмулируем клик по кнопке
  fireEvent.click(buttonElement);

  // Проверяем, что обработчик был вызван ровно один раз
  expect(handleClick).toHaveBeenCalledTimes(1);
});

test("не вызывает обработчик клика, если disabled", () => {
  const handleClick = jest.fn();

  // Рендерим отключенную кнопку
  render(
    <Button disabled onClick={handleClick}>
      Нажать
    </Button>
  );

  const buttonElement = screen.getByText("Нажать");

  // Эмулируем клик
  fireEvent.click(buttonElement);

  // Убеждаемся, что обработчик не был вызван
  expect(handleClick).not.toHaveBeenCalled();
});

Комментарии в коде помогают лучше понять, что именно мы проверяем.


Документация и примеры использования компонента

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

Что лучше всего описывать

  • Краткое назначение компонента
  • Обязательные и необязательные пропсы
  • Типы пропсов и значение по умолчанию
  • Примеры использования в типичных сценариях
  • Рекомендации и ограничения

Пример короткого описания для Button:

Button

Компонент кнопки, который используется для инициирования действий пользователем.

Props

  • variant — визуальный стиль кнопки (primary, secondary, danger), по умолчанию primary
  • disabled — отключает кнопку, по умолчанию false
  • loading — включает состояние загрузки, блокирует клики, по умолчанию false
  • onClick — обработчик клика (функция)
  • children — содержимое кнопки (обычно текст)

Пример

<Button variant="primary" onClick={handleSubmit}>
  Отправить
</Button>

Хорошей практикой является использование Storybook или аналогичных инструментов для интерактивной документации компонентов.


Заключение

Создание компонента — это не только вопрос синтаксиса фреймворка. Важнее сама архитектура и дизайн API:

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

Если придерживаться этих принципов, ваши компоненты будут:

  • предсказуемыми
  • легко переиспользуемыми
  • удобными для других разработчиков
  • менее хрупкими по мере роста кода

Дальше вы можете развивать эту базу: подключать типизацию (TypeScript), строить дизайн‑систему, выделять компоненты в отдельные библиотеки. Но фундамент всегда один и тот же — хороший дизайн компонента как самостоятельной единицы интерфейса.


Частозадаваемые технические вопросы по теме и ответы

1. Как передать ссылку (ref) во внутренний DOM элемент компонента

Используйте forwardRef в React:

import React, { forwardRef } from "react";

const Input = forwardRef(function Input(props, ref) {
  return (
    <input
      {...props}
      ref={ref} // Пробрасываем ref во внутренний input
    />
  );
});

export default Input;

Теперь родитель может получить доступ к реальному DOM:

const inputRef = useRef(null);
<Input ref={inputRef} />;

2. Как оптимизировать рендер компонента при частых обновлениях

Используйте React.memo и мемоизацию обработчиков:

const Button = React.memo(function Button(props) {
  // Компонент будет перерисовываться только если изменились его пропсы
  return <button {...props} />;
});

В родителе оборачивайте колбэки в useCallback, чтобы не создавать новую функцию при каждом рендере.

3. Как правильно типизировать компонент с пропсом children в TypeScript

Опишите интерфейс пропсов и используйте React.FC или явный тип:

import { ReactNode } from "react";

interface ButtonProps {
  children: ReactNode;
  onClick?: () => void;
}

function Button({ children, onClick }: ButtonProps) {
  return <button onClick={onClick}>{children}</button>;
}

Так вы получите подсказки типов при использовании компонента.

4. Как сделать компонент доступным для скринридеров

Добавьте aria‑атрибуты и корректные роли:

function IconButton({ label, ...rest }) {
  return (
    <button
      type="button"
      aria-label={label} // Текст для скринридера
      {...rest}
    >
      {/* Здесь может быть иконка без текста */}
      <Icon />
    </button>
  );
}

Для сложных компонентов, например модалок, используйте role="dialog", aria-modal="true", связывайте заголовок через aria-labelledby.

5. Как переопределить стили компонента без его изменения

Добавьте проп className и применяйте его к корневому элементу:

function Card({ className = "", children }) {
  return (
    <div className={`card ${className}`}>
      {children}
    </div>
  );
}

Теперь родитель может передать свои классы:

<Card className="card--highlighted">Текст</Card>

При необходимости можно также добавить проп style для инлайн‑стилей.

Стрелочка влевоДинамические компоненты - dynamic-componentsАсинхронные компоненты async-components - практическое руководствоСтрелочка вправо

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

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

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

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

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Vuex - полное руководство по управлению состоянием во Vue приложенияхРеактивные ссылки ref - полный разбор для разработчиковРеактивные объекты reactive-objects - подробное руководство с примерамиРеактивные переменные - концепция reactive и практические примерыМеханизм Provide Inject - как он работает и когда применятьPinia современный менеджер состояния для VueЛокальное состояние local state в веб разработкеГлобальное состояние в приложениях - global state
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
Функция append в Go GolangОтображение компонента mounted - практическое руководствоХуки жизненного цикла компонентов - полное руководство для разработчиковУничтожение компонента destroyed - как правильно очищать ресурсы и подпискиИнициализация данных в состоянии created - как и когда подготавливать данные в приложенииОбновление компонента beforeUpdate во VueМонтирование компонента - хук beforeMount в VueРазрушение компонента во Vue - beforeDestroy и beforeUnmountСоздание экземпляра beforeCreate - полный разбор жизненного цикла
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Слоты компонента - концепция и практическое использованиеРегистрация компонентов component-registration в приложениях с внедрением зависимостейProps компонента в React - полный разбор с примерамиФункциональные компоненты в React - функциональный подход к построению интерфейсовСобытия компонента - events в современных интерфейсахДинамические компоненты - dynamic-componentsСоздание компонента component - практическое руководствоАсинхронные компоненты async-components - практическое руководство
Наблюдатели watchers - от паттерна до практических реализацийУправление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsШаблоны Vue templates - практическое руководство для разработчиковИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueСтруктура и особенности Single File Components SFC в VueРабота со стилями и стилизацией в VueРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueРабота со SCSS в проектах на Vue для стилизацииРеактивность Vue reactivity - как это работает под капотом и как этим пользоватьсяПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueОпции компонента в Go - паттерн component-optionsРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование методов в компонентах Vue для обработки логикиИспользование хуков жизненного цикла в VueРабота с ключами key в списках и компонентах VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitОрганизация сеток и гридов для верстки интерфейсов на VueРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreВычисляемые свойства computed во Vue.jsСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueОсновы Vue - vue-basics для уверенного стартаИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueТелепортация - архитектура и реализация в серверных приложенияхРабота с teleport для управления DOM во VueSuspense в React - управление асинхронными данными и ленивой загрузкойПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиПлагины Vue vue-plugins - полное практическое руководствоРуководство по nextTick для работы с DOMМиксины - mixins в современном программированииJSX в Vue с использованием плагина vue-jsxСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьКастомные элементы - Custom Elements в современном JavaScriptИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний

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

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

Vue 3 и Pinia

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

TypeScript с нуля

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

Next.js - с нуля

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

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