3.12.7 Справка по API хуков


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


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

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




3.12.7.1 Базовые хуки



3.12.7.1.1 useState



Код
    
  const [state, setState] = useState(initialState);
  

Возвращает значение с состоянием и функцию для его обновления.

Во время начальной отрисовки возвращаемое состояние state совпадает со значением initialState, передаваемым в качестве первого аргумента.

Функция setState обновляет состояние. Она принимает новое значение состояния и ставит в очередь повторную отрисовку компонента.


Код
    
  setState(newState);
  

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


Внимание!

React гарантирует, что функция setState остается стабильной и неизменной при повторных отрисовках. Поэтому её без всяких опасений можно не указывать в списке зависимостей у хуков useEffect или useCallback.


3.12.7.1.1.1 Обновления состояния в функциональной форме


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


Код
    
  function Counter({initialCount}) {
    const [count, setCount] = useState(initialCount);
    return (
      <>
        Значение счетчика: {count}
        <button onClick={() => setCount(initialCount)}>Сброс</button>
        <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
        <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      </>
    );
  }
  

Кнопки + и - используют функциональную форму обновления состояния, так как обновленное значение основано на предыдущем значении. Однако кнопка «Сброс» использует обычную форму и всегда устанавливает count обратно к начальному значению.


Внимание!

В отличие от метода setState, характерного для компонентов-классов, useState не объединяет(мерджит) объекты обновления автоматически. Однако вы сами можете реализовать такое поведение, комбинируя функциональную форму обновления со spread синтаксисом для объекта:

Код
    
  setState(prevState => {
    // Object.assign будет работать точно также
    return {...prevState, ...updatedValues};
  });
  

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


3.12.7.1.1.2 Ленивая установка начального состояния


Аргумент initialState - это состояние, которое будет использовано только во время начальной отрисовки. В последующих отрисовках он игнорируется. Если начальное состояние - это результат дорогостоящих вычислений, то в первый аргумент можно передать функцию, которая будет выполнена только при начальной отрисовке:


Код
    
  const [state, setState] = useState(() => {
    const initialState = someExpensiveComputation(props);
    return initialState;
  });
  


3.12.7.1.1.3 Отмена обновления состояния


Если вы обновите хук состояния тем же значением, что и текущее состояние, React прекратит обновление компонента и выйдет из хука. Также он не будет отрисовывать дочерние элементы и запускать эффекты. (React использует алгоритм сравнения Object.is.)

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


3.12.7.1.2 useEffect



Код
    
  useEffect(didUpdate);
  

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

Мутации, подписки, таймеры, логирование и любые другие побочные эффекты не допустимы внутри основного тела компонента-функции. Иначе это приведет к запутанным ошибкам и несоответствиям в UI.

Здесь на помощь приходит useEffect. Функция, переданная useEffect, будет запущена после того, как результат отрисовки будет зафиксирован и отображен на экране. Представляйте эффекты как мост из чисто функционального мира React в императивный мир.

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


3.12.7.1.2.1 Очистка эффекта


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


Код
    
  useEffect(() => {
    const subscription = props.source.subscribe();
    return () => {
      // Очистить (удалить) подписку
      subscription.unsubscribe();
    };
  });
  

Функция очистки запускается до удаления компонента из UI, чтобы предотвратить утечки памяти. Кроме того, если компонент отрисовывается несколько раз (так обычно и происходит), предыдущий эффект очищается перед выполнением следующего эффекта. Для нашего примера это означает, что новая подписка создается при каждом обновлении. Чтобы избежать запуска эффекта на каждом обновлении, читайте далее.


3.12.7.1.2.2 Управление временем запуска эффектов


В отличие от componentDidMount и componentDidUpdate, функция, переданная useEffect, срабатывает после компоновки(layout) и рисования(paint) во время отложенного события. Это делает её подходящей для запуска многих распространенных эффектов, таких как настройка подписок и обработчиков событий, так как большинство типов действий не должны блокировать браузер во время обновления экрана.

Однако не все эффекты могут быть отложены. Например, мутация DOM, которая видна пользователю, должна запускаться синхронно перед следующим рисованием(paint), чтобы пользователь не заметил визуальное несоответствие. (Различие концептуально аналогично различию между пассивными и активными слушателями событий.) Для этих типов эффектов React предоставляет еще один хук, называемый useLayoutEffect. Он имеет ту же сигнатуру, что и useEffect, и отличается только моментом запуска.

Хотя useEffect откладывается до тех пор, пока браузер не выполнит прорисовку, он гарантированно срабатывает перед любыми новыми отрисовками компонента. React всегда запускает все эффекты предыдущей отрисовки перед началом очередного обновления.


3.12.7.1.2.3 Запуск эффектов по условию


Поведение эффектов по умолчанию: эффект запускается после каждой завершенной отрисовки. Таким образом, эффект всегда пересоздаётся, если изменяется одна из его зависимостей.

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

Чтобы добиться этого, передайте функции useEffect второй аргумент, который является массивом значений. От этих значений будет зависеть эффект. Теперь наш обновленный пример выглядит так:


Код
    
  useEffect(
    () => {
      const subscription = props.source.subscribe();
      return () => {
        subscription.unsubscribe();
      };
    },
    [props.source],
  );
  

Теперь подписка будет пересоздаваться только при изменении props.source.


Внимание!

Используя эту оптимизацию, убедитесь, что массив, используемый эффектом, включает в себя значения только из области компонента (например, из props и state), которые должны изменяться со временем. В противном случае ваш код будет ссылаться на устаревшие значения из предыдущих отрисовок. Узнайте больше о том, как работать с функциями и что делать, если значения массива меняются слишком часто.

Если вы хотите запустить эффект и очистить его только один раз (при монтировании и демонтировании), вы можете передать пустой массив [] в качестве второго аргумента. Это говорит React, что ваш эффект не зависит от каких-либо значений из props или state, поэтому его не нужно запускать повторно. React не обрабатывает это как особый случай - это непосредственно следует из традиционного поведения массива зависимостей.

Если вы передадите пустой массив [] в useEffect, то props и state внутри эффекта всегда будут иметь свои начальные значения. Хотя передача [] в качестве второго аргумента ближе к знакомой ментальной модели методов componentDidMount и componentWillUnmount, существуют и более эффективные решения, позволяющие избежать слишком частого повторного запуска эффектов. Кроме того, не забывайте, что React откладывает запуск useEffect до тех пор, пока браузер не выполнит рисование, поэтому выполнить некоторую дополнительную работу можно без всяких проблем.

Мы рекомендуем использовать правило exhaustive-deps как часть нашего пакета eslint-plugin-react-hooks. Оно показывает предупреждение, когда зависимости указаны неправильно, и предлагает исправление.

В качестве аргумента функции эффекта, конечно, не передается никакого массива зависимостей. Однако, концептуально, поведение идентично: каждое значение, на которое ссылается функция эффекта, должно присутствовать в массиве зависимостей. В будущем наш продвинутый компилятор будет создавать этот массив автоматически.


3.12.7.1.3 useContext



Код
    
  const value = useContext(MyContext);
  

Принимает объект контекста (значение, возвращаемое из React.createContext) и возвращает текущее значение контекста. Текущее значение контекста определяется свойством value ближайшего <MyContext.Provider> над вызывающим компонентом в дереве.

Когда ближайший <MyContext.Provider> над компонентом обновляется, этот хук инициирует повторную отрисовку с последним значением контекста, переданным провайдеру MyContext.

Помните, что аргументом useContext должен быть сам объект контекста:

  • Правильно: useContext(MyContext)

  • Неправильно: useContext(MyContext.Consumer)

  • Неправильно: useContext(MyContext.Provider)

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


Подсказка!

Если вы уже знакомы с API контекста, то useContext(MyContext) эквивалентен static contextType = MyContext в классе или <MyContext.Consumer>.

useContext(MyContext) позволяет только читать контекст и подписываться на его изменения. Вам всё еще нужен <MyContext.Provider> выше в дереве, чтобы предоставлять значение для этого контекста.



3.12.7.2 Дополнительные хуки


Следующие хуки являются либо дополнительными вариантами базовых, перечисленных ранее, либо необходимы только для каких-то специфических случаев.


3.12.7.2.1 useReducer



Код
    
  const [state, dispatch] = useReducer(reducer, initialArg, init);
  

Это альтернатива useState. Принимает редюсер типа (state, action) => newState и возвращает текущее состояние в паре с методом dispatch. (Если вы уже знакомы с Redux, то знаете, как это работает.)

Обычно useReducer предпочтительнее useState, когда у вас сложная логика состояния, работающая с несколькими значениями, или когда следующее состояние зависит от предыдущего. useReducer также позволяет оптимизировать производительность компонентов, которые запускают глубокие обновления, поскольку вы можете передавать dispatch вместо обычных коллбэков.

Вот пример счетчика из пункта о useState, переписанный для использования редюсера:


Код
    
  const initialState = {count: 0};
  
  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return {count: state.count + 1};
      case 'decrement':
        return {count: state.count - 1};
      default:
        throw new Error();
    }
  }
  
  function Counter({initialState}) {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
      <>
        Значение счетчика: {state.count}
        <button onClick={() => dispatch({type: 'increment'})}>+</button>
        <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      </>
    );
  }
  


Внимание!

React гарантирует, что функция dispatch остается стабильной и неизменной при повторных отрисовках. Поэтому её без всяких опасений можно не указывать в списке зависимостей у хуков useEffect или useCallback.

раздел в разработке...


3.12.7.2.1.1 Указание начального состояния


Существует два разных способа инициализации состояния useReducer. Вы можете выбрать любой из них в зависимости от ситуации. Самый простой способ - передать начальное состояние вторым аргументом:


Код
    
  const [state, dispatch] = useReducer(
      reducer,
      {count: initialCount}
  );
  


Внимание!

React не использует инициализацию состояния в аргументе типа state = initialState, которое так популярно в Redux. Иногда начальное значение должно зависеть от свойств props, поэтому оно указывается в вызове хука. Если вы чувствуете себя некомфортно, то можете вызвать useReducer(reducer, undefined, reducer), чтобы эмулировать поведение Redux, но это не рекомендуется.


3.12.7.2.1.2 Ленивая инициализация


Второй способ заключается в ленивом/отложенном создании начального состояния. Для этого вам нужно передать функцию init в качестве третьего аргумента. Начальное состояние будет установлено в значение init(initialArg).

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


Код
    
  function init(initialCount) {
    return {count: initialCount};
  }
  
  function reducer(state, action) {
    switch (action.type) {
      case 'increment':
        return {count: state.count + 1};
      case 'decrement':
        return {count: state.count - 1};
      case 'reset':
        return init(action.payload);
      default:
        throw new Error();
    }
  }
  
  function Counter({initialCount}) {
    const [state, dispatch] = useReducer(reducer, initialCount, init);
    return (
      <>
        Значение счетчика: {state.count}
        <button
            onClick={() => dispatch({type: 'reset', payload: initialCount})}>
  
          Сброс
        </button>
        <button onClick={() => dispatch({type: 'increment'})}>+</button>
        <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      </>
    );
  }
  


3.12.7.2.1.3 Прекращение процесса диспетчеризации


Если из хука редюсера вы вернете то же самое значение значение состояния что и текущее, React прекратит диспетчеризацию. При этом отрисовки дочерних элементов или срабатывания эффектов не будет. (React использует алгоритм сравнения Object.is).

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


3.12.7.2.2 useCallback



Код
    
  const memoizedCallback = useCallback(
    () => {
      doSomething(a, b);
    },
    [a, b],
  );
  

Возвращает мемоизированный коллбэк.

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

useCallback(fn, deps) это эквивалент useMemo(() => fn, deps).


Внимание!

Массив зависимостей не передаётся как аргумент колбэка. Хотя концептуально поведение идентично: каждое значение, на которое ссылается колбэк, должно присутствовать в массиве зависимостей. В будущем наш продвинутый компилятор будет создавать этот массив автоматически.

Мы рекомендуем использовать правило exhaustive-deps как часть нашего пакета eslint-plugin-react-hooks. Оно показывает предупреждение, когда зависимости указаны неправильно, и предлагает исправление.


3.12.7.2.3 useMemo



Код
    
  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  

Возвращает мемоизированное значение.

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

Помните, что функция, переданная useMemo, запускается во время отрисовки, так что не делайте в ней никаких сторонних действий (то, чего вы обычно не делаете во время отрисовки). Например, побочные эффекты принадлежат useEffect, а не useMemo.

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

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


Внимание!

Массив зависимостей не передаётся как аргумент колбэка. Хотя концептуально поведение идентично: каждое значение, на которое ссылается колбэк, должно присутствовать в массиве зависимостей. В будущем наш продвинутый компилятор будет создавать этот массив автоматически.

Мы рекомендуем использовать правило exhaustive-deps как часть нашего пакета eslint-plugin-react-hooks. Оно показывает предупреждение, когда зависимости указаны неправильно, и предлагает исправление.


3.12.7.2.4 useRef



Код
    
  const refContainer = useRef(initialValue);
  

useRef возвращает изменяемый объект ref, чьё свойство .current инициализируется переданным аргументом initialValue. Возвращенный объект будет сохранён в течение всего времени жизни компонента.

Обычный вариант использования - это императивный доступ к дочернему элементу:


Код
    
  function TextInputWithFocusButton() {
    const inputEl = useRef(null);
    
    const onButtonClick = () => {
      // `current` указывает на монтированный элемент текстового поля ввода
      inputEl.current.focus();
    };
    
    return (
      <>
        <input ref={inputEl} type="text" />
        <button onClick={onButtonClick}>Фокус</button>
      </>
    );
  }
  

По сути, useRef похож на "коробку", которая может хранить изменяемое значение в своём свойстве .current.

Вероятно, вы знакомы с ссылками в первую очередь как способ доступа к DOM. Если вы передадите объект ref в React с помощью <div ref = {myRef} />, React будет устанавливать его свойство .current в соответствующий узел DOM каждый раз, когда этот узел изменяется.

Однако useRef() полезен не только для атрибута ref. Он удобен для хранения любого изменяемого значения почти так же, как и поля экземпляра класса.

Это возможно благодаря тому, потому что useRef() создает простой объект JavaScript. Единственное отличие между useRef() и созданием объекта {current: ...} заключается в том, что useRef будет возвращать вам один и тот же объект ref при каждой отрисовке.

Имейте в виду, что useRef не уведомляет вас о мутации своего содержимого. Изменение свойства .current не приведёт к повторной отрисовке. Если вы хотите запустить какой-то код, когда React присоединяет или отсоединяет ссылку на узел DOM, для ref лучше использовать колбэк: ref={ elem => { ... } }.


3.12.7.2.5 useImperativeHandle



Код
    
  useImperativeHandle(ref, createHandle, [deps])
  

useImperativeHandle кастомизирует значение экземпляра ref, предоставленного родительским компонентом. Напоминаем, что в большинстве случаев следует избегать императивного кода с использованием ссылок ref. useImperativeHandle следует использовать вместе с forwardRef:


Код
    
  function FancyInput(props, ref) {
    const inputRef = useRef();
    useImperativeHandle(ref, () => ({
      focus: () => {
        inputRef.current.focus();
      }
    }));
    return <input ref={inputRef} ... />;
  }
  
  FancyInput = forwardRef(FancyInput);
  


3.12.7.2.6 useLayoutEffect



Код
    
  useLayoutEffect(didUpdate);
  

Сигнатура метода идентична useEffect, однако он запускается синхронно после всех мутаций DOM. Используйте его для чтения лэйаута из DOM и синхронной повторной отрисовки. Обновления, запланированные внутри useLayoutEffect, будут сбрасываться синхронно до того, как браузер сможет что-то отрисовать.

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


Подсказка.

Если вы делаете миграцию кода из компонента класса, обратите внимание, что useLayoutEffect запускается в той же фазе, что и методы componentDidMount и componentDidUpdate. Однако мы рекомендуем сперва начать с useEffect, и только в том случае, если возникнут проблемы попытаться использовать useLayoutEffect.

Если вы используете отрисовку на стороне сервера, имейте в виду, что ни useLayoutEffect, ни useEffect не могут работать до тех пор, пока не будет загружен JavaScript. Вот почему React показывает предупреждение, когда отрисовываемый сервером компонент содержит useLayoutEffect. Чтобы его исправить, либо переместите всю логику в useEffect (если она не обязательна для первой отрисовки), либо отложите показ этого компонента до тех пор, пока клиент не выполнит отрисовку (если HTML выглядит сломанным до запуска useLayoutEffect).

Чтобы исключить компонент, которому требуются эффекты лэйаута, из отрисованного сервером HTML-кода, отрисовывайте его условно с помощью isChildShowed && <Child /> и отложите его показ с помощью useEffect(() => {showChild(true);}, []). Таким образом, пользовательский интерфейс не будет выглядеть сломанным до своего оживления.


3.12.7.2.7 useDebugValue



Код
    
  useDebugValue(value)
  

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

Для примера рассмотрим пользовательский хук useFriendStatus, из раздела Пользовательский хук:


Код
    
  function useFriendStatus(friendID) {
    const [isOnline, setIsOnline] = useState(null);
  
    // ...
  
    // Показать надпись в DevTools рядом с данным хуком
    // например "FriendStatus: Online"
    useDebugValue(isOnline ? 'Online' : 'Offline');
  
    return isOnline;
  }
  


Подсказка.

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


Отложенное форматирование значений отладки


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

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

Например, пользовательский хук, который возвращает значение типа Date, может избежать ненужного вызова функции toDateString, передав следующий форматтер:


Код
    
  useDebugValue(date, date => date.toDateString());