Spec-Zone.ru › React 17

Справочник по API хуков

Хуки — это новое дополнение в React 16.8. Они позволяют использовать состояние и другие возможности React без написания класса.

Эта страница описывает API встроенных хуков в React.

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

  • Основные хуки

    • useState
    • useEffect
    • useContext
  • Дополнительные хуки

    • useReducer
    • useCallback
    • useMemo
    • useRef
    • useImperativeHandle
    • useLayoutEffect
    • useDebugValue

Основные хуки

useState

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

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

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

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

setState(newState);

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

Примечание

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

Функциональные обновления

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

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

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

Если ваша функция обновления возвращает точно такое же значение, как текущее состояние, последующий перерендер будет пропущен.

Примечание

В отличие от метода setState в классах компонентов, useState не автоматически объединяет объекты обновлений. Вы можете воспроизвести это поведение, объединив функциональный способ обновления с синтаксисом разброса объектов:

const [state, setState] = useState({});
setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

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

Ленивое начальное состояние

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

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

Отказ от обновления состояния

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

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

useEffect

useEffect(didUpdate);

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

Изменения, подписки, таймеры, регистрация и другие побочные эффекты не допускаются внутри основного тела функции компонента (так называемая фаза рендеринга React). Это приведёт к запутанным ошибкам и несоответствиям в пользовательском интерфейсе.

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

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

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

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

useEffect(() => {
  const subscription = props.source.subscribe();
  return () => {
    // Clean up the subscription
    subscription.unsubscribe();
  };
});

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

Время срабатывания эффектов

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

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

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

Условный запуск эффекта

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

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

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

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

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

Примечание

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

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

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

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

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

useContext

const value = useContext(MyContext);

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

Когда ближайший <MyContext.Provider> над компонентом обновляется, этот хук инициирует повторный рендеринг с последним значением контекста value, переданным в этот MyContext провайдер. Даже если предок использует React.memo или shouldComponentUpdate, повторный рендеринг всё ещё произойдёт, начиная с самого компонента с помощью useContext.

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

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

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

Подсказка

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

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

Объединение с Context.Provider

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);

  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

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

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

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

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() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

Примечание

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

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

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

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

Примечание

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

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

Вы также можете создать начальное состояние лениво. Для этого вы можете передать функцию 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 (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

Прерывание dispatch

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

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

useCallback

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

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

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

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

Примечание

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

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

useMemo

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

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

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

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

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

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

Примечание

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

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

useRef

const refContainer = useRef(initialValue);

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

Распространённый случай использования — императивный доступ к дочернему элементу:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

По сути, useRef подобен «ящику», который может хранить мутабельное значение в своём свойстве .current.

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

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

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

Помните, что useRef не уведомляет вас, когда его содержимое меняется. Изменение свойства .current не вызывает повторный рендер. Если вы хотите выполнить какой-то код, когда React присоединяет или отсоединяет ref к узлу DOM, вы можете использовать ссылочный колбэк вместо этого.

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);

В этом примере родительский компонент, рендеринг <FancyInput ref={inputRef} /> сможет вызвать inputRef.current.focus().

useLayoutEffect

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

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

Подсказка

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

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

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

useDebugValue

useDebugValue(value)

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

Например, рассмотрим пользовательский хук useFriendStatus описанный в “Создание собственных хуков”:

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  // ...

  // Show a label in DevTools next to this Hook
  // e.g. "FriendStatus: Online"
  useDebugValue(isOnline ? 'Online' : 'Offline');

  return isOnline;
}

Подсказка

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

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

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

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

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

useDebugValue(date, date => date.toDateString());
Полезен ли этот раздел?

© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://17.reactjs.org/docs/hooks-reference.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API