Перейти к содержанию

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

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

На этой странице описан API, относящийся к встроенным хукам React.

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

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

useState

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

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

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

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

1
setState(newState)

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

Примечание

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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>
    </>
  )
}

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

Примечание

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

1
2
3
4
setState((prevState) => {
  // Object.assign также будет работать
  return { ...prevState, ...updatedValues }
})

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

Ленивая инициализация состояния

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

1
2
3
4
const [state, setState] = useState(() => {
  const initialState = someExpensiveComputation(props)
  return initialState
})

Досрочное прекращение обновления состояния

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

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

useEffect

1
useEffect(didUpdate)

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

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

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

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

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

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

1
2
3
4
5
6
7
useEffect(() => {
  const subscription = props.source.subscribe()
  return () => {
    // Очистить подписку
    subscription.unsubscribe()
  }
})

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

Порядок срабатывания эффектов

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

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

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

Условное срабатывание эффекта

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

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

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

1
2
3
4
5
6
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

1
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.Provider>.

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

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

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

useReducer

1
2
3
4
5
const [state, dispatch] = useReducer(
  reducer,
  initialArg,
  init
)

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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: 'increment' })}
      >
        +
      </button>
      <button
        onClick={() => dispatch({ type: 'decrement' })}
      >
        -
      </button>
    </>
  )
}

Примечание

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

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

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

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

Примечание

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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: 'increment' })}
      >
        +
      </button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  )
}

Досрочное прекращение dispatch

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

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

useCallback

1
2
3
const memoizedCallback = useCallback(() => {
  doSomething(a, b)
}, [a, b])

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

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

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

Примечание

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

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

useMemo

1
2
3
4
const memoizedValue = useMemo(
  () => computeExpensiveValue(a, b),
  [a, b]
)

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

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

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

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

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

Примечание

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

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

useRef

1
const refContainer = useRef(initialValue)

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
function TextInputWithFocusButton() {
  const inputEl = useRef(null)
  const onButtonClick = () => {
    // `current` указывает на смонтированный элемент `input`
    inputEl.current.focus()
  }
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>
        Установить фокус на поле ввода
      </button>
    </>
  )
}

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

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

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

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

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

useImperativeHandle

1
useImperativeHandle(ref, createHandle, [deps])

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

В этом примере родительский компонент, который отображает <FancyInput ref={fancyInputRef} />, сможет вызывать fancyInputRef.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

1
useDebugValue(value)

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null)

  // ...

  // Показывать ярлык в DevTools рядом с этим хуком
  // например, «Статус друга: В сети»
  useDebugValue(isOnline ? 'В сети' : 'Не в сети')

  return isOnline
}

Совет

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

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

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

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

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

1
useDebugValue(date, (date) => date.toDateString())

Комментарии