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.

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