Архитектура React построена на принципе однонаправленного потока данных (One-Way Data Flow): данные в дереве компонентов передаются строго сверху вниз — от родительских компонентов к дочерним — через входные параметры, называемые пропсами (Props, сокращение от Properties).
Пропсы обладают фундаментальным свойством: они строго иммутабельны (Read-Only). Компонент никогда не имеет права модифицировать собственные пропсы. Если дочернему компоненту требуется сообщить родителю об изменении данных, он делает это не через мутацию пропсов, а вызывая переданную сверху функцию обратного вызова (Callback function).
Вместо классического объектно-ориентированного наследования классов (extends BaseComponent) React использует композицию (Composition). Главным механизмом композиции выступает специальный проп children, позволяющий родительскому компоненту служить универсальным лейаут-слотом (контейнером) для любого внешнего JSX-дерева.
Взглянем на однонаправленный поток данных и композицию:
+-------------------------------------------------------------------------+
| ОДНОНАПРАВЛЕННЫЙ ПОТОК ДАННЫХ (Top-Down Flow) |
+-------------------------------------------------------------------------+
| [Родительский компонент: Dashboard] |
| | ^ |
| props (Данные| | callback (События) |
| идут вниз) v | идут вверх) |
| [Дочерний компонент: MetricCard] |
+-------------------------------------------------------------------------+
| КОМПОЗИЦИЯ ЧЕРЕЗ CHILDREN (Слотная архитектура): |
| |
| <Modal title="Подтверждение"> |
| <p>Вы уверены, что хотите удалить запись?</p> |
| <Button variant="danger">Удалить</Button> |
| </Modal> |
| |
| Компонент Modal понятия не имеет, что именно находится внутри! |
| Он лишь рендерит {children} в отведенном визуальном контейнере. |
+-------------------------------------------------------------------------+Транспилятор JSX упаковывает все переданные атрибуты и вложенный children в единый plain JS-объект.
В режиме разработки React замораживает объект props для предотвращения случайных мутаций внутри компонента.
Компонент получает типизированный объект props в качестве первого аргумента своей функции.
При повторном рендере React выполняет поверхностное сравнение (Shallow Compare) пропсов для оптимизации.
Что произойдет, если компонент попытается изменить свой проп напрямую: `props.title = 'Новый заголовок'`?
Card, Modal, Drawer, принимающие произвольный контент через children.onSelect={(id) => handleSelect(id)}.headerSlot, footerSlot, actionSlot).variant="primary" | "ghost", size="sm" | "lg").В современном React пропсы типизируются через интерфейсы или псевдонимы типов без устаревшего React.FC:
import React from 'react';
// Описание контракта пропсов
interface MetricCardProps {
title: string;
value: number | string;
changePercent?: number; // Опциональный проп
icon: React.ComponentType<{ className?: string }>; // Компонент иконки
onRefresh?: () => void; // Колбэк без аргументов
}
export function MetricCard({
title,
value,
changePercent,
icon: Icon,
onRefresh,
}: MetricCardProps) {
const isPositive = changePercent !== undefined && changePercent >= 0;
return (
<div className="p-4 rounded-xl border border-border bg-bg-surface flex flex-col gap-2">
<div className="flex items-center justify-between text-text-muted">
<span className="text-sm font-medium">{title}</span>
<Icon className="w-5 h-5 text-accent-cyan" />
</div>
<div className="text-2xl font-bold text-text-primary">{value}</div>
{changePercent !== undefined && (
<span
className={`text-xs font-semibold ${
isPositive ? 'text-emerald-500' : 'text-rose-500'
}`}
>
{isPositive ? '+' : ''}{changePercent}% за неделю
</span>
)}
{onRefresh && (
<button
onClick={onRefresh}
className="mt-2 text-xs text-accent-cyan hover:underline self-start"
>
Обновить данные
</button>
)}
</div>
);
}children и ReactNode#Проп children имеет стандартный тип React.ReactNode, допускающий любые допустимые JSX-структуры (строки, числа, элементы, фрагменты, порталы или null):
import React from 'react';
interface ContainerProps {
title: string;
headerAction?: React.ReactNode; // Именованный слот
children: React.ReactNode; // Основной контентный слот
}
export function ContentContainer({ title, headerAction, children }: ContainerProps) {
return (
<section className="rounded-2xl border border-border bg-bg-surface overflow-hidden shadow-sm">
<div className="flex items-center justify-between px-6 py-4 border-b border-border bg-bg-surface/50">
<h3 className="text-base font-semibold text-text-primary">{title}</h3>
{headerAction && <div>{headerAction}</div>}
</div>
<div className="p-6">
{/* Рендеринг переданных дочерних элементов */}
{children}
</div>
</section>
);
}В современных функциональных компонентах значения по умолчанию задаются стандартной деструктуризацией JavaScript:
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary' | 'danger';
isDisabled?: boolean;
}
// Задание дефолтных значений прямо в сигнатуре
export function AppButton({
label,
variant = 'primary',
isDisabled = false,
}: ButtonProps) {
return (
<button
disabled={isDisabled}
className={`btn-base btn-${variant}`}
>
{label}
</button>
);
}Какой подход является наиболее простым и рекомендуемым в документации React для решения проблемы Props Drilling, когда данные нужно передать глубоко вниз через промежуточные компоненты?
В React 19 ref передается как обычный проп (без forwardRef). Однако не следует передавать пропсы по цепочке через неконтролируемый спред {...props}, если часть пропсов не предназначена для DOM-узла:
// ОПАСНО: неизвестные кастомные пропсы попадут в атрибуты браузерного DOM!
function BadInput({ customTheme, ...rest }: any) {
return <input {...rest} />; // customTheme не попадет, но другие невалидные поля попадут!
}Всегда явно объявляйте интерфейсы и разделяйте DOM-атрибуты (React.ComponentProps<'input'>).
Устаревший паттерн React.cloneElement(children, { newProp: 123 }) хрупок, неявен для TypeScript и затрудняет отладку.
Решение: Используйте паттерн слотов, функции Render Props или React Context для неявной передачи состояний составных компонентов (Compound Components).