Что такое prop drilling в React и как с ним бороться?

MiddleReact · Frontend·Обновлено 8 августа 2026
Коротко
Prop drilling — это передача props через несколько промежуточных компонентов, которым они не нужны, только чтобы добраться до глубоко вложенного компонента. Решается через Context API, менеджеры состояния (Redux, Zustand) или композицию компонентов.

Что такое prop drilling

Prop drilling (или «бурение пропсов») — ситуация, когда данные передаются через цепочку компонентов, которые сами эти данные не используют. Компоненты выступают лишь «транзитными узлами», принимая и передавая пропсы дальше.

Это приводит к нескольким проблемам:

  • Компоненты обрастают пропсами, которые им не нужны
  • Рефакторинг становится сложным: при изменении типа данных нужно обновить все промежуточные компоненты
  • Читаемость кода ухудшается — непонятно, какой компонент реально использует данные

Способы решения

1. Context API

Встроенный механизм React для передачи данных в дерево без явной передачи пропсов на каждом уровне.

// Создаём контекст с типом
const UserContext = createContext<User | null>(null);

// Провайдер оборачивает дерево
function App() {
  const [user, setUser] = useState<User>(currentUser);
  return (
    <UserContext.Provider value={user}>
      <Layout />
    </UserContext.Provider>
  );
}

// Глубоко вложенный компонент получает данные напрямую
function UserAvatar() {
  const user = useContext(UserContext);
  return <img src={user?.avatar} alt={user?.name} />;
}

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

2. Менеджеры состояния

Для сложных приложений с большим объёмом общего состояния используют внешние библиотеки.

Zustand — лёгкий вариант:

import { create } from 'zustand';

const useUserStore = create<UserStore>((set) => ({
  user: null,
  setUser: (user) => set({ user }),
}));

// Любой компонент подключается напрямую
function UserAvatar() {
  const user = useUserStore((state) => state.user);
  return <img src={user?.avatar} />
}

3. Композиция компонентов (Component Composition)

Часто недооценённый подход: передавать готовые компоненты вместо данных через children или render props.

// Вместо передачи user через несколько уровней —
// передаём уже готовый компонент
function App() {
  const user = useCurrentUser();
  return (
    <Layout
      header={<Header avatar={<UserAvatar user={user} />} />}
    />
  );
}

// Layout не знает ничего о user
function Layout({ header }: { header: ReactNode }) {
  return <div>{header}<main>...</main></div>;
}

4. Кастомные хуки

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

function useCurrentUser() {
  return useContext(UserContext);
}

// Вызывается только в компонентах, которым реально нужен пользователь
function UserProfile() {
  const user = useCurrentUser();
  return <div>{user.name}</div>;
}

Когда prop drilling — это нормально

Не всегда prop drilling — проблема. Для 1–2 уровней вложенности явная передача пропсов предпочтительнее: она делает поток данных явным и упрощает тестирование. Усложнять архитектуру стоит только когда цепочка становится длиннее 2–3 уровней или данные используются в разных ветках дерева.

Что хочет услышать интервьюер

Чёткое определение prop drilling с пониманием, почему это проблема (связанность, рефакторинг)

Знание Context API как встроенного решения и понимание его ограничений (перерендеры при изменении контекста)

Понимание разницы между Context и внешними стейт-менеджерами (Redux, Zustand, Jotai)

Знание паттерна композиции компонентов как альтернативы без добавления зависимостей

Понимание, что prop drilling на 1–2 уровня — это допустимо и даже предпочтительно

Пример: Проблема: prop drilling через 3 уровня

// Данные user передаются через компоненты, которым он не нужен
function App() {
  const user = { name: 'Иван', avatar: '/ivan.png' };
  return <Page user={user} />;
}

function Page({ user }: { user: User }) {
  // Page не использует user, только передаёт дальше
  return <Sidebar user={user} />;
}

function Sidebar({ user }: { user: User }) {
  // Sidebar тоже не использует user
  return <UserWidget user={user} />;
}

function UserWidget({ user }: { user: User }) {
  // Только здесь user реально нужен
  return <img src={user.avatar} alt={user.name} />;
}

Пример: Решение через Context API

import { createContext, useContext, useState } from 'react';

interface User {
  name: string;
  avatar: string;
}

const UserContext = createContext<User | null>(null);

export function useUser() {
  const user = useContext(UserContext);
  if (!user) throw new Error('useUser должен использоваться внутри UserProvider');
  return user;
}

export function UserProvider({ children }: { children: React.ReactNode }) {
  const [user] = useState<User>({ name: 'Иван', avatar: '/ivan.png' });
  return (
    <UserContext.Provider value={user}>
      {children}
    </UserContext.Provider>
  );
}

// Промежуточные компоненты не несут лишних пропсов
function App() {
  return (
    <UserProvider>
      <Page />
    </UserProvider>
  );
}

function Page() { return <Sidebar />; }
function Sidebar() { return <UserWidget />; }

function UserWidget() {
  const user = useUser(); // Получаем напрямую
  return <img src={user.avatar} alt={user.name} />;
}

Пример: Решение через композицию компонентов

// Передаём готовый компонент, а не данные
function App() {
  const user = { name: 'Иван', avatar: '/ivan.png' };
  // UserWidget создаётся здесь, где есть доступ к user
  return <Page sidebar={<Sidebar widget={<UserWidget user={user} />} />} />;
}

function Page({ sidebar }: { sidebar: React.ReactNode }) {
  // Page не знает ни о каком user
  return (
    <div>
      <main>Контент</main>
      {sidebar}
    </div>
  );
}

function Sidebar({ widget }: { widget: React.ReactNode }) {
  // Sidebar тоже не знает о user
  return <aside>{widget}</aside>;
}

function UserWidget({ user }: { user: { name: string; avatar: string } }) {
  return <img src={user.avatar} alt={user.name} />;
}

Типичные ошибки

Считать, что Context API — всегда лучшее решение: не учитывают проблему лишних перерендеров при частом обновлении контекста

Путать prop drilling с нормальной передачей пропсов: предлагают усложнить архитектуру там, где достаточно 1–2 уровня

Не знать о паттерне композиции (children/render props) как об элегантном решении без дополнительных зависимостей

Называть только Redux как решение, не упоминая более современные альтернативы (Zustand, Jotai, Recoil)

Не уметь объяснить, как мемоизировать контекст (useMemo на value) для предотвращения лишних перерендеров

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

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

TypeScript с нуля

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

Feature-Sliced Design

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

Next.js - с нуля

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