3.12.7 Справка по API хуков
Хуки доступны в версии React 16.8. Они позволяют использовать состояние и другие функции React, освобождая от необходимости писать класс.
В данном разделе описан API для встроенных хуков React.
Если вы новичок в хуках, то сначала можете ознакомиться с обзором. Помимо прочего вы можете найти много полезной информации в разделе часто задаваемых вопросов.
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.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)
.
Внимание!
Массив зависимостей не передаётся как аргумент колбэка. Хотя концептуально поведение идентично: каждое значение, на которое ссылается колбэк, должно присутствовать в массиве зависимостей. В будущем наш продвинутый компилятор будет создавать этот массив автоматически.Мы рекомендуем использовать правило как часть нашего пакета 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
, а затем добавьте
этот хук для оптимизации производительности.
Внимание!
Массив зависимостей не передаётся как аргумент колбэка. Хотя концептуально поведение идентично: каждое значение, на которое ссылается колбэк, должно присутствовать в массиве зависимостей. В будущем наш продвинутый компилятор будет создавать этот массив автоматически.Мы рекомендуем использовать правило как часть нашего пакета 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());