← Назад к вопросам

Как устроен хук?

1.7 Middle🔥 181 комментариев
#JavaScript Core

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как устроены хуки в React?

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

Основные принципы работы хуков

React хранит внутреннюю структуру данных — связный список Hook-объектов для каждого компонента. При каждом рендере React проходит по этому списку, вызывая соответствующие хуки в строгом порядке.

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

    // ❌ НЕВЕРНО: порядок хуков может меняться между рендерами
    if (condition) {
        const [state, setState] = useState(initialState);
    }
    
    // ✅ ВЕРНО: порядок хуков стабилен при каждом рендере
    const [state, setState] = useState(initialState);
    
  2. Fiber-ноды и очередь обновлений. Каждому компоненту соответствует Fiber-нода — единица работы в новой архитектуре React. В ней хранятся:

    *   `memoizedState`: Ссылка на первый Hook-объект в связном списке.
    *   `queue`: Очередь обновлений (для `useState`/`useReducer`).

Внутреннее устройство на примере useState

Рассмотрим упрощенную модель работы useState:

  1. Первоначальный рендер: При первом вызове компонента React создает Hook-объект для каждого вызова useState. Этот объект хранит:
    *   `baseState`: Базовое состояние.
    *   `queue`: Очередь обновлений (функции или значения).
    *   `next`: Ссылка на следующий хук в списке.
    Состояние сохраняется в поле `memoizedState` этого объекта.

  1. Последующие рендеры: При повторном вызове компонента React идет по существующему списку Hook-объектов и возвращает актуальное состояние и функцию-диспетчер, привязанную к конкретной очереди этого хука.

  2. Процесс обновления: При вызове функции setState (например, setCount(newValue)) обновление (значение или функция) помещается в очередь соответствующего Hook-объекта. React планирует повторный рендер компонента. Во время рендера React применяет все обновления из очереди к базовому состоянию, вычисляет новое состояние и возвращает его.

Устройство других ключевых хуков

  • useEffect и useLayoutEffect: React хранит в Hook-объекте переданную функцию-эффект и зависимости. После отрисовки компонента (или после мутаций DOM для useLayoutEffect) React сравнивает зависимости. Если они изменились, предыдущий эффект помечается для очистки (вызов возвращаемой функции), а новый — для выполнения.

  • useRef: Самый простой хук. React хранит объект { current: initialValue } в Hook-объекте и возвращает ссылку на один и тот же объект при каждом рендере. Изменение current не вызывает ререндер.

  • useMemo и useCallback: React хранит закешированное значение/функцию и массив зависимостей. При рендере происходит поинтовое сравнение зависимостей (используется алгоритм, аналогичный Object.is). Если зависимости не изменились, возвращается сохраненное значение.

Пользовательские хуки и композиция

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

function useWindowWidth() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    window.addEventListener('resize', handleResize);
    // Очистка при размонтировании
    return () => window.removeEventListener('resize', handleResize);
  }, []); // Пустой массив зависимостей = эффект только при монтировании/размонтировании

  return width;
}
// Использование в компоненте
function MyComponent() {
  const width = useWindowWidth(); // Состояние и эффект инкапсулированы в хуке
  return <div>Window width: {width}</div>;
}

Итог: ключевые аспекты устройства хуков

  • Связный список Hook-объектов: Гарантирует сохранение состояния между рендерами и требует стабильного порядка вызова.
  • Разделение на фазы: React разделяет фазу рендера (выполнение тела функции компонента) и фазу коммита (применение эффектов, обновление DOM).
  • Ленивая инициализация: Некоторые хуки (например, useState с функцией) позволяют отложить вычисление начального состояния.
  • Оптимизации через замыкания: Функции-диспетчеры (setState) и колбэки, созданные хуками, часто стабильны между рендерами, что позволяет избежать лишних ререндеров дочерних компонентов.

Хуки — это не магия, а продуманный API, который опирается на внутренние механизмы React (Fiber, очередность обновлений) и предоставляет разработчику декларативный, композируемый и легко тестируемый способ управления состоянием и побочными эффектами.