Правила хуков — правила использования

30 июля 2026
Автор

Олег Марков

Введение

Хуки — одна из ключевых концепций современного React. Начиная с версии 16.8, они позволяют использовать состояние и другие возможности React в функциональных компонентах. Однако у хуков есть строгие правила использования, без соблюдения которых ваше приложение будет вести себя непредсказуемо, а React не сможет корректно отслеживать состояние компонентов.

В этой статье я расскажу о двух главных правилах хуков, объясню, почему они существуют, покажу типичные ошибки и как их избежать. Также разберём инструменты, которые помогают следить за соблюдением правил в команде.

Курс по теме

Пройти курс React и Redux Toolkit

40 000+ студентов · рейтинг 4.8 · гарантия возврата 30 дней

Два главных правила хуков

React официально устанавливает два основных правила для использования хуков:

  1. Вызывайте хуки только на верхнем уровне — не внутри циклов, условий или вложенных функций
  2. Вызывайте хуки только из React-функций — из функциональных компонентов или кастомных хуков

Давайте подробно разберём каждое из них.

Правило 1: Вызывайте хуки только на верхнем уровне

Что это означает

Хуки нельзя вызывать внутри:

  • Условных операторов (if, else, тернарный оператор)
  • Циклов (for, while, forEach и т.д.)
  • Вложенных функций
  • Блоков try/catch

Хуки всегда должны вызываться на верхнем уровне вашего функционального компонента или кастомного хука — до любых условных проверок или ранних возвратов.

Неправильное использование

function UserProfile({ userId, isAdmin }) {
  // ❌ Неправильно: хук внутри условия
  if (isAdmin) {
    const [adminData, setAdminData] = useState(null);
  }

  // ❌ Неправильно: хук внутри цикла
  const permissions = ['read', 'write', 'delete'];
  permissions.forEach(permission => {
    const [hasPermission, setHasPermission] = useState(false);
  });

  // ❌ Неправильно: хук внутри вложенной функции
  function loadData() {
    const [data, setData] = useState(null);
    useEffect(() => {
      // ...
    }, []);
  }

  return <div>Профиль пользователя</div>;
}

Правильное использование

function UserProfile({ userId, isAdmin }) {
  // ✅ Правильно: все хуки на верхнем уровне
  const [adminData, setAdminData] = useState(null);
  const [permissions, setPermissions] = useState({
    read: false,
    write: false,
    delete: false
  });
  const [data, setData] = useState(null);

  // Теперь можно использовать условия в логике — но не для вызова хуков
  useEffect(() => {
    if (isAdmin) {
      // Загружаем данные администратора
      fetchAdminData(userId).then(setAdminData);
    }
  }, [isAdmin, userId]);

  return <div>Профиль пользователя</div>;
}

Почему это правило существует

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

При каждом рендере React просматривает вызовы хуков в строго фиксированном порядке и сопоставляет их с сохранёнными значениями. Это похоже на пронумерованный список:

Рендер 1:
  [0] useState(null)   → adminData = null
  [1] useState(false)  → isLoading = false
  [2] useEffect(...)   → подписка на данные

Рендер 2 (если isAdmin изменился):
  [0] useState(null)   → adminData = null  ✅ правильно
  [1] useState(false)  → isLoading = false ✅ правильно
  [2] useEffect(...)   → подписка на данные ✅ правильно

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

// Если isAdmin = true
Рендер 1:
  [0] useState(null)   → adminData = null
  [1] useState(false)  → isLoading = false
  [2] useEffect(...)

// Если isAdmin = false (хук пропущен!)
Рендер 2:
  [0] useState(false)  → adminData получает значение isLoading! ❌
  [1] useEffect(...)   → подписка получает данные useState! ❌

React потеряет соответствие между хуком и его сохранённым состоянием, что приведёт к непредсказуемым ошибкам.

Исключение: ранний возврат

Ранний возврат также нарушает правило, если хуки вызываются после него:

function Component({ data }) {
  // ❌ Неправильно: хук после раннего возврата
  if (!data) {
    return <div>Загрузка...</div>;
  }

  // Этот хук будет вызван только когда data существует
  const [processed, setProcessed] = useState(data);

  return <div>{processed}</div>;
}
function Component({ data }) {
  // ✅ Правильно: хуки до раннего возврата
  const [processed, setProcessed] = useState(data);

  if (!data) {
    return <div>Загрузка...</div>;
  }

  return <div>{processed}</div>;
}

Правило 2: Вызывайте хуки только из React-функций

Где можно использовать хуки

Хуки можно вызывать только из двух мест:

  1. Функциональные компоненты React
  2. Кастомные хуки (функции, имя которых начинается с use)

Неправильное использование

// ❌ Неправильно: хук в обычной JavaScript-функции
function calculateTotal(items) {
  const [total, setTotal] = useState(0); // Ошибка!

  items.forEach(item => {
    setTotal(prev => prev + item.price);
  });

  return total;
}

// ❌ Неправильно: хук в обработчике события
document.addEventListener('click', () => {
  const [clicked, setClicked] = useState(false); // Ошибка!
});

// ❌ Неправильно: хук в классовом компоненте
class MyComponent extends React.Component {
  render() {
    const [count, setCount] = useState(0); // Ошибка!
    return <div>{count}</div>;
  }
}

Правильное использование

// ✅ Правильно: хук в функциональном компоненте
function ShoppingCart({ items }) {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    const newTotal = items.reduce((sum, item) => sum + item.price, 0);
    setTotal(newTotal);
  }, [items]);

  return <div>Итого: {total} руб.</div>;
}

// ✅ Правильно: хук в кастомном хуке
function useTotal(items) {
  const [total, setTotal] = useState(0);

  useEffect(() => {
    const newTotal = items.reduce((sum, item) => sum + item.price, 0);
    setTotal(newTotal);
  }, [items]);

  return total;
}

// ✅ Правильно: использование кастомного хука
function ShoppingCart({ items }) {
  const total = useTotal(items);
  return <div>Итого: {total} руб.</div>;
}

Почему это правило существует

React должен знать, какому компоненту принадлежит каждый вызов хука. Это позволяет:

  • Правильно изолировать состояние между компонентами
  • Корректно выполнять обновления при перерисовке
  • Отображать понятные сообщения об ошибках в DevTools

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

Правило именования кастомных хуков

Хотя это не строгое техническое правило, а соглашение, оно тесно связано с предыдущим правилом. Кастомные хуки должны начинаться с префикса use.

// ✅ Правильно: начинается с use
function useWindowSize() {
  const [size, setSize] = useState({ width: 0, height: 0 });

  useEffect(() => {
    function updateSize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight
      });
    }

    window.addEventListener('resize', updateSize);
    updateSize();

    return () => window.removeEventListener('resize', updateSize);
  }, []);

  return size;
}

// ❌ Неправильно: не начинается с use
function getWindowSize() {
  // Даже если внутри используются хуки — это нарушение соглашения
  const [size, setSize] = useState({ width: 0, height: 0 });
  // ...
}

Префикс use важен по нескольким причинам:

  • Плагин ESLint eslint-plugin-react-hooks проверяет правила только для функций с префиксом use
  • Другие разработчики сразу понимают, что функция является хуком
  • React DevTools корректно отображает кастомные хуки в дереве компонентов

Типичные ошибки и как их исправить

Ошибка 1: Хук в условии для оптимизации

// ❌ Попытка "оптимизировать" — не вызывать эффект без данных
function DataViewer({ data }) {
  if (!data) return null; // ← Ранний возврат

  // Этот хук пропускается когда data = null
  const [formatted, setFormatted] = useState(
    formatData(data)
  );

  return <div>{formatted}</div>;
}
// ✅ Правильное решение: перенести логику внутрь эффекта
function DataViewer({ data }) {
  const [formatted, setFormatted] = useState(null);

  useEffect(() => {
    if (data) {
      setFormatted(formatData(data));
    }
  }, [data]);

  if (!data) return null;

  return <div>{formatted}</div>;
}

Ошибка 2: Динамическое количество хуков

// ❌ Количество хуков зависит от данных
function TagList({ tags }) {
  return (
    <div>
      {tags.map(tag => {
        // Каждый рендер может создать разное количество хуков!
        const [isSelected, setIsSelected] = useState(false);
        return (
          <button
            key={tag.id}
            onClick={() => setIsSelected(!isSelected)}
            className={isSelected ? 'selected' : ''}
          >
            {tag.name}
          </button>
        );
      })}
    </div>
  );
}
// ✅ Правильное решение: вынести в отдельный компонент
function Tag({ tag }) {
  // Теперь каждый компонент имеет свой стабильный хук
  const [isSelected, setIsSelected] = useState(false);

  return (
    <button
      onClick={() => setIsSelected(!isSelected)}
      className={isSelected ? 'selected' : ''}
    >
      {tag.name}
    </button>
  );
}

function TagList({ tags }) {
  return (
    <div>
      {tags.map(tag => (
        <Tag key={tag.id} tag={tag} />
      ))}
    </div>
  );
}

Ошибка 3: Хук внутри try/catch

// ❌ Неправильно: хук внутри try/catch
function SafeComponent({ config }) {
  try {
    const [value, setValue] = useState(config.defaultValue);
    return <div>{value}</div>;
  } catch (error) {
    return <div>Ошибка конфигурации</div>;
  }
}
// ✅ Правильно: хук на верхнем уровне
function SafeComponent({ config }) {
  const defaultValue = config?.defaultValue ?? '';
  const [value, setValue] = useState(defaultValue);

  if (!config) {
    return <div>Ошибка конфигурации</div>;
  }

  return <div>{value}</div>;
}

Инструменты для соблюдения правил

eslint-plugin-react-hooks

React предоставляет официальный плагин ESLint для автоматической проверки правил хуков. Он входит в стандартную конфигурацию Create React App и большинства современных шаблонов.

Установка:

npm install eslint-plugin-react-hooks --save-dev

Конфигурация в .eslintrc.js:

module.exports = {
  plugins: ['react-hooks'],
  rules: {
    // Проверяет правила вызова хуков
    'react-hooks/rules-of-hooks': 'error',
    // Проверяет полноту массива зависимостей useEffect
    'react-hooks/exhaustive-deps': 'warn'
  }
};

Что проверяет плагин:

// Эти ошибки плагин обнаружит автоматически

// ❌ react-hooks/rules-of-hooks: хук в условии
function Component({ show }) {
  if (show) {
    useState(0); // ESLint: React Hook "useState" is called conditionally
  }
}

// ❌ react-hooks/rules-of-hooks: хук в обычной функции
function notAHook() {
  useState(0); // ESLint: React Hook "useState" cannot be called in a regular function
}

// ❌ react-hooks/exhaustive-deps: неполный массив зависимостей
function Component({ userId }) {
  useEffect(() => {
    fetchUser(userId); // ESLint: React Hook useEffect has a missing dependency: 'userId'
  }, []); // Предупреждение: userId отсутствует в зависимостях
}

React DevTools

React DevTools помогают визуально проверить работу хуков в компоненте. В панели компонента вы можете увидеть все хуки и их текущие значения, что помогает отлаживать проблемы.

Правила хуков и React Strict Mode

React Strict Mode в режиме разработки намеренно вызывает функции компонентов дважды, чтобы выявить побочные эффекты. Это помогает обнаружить нарушения правил хуков:

// В index.js или main.tsx
import { StrictMode } from 'react';

root.render(
  <StrictMode>
    <App />
  </StrictMode>
);

В Strict Mode React выполняет двойной вызов:

  • Тела функциональных компонентов
  • Тел функций, переданных в useState, useMemo, useReducer
  • Функций setup в useEffect

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

Проверка знаний: найдите ошибки

Попробуйте найти нарушения правил хуков в следующем примере:

function ProductCard({ product, isLoggedIn }) {
  const [price, setPrice] = useState(product.price);

  if (!isLoggedIn) {
    return <div>Войдите для просмотра</div>;
  }

  const [discount, setDiscount] = useState(0);

  useEffect(() => {
    if (product.category === 'sale') {
      for (let i = 0; i < product.discounts.length; i++) {
        const [applied, setApplied] = useState(false);
      }
    }
  }, [product]);

  return <div>{price - discount}</div>;
}

Ошибки в примере:

  1. useState(0) для discount вызывается после раннего возврата — нарушение правила верхнего уровня
  2. useState(false) внутри цикла for — нарушение правила верхнего уровня
  3. useState(false) внутри useEffect — нарушение правила верхнего уровня

Исправленная версия:

function ProductCard({ product, isLoggedIn }) {
  const [price, setPrice] = useState(product.price);
  const [discount, setDiscount] = useState(0);
  // Заменяем массив хуков на один хук с массивом
  const [appliedDiscounts, setAppliedDiscounts] = useState([]);

  useEffect(() => {
    if (product.category === 'sale') {
      const applied = product.discounts.map(() => false);
      setAppliedDiscounts(applied);
    }
  }, [product]);

  if (!isLoggedIn) {
    return <div>Войдите для просмотра</div>;
  }

  return <div>{price - discount}</div>;
}

Заключение

Правила хуков существуют не случайно — они обеспечивают предсказуемую работу системы состояния в React. Запомните два главных правила:

  1. Вызывайте хуки только на верхнем уровне — никогда внутри условий, циклов или вложенных функций
  2. Вызывайте хуки только из React-функций — из функциональных компонентов или кастомных хуков (имя которых начинается с use)

Используйте eslint-plugin-react-hooks для автоматической проверки правил. Этот плагин поймает большинство нарушений ещё до запуска приложения. Понимание того, почему эти правила существуют (React отслеживает хуки по порядку вызова), помогает писать правильный код интуитивно, не заглядывая в документацию каждый раз.

Стрелочка влевоuseMemo vs useCallback: подробное руководство по мемоизации в ReactuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Стрелочка вправо

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

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

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

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

Все гайды по React

Uncontrolled Components: когда DOM управляет даннымиБезопасность в React: защита от XSS, CSRF и утечек данныхRender Props: гибкое управление рендерингом в ReactРефакторинг React-кода: техники и лучшие практикиReact Native приложение с нуля до публикации в сторахПрофилирование 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 — управление состоянием в ReactYup - валидация схемZod - валидация с TypeScriptXState - конечные автоматыТемизация в ReactТестирование хуковTailwind CSS с ReactSWR - библиотека для запросовStyled Components — стилизация через JSStorybook - документация компонентовSnapshots тестированиеRedux Toolkit - современный ReduxRTK Query - работа с APIRecoil — библиотека управления состоянием от 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: как и когда оптимизировать тяжелые вычисленияХук use() в React 19useEffect в React что это и как использоватьuseContext — работа с контекстом в ReactuseCallback в React — мемоизация функций и оптимизация ре-рендеровuseActionState в React 19Оптимизация рендеринга в React: от теории к глубокой практикеЧто такое useRef и как его применять в ReactКак и зачем использовать React HooksУправление состоянием в React через ContextНовые возможности React 19Как предотвратить лишние ре-рендеры в React: полное руководствоuseMemo vs useCallback: подробное руководство по мемоизации в ReactПравила хуков — правила использованияuseEffect vs useLayoutEffect: в чём разница и какой хук выбрать?Кастомные хуки в React — создание собственных хуковuseState продвинутое использование в React
Transition API — плавные обновления интерфейса в ReactReact Suspense — приостановка рендераStrictMode в React — как находить ошибки на этапе разработкиКак работает рендеринг в ReactЧто такое props в React и как их правильно использоватьКак работает JSX связка React и HTMLЧто такое React.js и как его использоватьСерверные компоненты React (RSC) — подробный разбор и практикаКак использовать элементы в 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 ₽
Подробнее

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