Эта статья представляет собой Plus-версию документа официального веб-сайта.На основе документа официального веб-сайта (с исключениями) добавлены некоторые пояснения в процессе обучения и краткое изложение подводных камней в процессе использования. Если у вас есть время, рекомендуется еще раз прочитать официальную документацию сайта.
1. Введение
Хуки — это новая функция в React 16.8, которая позволяет вам писатькомпонент классаВы также можете использовать состояние и другие функции React.
Хуки обеспечивают мощный и выразительный способ повторного использования логики между компонентами.
import React, { useState } from 'react';
function Example() {
// 声明一个新的叫做 count 的 state 变量
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
1.1 Мотивация
Хуки решают всевозможные, казалось бы, несвязанные проблемы, с которыми мы сталкивались за годы написания и поддержки тысяч компонентов.
1.1.1 Сложность повторного использования логики состояния между компонентами
React не дает возможности привязать многократно используемое поведение к компонентам. Вы можете быть знакомы с некоторыми решениями этой проблемы, такими как компоненты, состоящие из поставщиков, потребителей, компонентов более высокого порядка, свойств рендеринга и других уровней абстракции. Но такие решения требуют реорганизации структуры компонентов, что затрудняет понимание кода.
Теперь вы можете использовать хуки для извлечения логики состояния из компонентов, чтобы эту логику можно было тестировать по отдельности и использовать повторно. Крюк делает тебяПовторное использование логики состояния без изменения структуры компонента.
1.1.2 Сложные компоненты становятся трудными для понимания
Каждый жизненный цикл часто содержит некоторую несвязанную логику. если мы можем бытьcomponentDidMount
Настройте прослушиватели событий вcomponentWillUnmount
Очищено. Связанный код, который необходимо изменить путем сравнения, разбивается, а совершенно несвязанный код смешивается в одном и том же методе. Это может легко привести к ошибкам и привести к несоответствиям в логике.
В большинстве случаев невозможно разбить компоненты на более мелкие детали, потому что логика состояния присутствует везде. Это также создает некоторые проблемы для тестирования. В то же время именно поэтому многие люди используют React сБиблиотека государственного управленияОдна из причин комбинированного использования. Но это частоБудет введено множество абстракций, требующих переключения между разными файлами., что затрудняет повторное использование.
1.1.3 Непонятные классы
Использование классов должно использоваться безthis
, а в JavaScriptthis
Направление непросто понять, в разных сценариях использования методаthis
Объекты, на которые указывают, разные.
1.2 Различия Компоненты класса и функции компонентов
Строго говоря,компонент классаа такжефункциональный компонентЕсть различия. Разные методы написания представляют разные методологии программирования. Класс — это инкапсуляция данных и логики.Если вы решите написать класс, вы должны написать связанные данные и операции в том же классе. В общем случае функции должны делать только одну вещь и возвращать значение. Если у вас есть несколько операций, каждая операция должна быть написана как отдельная функция. Кроме того, состояние данных должно быть отделено от метода действия. Согласно этой идее,React Функциональный компонент должен делать только одно: возвращать HTML-код компонента..
Функциональное программирование относится к операциям, которые не имеют ничего общего с вычислением данных, т.к.побочный эффект. Если функция непосредственно содержит операцию, производящую побочный эффект, она больше нечистая функция, мы называем егоНечистая функция. Чистая функция может содержать побочные эффекты только косвенным образом (т. е. через вызовы других функций).
Использование хуков делает код более лаконичным, а функциональные компоненты больше соответствуют функциональной природе React.
Hook Hook — это решение для побочных эффектов функциональных компонентов React, которое используется для введения побочных эффектов в функциональные компоненты. Тело функционального компонента должно использоваться только для возврата HTML-кода компонента, все остальные операции (побочные эффекты) должны быть импортированы через хуки.
Так как есть так много побочных эффектов, есть много видов крючков. React предоставляет специальные хуки для многих распространенных операций.
-
useState
сохранить состояние -
useContext
контекст доступа -
useRef
сохранить ссылку - ...
-
useEffect
Общий хук побочного эффекта, он используется, чтобы узнать, не можете ли вы найти соответствующий хук
Пока это побочный эффект, его можно использовать.useEffect()
Представлено, его обычное использование заключается в следующем
- выборка данных
- Мониторинг событий или подписка (настройка подписки)
- изменение DOM (изменение DOM)
- выходной журнал (журналирование)
Многие идеи React повлияли на всю отрасль, например, виртуальный DOM, JSX, функциональное программирование, неизменяемое состояние, односторонний поток данных и т. д. Хуки также привнесут важные инновации во внешний интерфейс.
1.3 Инкрементная стратегия
Команда React готова делать хуки повсюдукомпонент классасценариев использования, но по-прежнему будеткомпонент классапредоставить поддержку. Хуки и существующий код могут работать одновременно, и вы можете использовать их постепенно.
2 useState
сохранить состояние
useState
аналогичны компонентам классаthis.setState
.
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42) // 原始值类型
const [todos, setTodos] = useState([{text: 'Learn Hooks'}]) // 对象
const [fruit, setFruit] = useState(() => 'banana') // 函数
// ...
}
useState
- Единственным параметром является начальное состояние, переданное значение будет использоваться только при первой инициализации.
- Возвращаемое значение: текущее состояние и функция, которая обновляет состояние.
- Во время первоначального рендеринга возвращаемое состояние состояния совпадает со значением, переданным в качестве первого параметра initialState.
- Функция setState используется для обновления состояния. Он получает новое значение состояния и ставит в очередь повторный рендеринг компонента.
- Вообще говоря, переменные «исчезают» после выхода из функции, за исключением переменных в состоянии, которые React сохраняет.
- React гарантирует, что идентификатор функции setState стабилен и не изменится при повторном рендеринге компонента.
useState
а такжеthis.setState
разница между
-
useState
Не будет объединять новое состояние со старым состоянием - Дать
useState
Передаваемое состояние может быть любого типа, иthis.setState
может быть только объектом
2.1 Функциональное обновление
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>
</>
);
}
useState
Объекты обновления не объединяются автоматически. вы можете комбинироватьспред оператордля достижения эффекта слияния и обновления объектов.useReducer
— это еще один вариант, который больше подходит для управления объектами состояния с несколькими подзначениями.
setState(prevState => {
return {...prevState, ...updatedValues};
});
2.2 Ленивое начальное состояние
Параметр initialState будет работать только при начальном рендеринге компонента и будет игнорироваться при последующих рендерингах. Если начальное состояние нужно получить путем сложных вычислений, томожет передаваться в функциюИ возвращается в исходное состояние рассчитывается в функции, функция вызывается только при исходном рендеринге:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props); // 只在组件初始化时执行一次
return initialState;
});
// 错误的用法,someExpensiveComputation 在每次组件重新渲染时都会执行
const [state, setState] = useState(someExpensiveComputation(props));
2.3 Пропустить обновление состояния
При вызове функции обновления State Hook и передаче текущего состояния React пропустит рендеринг дочерних компонентов и выполнение эффектов.
React может по-прежнему нуждаться в рендеринге компонента, прежде чем пропускать рендеринг. Но поскольку React не делает узлы излишне «глубже» в дереве компонентов, вам не нужно об этом беспокоиться. Если вы выполняете дорогостоящие вычисления во время рендеринга, вы можете использоватьuseMemo
оптимизировать.
2.4 Руководство по избеганию ям
useState
Возвращаемый метод состояния обновления является асинхронным, и новое значение нельзя получить до следующей перерисовки.
const [count, setCount] = useState(0);
setCount(1);
console.log(count); // 是 0 不是 1
Использование обратных вызовов может помочь нам получить последнее состояние. В примере нижеcurrent => current + 1
Хотя написание более прямоеcount + 1
Немного больше ввода, но результат больше соответствует нашим интуитивным ожиданиям.
const App3 = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(current => {
console.log(1, {count, current}); // 1 {count: 0, current: 0}
return current + 1;
});
setCount(current => {
console.log(2, {count, current}); // 2 {count: 0, current: 1}
return current + 1;
});
};
useEffect(() => {
console.log(3, {count}); // 3 {count: 2}
}, [count]);
return <div onClick={handleClick}>{count}</div>;
}
2.5 Поведенческое тестирование
- setState обновляет состояние компонента, компонент функции будет запущен2 раза реагировать JS.org/docs/strict…
- setState, передающий тот же ссылочный тип или значение примитивного типа, не будет запускать обновление компонента
-
setArray([...array])
Хотя содержимое, соответствующее элементу ArrayItem, не изменилось, оно все равно вызовет повторную визуализацию.
2.5.1 React.FunctionComponent
vs React.PureComponent
Когда родительский компонент обновляется, если реквизиты дочернего компонента не изменились
- FunctionComponent всегда выполняется
- PureComponent
render
Метод не будет выполняться, но система предусмотрена по умолчаниюshouldComponentUpdate
Он все равно будет выполнен [Примечание 1]
Для сравнения, производительность PureComponent лучше.В большинстве случаев эта небольшая разница в производительности не учитывается, но ее можно использовать для оптимизации в ключевых случаях. Конечно, используяReact.memo()
БудуFunctionComponent
Такой же оптимизации можно добиться, обернув один слой.
Примечание 1: ФактическийPureComponent
не существует и не может существовать вshouldComponentUpdate
, фактический код выглядит следующим образом. Сравните один в ReactClassComponent
Нужно ли его обновлять, там всего два места. Сначала посмотрите, есть лиshouldComponentUpdate
метод, второй здесьPureComponent
судить.
if (ctor.prototype && ctor.prototype.isPureReactComponent) {
return (
!shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
);
}
3 useEffect
добавить побочные эффекты
Возможно, вы уже выполняли компоненты React раньше.Данные получены, подписаны или изменены вручную DOM. В совокупности мы называем эти операции «побочными эффектами» или просто «действиями».
useEffect
Это хук эффектов, который добавляет возможность управлять побочными эффектами функциональных компонентов. Это то же самое, что и в компоненте классаcomponentDidMount
,componentDidUpdate
а такжеcomponentWillUnmount
Имеет ту же цель, просто объединен в один API 👏👏.
import React, { useState, useEffect } from 'react'
function Example() {
const [count, setCount] = useState(0)
// 相当于 componentDidMount 和 componentDidUpdate:
useEffect(() => {
// 使用浏览器的 API 更新页面标题
document.title = `You clicked ${count} times`
})
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
<button onClick={() => setCount(c => c + 1)}>Click me</button> // setCount 支持回调函数
</div>
)
}
когда ты звонишьuseEffect
, вы говорите React запустить вашу функцию «побочного эффекта» после внесения изменений в DOM. В компоненте класса функция рендеринга не должна иметь побочных эффектов. В общем, выполнять операции здесь рано, поэтому обычно мы помещаем побочные операции в компонентах класса вcomponentDidMount
а такжеcomponentDidUpdate
середина.
Поскольку функции побочных эффектов объявлены внутри компонента, они могут получить доступ кprops
а такжеstate
.
По умолчанию React будет вызывать функции побочных эффектов после каждого рендеринга, включая первый рендеринг.
Побочные функции также могут бытьвернуть функциючтобы указать, как «очистить» побочные эффекты.
несмотря на то что useEffect
Задержит выполнение после того, как браузер отрисует, но гарантированно выполнится до любых новых рендеров.. React обновит эффект предыдущего рендеринга перед обновлением компонента.
Почему он вызывается внутри компонента useEffect
?
БудуuseEffect
Размещение его внутри компонента дает нам прямой доступ к свойствам или переменным состояния в эффектах. Нам не нужен специальный API для его чтения, он уже хранится в области видимости функции.Хук использует механизм закрытия JavaScript.
useEffect
Будет ли он выполняться после каждого рендера?
Да, по умолчанию он выполняется после первого рендера и после каждого обновления. Возможно, вам будет удобнее думать, что эффект возникает «после рендеринга», и вам не нужно беспокоиться о «монтировании» или «обновлении».
а такжеcomponentDidMount
илиcomponentDidUpdate
разные, используйтеuseEffect
Отправленные эффекты не блокируют обновление экрана браузером, что делает ваше приложение более отзывчивым. В большинстве случаев эффекты не должны выполняться синхронно. В отдельных случаях (например, схемы измерения) существует отдельнаяuseLayoutEffect
Хук предназначен для использования вами, его API такой же, какuseEffect
такой же.
// 类组件里同一行代码要写两次
class Example extends Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times` // 1
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times` // 2
}
render() { /* ... */ }
}
// 使用钩子一行搞定
function Example() {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = `You clicked ${count} times`;
})
return ( /* ... */ )
}
3.1 Разделение проблем с использованием нескольких эффектов
Крюк позволяет намРазделить по назначению кодаих, а не функции жизненного цикла. React будет вызывать каждый эффект по очереди в том порядке, в котором эффект объявлен.
использоватьuseEffect
Важно отметить, что если есть несколько побочных эффектов, несколькоuseEffect
а не объединять их вместе.
class FriendStatusWithCounter extends Component {
constructor(props) {
super(props);
this.state = { count: 0, isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...
}
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
3.2 Эффекты, которые необходимо очистить
Есть также некоторые побочные эффекты, которые необходимо устранить. Например, подписка на внешние источники данных. В этом случае работа по очистке очень важна для предотвращения утечек памяти! Каждый эффект может возвращать функцию очистки. Это объединяет логику добавления и удаления подписок.
При каждом рендеринге React очищает предыдущий эффект перед выполнением текущего эффекта. Наконец, React выполнит еще одну очистку, когда компонент будет размонтирован.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// 添加副作用
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// 通过返回一个函数来指定如何“清除”副作用:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Примечание:useEffect
Функция очистки естьПосле завершения нового рендеринга, но до повторного запуска функции нового побочного эффекта.бегать.
React запускает эффекты только после того, как позволяет браузеру рисовать. Это делает ваше приложение быстрее, поскольку большинству эффектов не нужно блокировать обновления экрана. Очистка эффекта также задерживается. Предыдущий эффект очищается после повторного рендеринга с новыми реквизитами.
function Foo() {
const [count, setCount] = useState(0);
if (count < 1) { setCount(count + 1); }
console.log(`${count}-1`);
useEffect(() => {
console.log(`${count}-2`);
return () => console.log(`${count}-3`);
})
return <div>Foo</div>;
}
// 初始化时输出
0-1
1-1
0-1
1-1
1-2
// 重新渲染时输出
1-1
1-1
1-3 // 此时浏览器已经重新渲染完成了,重新渲染完成后才会清理上一次的副作用
1-2
特别说明:以上是在 React.StrictMode + 开发环境 下的试验结果,非 StrictMode 下输出内容见下方
原因见 https://github.com/facebook/react/issues/15074#issuecomment-471197572
// 初始化时输出
0-1
1-1
1-2
// 重新渲染时输出
1-1
1-3
1-2
Почему вы должны запускать Эффект при каждом обновлении
Опытные разработчики JavaScript могут заметить, чтоПерейти к useEffect
Функция будет отличаться в каждом рендере, что сделано намеренно. На самом деле именно поэтому мы можем получить последнее значение счетчика в эффекте, не беспокоясь о его истечении. При каждом повторном рендеринге генерируется новый эффект, заменяющий предыдущий. В некотором смысле,эффект больше похож на часть результатов рендеринга- Каждый эффект "принадлежит" конкретному рендеру.
После изменения значения свойства, полученного элементом, элемент не будет уничтожен и перестроен, поэтому компонент класса находится подcomponentDidUpdate
В него необходимо добавить логику обновления. забывают обращаться с ним правильноcomponentDidUpdate
является распространенным источником ошибок в приложениях React. С хуком эффекта он очищает предыдущий эффект перед вызовом нового. Это поведение по умолчанию обеспечивает согласованность и позволяет избежать распространенных ошибок в компонентах класса, которые не обрабатывают логику обновления.
class FriendStatusWithCounter extends React.Component {
// ...
componentDidMount() {
ChatAPI.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange);
}
// 如果没有这里的逻辑,那么当 friend 变化时,我们的组件展示的还是原来的好友状态
componentDidUpdate(prevProps) {
// 取消订阅之前的 friend.id
ChatAPI.unsubscribeFromFriendStatus(prevProps.friend.id, this.handleStatusChange);
// 订阅新的 friend.id
ChatAPI.subscribeToFriendStatus(this.props.friend.id, this.handleStatusChange);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(this.props.friend.id, this.handleStatusChange);
}
// ...
}
Вот как использовать хук
function FriendStatus(props) {
// ...
useEffect(() => {
// ...
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// ...
}
3.4 Эффекты пропуска для оптимизации производительности
В некоторых случаях выполнение очистки или эффектов после каждого рендеринга может вызвать проблемы с производительностью. В компоненте класса мы можемcomponentDidUpdate
Добавьте логику сравнения в prevProps или prevState для решения:
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
Это очень распространенное требование, поэтому оно встроено вuseEffect
в Хук API. Если какое-то конкретное значение не меняется между повторными рендерингами, вы можете указать React пропустить вызов эффекта, передав массив какuseEffect
Второй необязательный параметр может быть:
// 这个时候跟 Vue.js 的 watch 很像
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
注:经过试验,以下类型的值都有效
* 某个 state 值, 如 [count]
* state 的子属性, 如 [obj.count] // const [obj, setObj] = useState({count: 0})
* ref.current, 如 [ref.current.count] // const ref = useRef({})
Если вы хотите выполнить эффект, который запускается только один раз (только когда компонент монтируется и размонтируется), вы можете передать пустой массив[]
как второй параметр. Это сообщает React, что ваш эффект не зависит ни от каких значений в свойствах или состоянии, поэтому его никогда не нужно повторять.
Если вы передаете пустой массив []
, свойства и состояние внутри эффекта всегда будут иметь свои начальные значения.
Кроме того, помните, что React будет ждать, пока браузер закончит рендеринг экрана, прежде чем отложить вызов.useEffect
, что делает дополнительные операции удобными.
3.5 Рекомендации по предотвращению ям
В разделе 3.4 написано, что «Если вы передаете пустой массив []
, свойства и состояние внутри эффекта всегда будут иметь свои начальные значения.", обнаруженный в реальном процессе кодирования, легко добавить по привычкеuseEffect
Второй параметр , но часто забывают добавить зависимости, используемые внутри, в результате чего внутреннее значение функции не соответствует (в уме) ожиданиям. Конечно, эту проблему «React Hook необходимо вручную поддерживать зависимости» можно решить, настроив автоматическое исправление ESLint.
useEffect(() => {
console.log(a); // 这里对 a 的引用是符合预期的
console.log(b);// 这里对 b 的引用值会停留在上次 a 变更时的状态或(如果 a 没变更过)初始化时的状态
}, [a]); // 这里应该是 [a, b],但实际编码过程中很容易漏掉 b
4 useContext
контекст доступа
useContext
получает объект контекста (React.createContext
возвращаемое значение) и возвращает текущее значение контекста. Текущее значение контекста определяется ближайшим компонентом верхнего компонента к текущему компоненту.<MyContext.Provider value={xxx}>
изvalue
опорное решение.
Когда самый верхний компонент компонента является ближайшим<MyContext.Provider>
При обновлении этот хук вызывает повторный рендеринг.
Если повторный рендеринг компонента обходится дорого, вы можете оптимизировать его с помощью мемоизации.
Если вы уже знакомы с контекстным API, прежде чем касаться Hook, должно быть понятно, что useContext(MyContext) эквивалентен компоненту класса вstatic contextType = MyContext
или<MyContext.Consumer>
.
Специальное примечание:
-
useContext(MyContext)
Просто позволяет читать значение контекста и подписываться на изменения в контексте. Вам все еще нужно использовать в верхнем дереве компонентов<MyContext.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>
);
}
5 дополнительных крючков
Некоторые из хуков, описанных ниже, являются вариантами основных хуков из предыдущего раздела, а некоторые используются только в особых случаях.
5.1 useRef
- Получите дескриптор дочернего компонента или узла DOM. Не удается получить ссылку на подкомпонент функции (но компонент функции может использовать
React.forwardRef
чтобы передать ref), он должен быть компонентом класса, поэтому класс нельзя полностью заменить на данный момент - Хранилище для общих данных между циклами рендеринга. состояние также может быть сохранено в циклах рендеринга, но вызовет повторный рендеринг, тогда как ref не вызовет повторный рендеринг.
- Реф.
current
Значение Ref может быть изменено по желанию, но сам объект Ref не является расширяемым свойством.Object.isExtensible(ref) === false
useRef возвращает изменяемый объект ref, чейcurrent
Свойство инициализируется переданным параметром initialValue. Возвращенный объект ref сохраняется на протяжении всего времени существования компонента.
Вы должны быть знакомы с ссылками, основным способом доступа к DOM. Если вы поместите объект ref с<div ref={myRef} />
формы в компонент, независимо от того, как изменится узел, React передаст объект refcurrent
Свойство устанавливается на соответствующий узел DOM.
Однако,useRef()
Сравниватьref
Свойства более полезны. Удобно хранить любое изменяемое значение, подобно тому, как поля экземпляра используются в классе.
Это потому, что он создает обычный объект Javascript. а такжеuseRef()
и построить один{current: ...}
Единственная разница в объектах заключается в том,useRef
будет возвращать один и тот же объект ref при каждом рендеринге.
Помните, что когда содержимое объекта ref изменяется,useRef
и не уведомит вас.изменять current
Свойство не вызывает повторную визуализацию компонента. Если вы хотите запустить некоторый код, когда React привязывает или отвязывает ссылку узла DOM, вам нужно использоватьref callbackреализовать.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
5.1.1 ref callback
React will call the ref callback with the DOM element when the component mounts, and call it with null
when it unmounts.
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
inputEl.current.focus();
};
const refCallback = el => {
console.log('refCallback', {el});
inputEl.current = el;
}
return (
<>
<input ref={refCallback} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
5.2 useMemo
memo()
Ограничивает повторную визуализацию компонента и useMemo()
Это необходимо для ограничения многократного выполнения функции.
useMemo()
а такжеuseEffect()
Логика второго параметра такая же, за исключением того, чтоuseMemo
имеет возвращаемое значение и выполняется перед рендерингом, иuseEffect
выполняется после рендеринга.
Передайте функцию «создать» и массив зависимостей в качестве аргументовuseMemo
, который пересчитывает запомненное значение только при изменении зависимости. Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендеринге.
Помните, проходитеuseMemo
функция будет выполняться во время рендеринга. Пожалуйста, не выполняйте операции без рендеринга внутри этой функции, такие операции, как побочные эффекты, относятсяuseEffect
сферы применения, а неuseMemo
.
Если массив зависимостей не указан,useMemo
Новое значение рассчитывается при каждом рендеринге.
// 这个跟 Vue.js 中的 computed 很像
const double = useMemo(() => count * 2, [count])
5.3 useCallback
Передайте встроенную функцию обратного вызова и массив зависимостей в качестве аргументовuseCallback
, который возвращает памятную версию функции обратного вызова, которая обновляется только при изменении зависимости. Когда вы передаете обратные вызовы для оптимизации и использования справочного равенства, чтобы избежать ненужного рендеринга (например,shouldComponentUpdate
), это будет очень полезно.
useCallback(fn, deps)
эквивалентноuseMemo(() => fn, deps)
.
// 套 memo 后,只要 props 没变就不会重新渲染
// `memo` 是一个 HOC,可以将 `Component` 或 `FunctionComponent` 转换成一个 `PureComponent`
// 本例中,App 内的 count 值变更,不会输出 “Foo render”,没套的话每次 App 的重新渲染都会触发 Foo 重复渲染
const Foo = memo(function Foo(props) {
console.log('Foo render')
// 这里必须显式绑定,在外层绑定不起作用,这个跟 Vue.js 行为不一样
// 还可以写成 {...props} 这样通用性更强
return <div onClick={props.onClick}>Me Foo</div>
})
const App = () => {
const [count, setCount] = useState(0)
// 没套 useCallback 的话,传递的函数句柄每次渲染都会变化,从而导致 Foo 重复渲染
const clickFoo = useCallback(() => console.log('Foo Clicked'), [])
return (
<div>
<button onClick={() => setCount(count + 1)}>Add</button> // 在 DOM 上无需 useCallback
<Foo onClick={clickFoo} /> // 传递给子组件就要套 useCallback
</div>
)
}
5.3.1 Рекомендации по предотвращению ям
useMemo
а такжеuseCallback
Все они представляют собой хуки, ориентированные на оптимизацию производительности, но новичкам легко ошибиться в их использовании: я не вижу, насколько лучше производительность, а код сложнее и нечитаемее (есть несколько слоев кода). код, который утомительно читать), и даже код отличается неожиданным поведением (с высокой вероятностью бытьuseMemo(xxx, dependencies)
серединаdependencies
Пропустил запись зависимостей, в результате чего не было получено последнее значение). Поэтому я лично рекомендую новичкам снова использовать эти два хука, когда они сталкиваются с проблемами производительности, и не использовать их без необходимости.
5.4 useReducer
В некоторых сценарияхuseReducer
было бы лучше, чемuseState
более применимо, напримерЛогика состояния сложна и содержит несколько подзначений., или следующее состояние зависит от предыдущего состояния. и, используяuseReducer
Вы также можете выполнять оптимизацию производительности для компонентов, запускающих глубокие обновления, поскольку вы можетеПередать диспетчеризацию дочерним компонентамвместо функции обратного вызова.
-
Redux: Global state management for user, auth, etc.
-
useReducer: Complex local state where dispatch is often passed to children as well.
-
useState: Simple local state where the setter is seldom passed to children.
I use all of the above.
- Глобальное общее состояние для легкой отладки и обслуживания с использованием Redux
- Простое использование состояния компонента
useState
- Сложное состояние компонента, которое требует нескольких типов операций или когда вам нужно передать сеттеры дочерним компонентам, используйте
useReducer
. Особенно, когда разные подкомпоненты должны выполнять разные операции над сложными состояниями, используйтеdispatch
Это может сделать операционное намерение дочернего компонента более ясным.
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>
</>
);
}
Есть два способа инициализировать состояние
- Один - прямой ввод
useReducer(reducer, initialState)
- Одним из них является динамическое создание при инициализации (отложенная инициализация).
useReducer(reducer, initialArg, init)
You can also create the initial state lazily. To do this, you can pass an init function as the third argument. The initial state will be set to init(initialArg)
.
It lets you extract the logic for calculating the initial state outside the reducer. This is also handy for resetting the state later in response to an action:
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>
</>
);
}
5.1.1 Рекомендации по предотвращению ям
useReducer
также следоватьuseState
Есть аналогичная проблема, сколько бы раз не звонилdispatch({type: 'a', payload: state.a + 1})
, результаты те же. В настоящее время мы можем поддерживать использование обратного вызова путем преобразования редьюсера. Конкретную реализацию см. В примере ниже.
const reducer = (state, action) => {
// 支持类似 dispatch(state => ({type: 'a', payload: state.a + 1})) 的写法
if (typeof action === 'function') {
action = action(state);
}
// 支持类似 dispatch({type: 'a', payload: state => state.a + 1})) 的写法
if (typeof action.payload === 'function') {
action.payload = action.payload(state);
}
console.log({state, action});
if (action.type === 'a') {
return { ...state, a: action.payload };
} else {
return state;
}
}
const App = () => {
const [state, dispatch] = useReducer(reducer, {a: 0})
const handleClick = () => {
dispatch({type: 'a', payload: state.a + 1});
dispatch({type: 'a', payload: state.a + 1}); // 坑
dispatch({type: 'a', payload: s => s.a + 1}); // 避坑
};
useEffect(() => {
console.log('state in useEffect', state);
})
return <div onClick={handleClick}>Dispatch</div>;
}
5.5 useImperativeHandle
useImperativeHandle
позволяет вам использоватьref
При настройке значения экземпляра, предоставляемого родительскому компоненту (обычное приложение пропускает func). Такого императивного кода следует избегать, насколько это возможно.useImperativeHandle
нужно сforwardRef
С использованием:
function FancyInput(props, ref) {
const inputRef = useRef()
useImperativeHandle(ref, () => ({
focus: () => inputRef.current.focus()
}))
return <input ref={inputRef} />;
}
FancyInput = forwardRef(FancyInput);
function Foo () {
const fancyInputRef = useRef(null)
return (
<>
<span onClick={() => fancyInputRef.current.focus()}></span>
<FancyInput ref={fancyInputRef} />
</>
)
}
5.6 useLayoutEffect
It fires synchronously after all DOM mutations. We recommend starting with useEffect first and only trying useLayoutEffect if that causes a problem.
Your code runs immediately after the DOM has been updated, but before the browser has had a chance to paint those changes (the user doesn't actually see the updates until after the browser has repainted).
Его сигнатура функции такая же, какuseEffect
то же самое, но это будет ввсе DOM После изменения синхронизировать (т.е. блокировать)Вызов эффекта. Вы можете использовать его для чтения макета DOM и синхронного повторного рендеринга.до того, как браузер выполнит рисование,useLayoutEffect
Внутреннее расписание обновлений будет обновляться синхронно.
useLayoutEffect
а такжеcomponentDidMount
,componentDidUpdate
Этап вызова тот же.
useLayoutEffect
Он заблокирует основной поток браузера, и все изменения в нем будут отражены при следующем рендеринге. а такжеuseEffect
Основной поток будет остановлен первым, а задача будет добавлена в очередь событий для выполнения. (Просто посмотрите на Task of DevTools/Performance/Main, просто увеличьте масштаб и посмотрите сразу)
Если вы используете рендеринг на стороне сервера...
5.7 useDebugValue
useDebugValue
Метки, которые можно использовать для отображения пользовательских хуков в React DevTools.
В некоторых случаях отображение отформатированных значений может быть дорогостоящей операцией. В этом нет необходимости, если не требуется проверка хука.
следовательно,useDebugValue
Принимает функцию форматирования в качестве необязательного второго аргумента. Эта функция будет вызываться только при проверке хука. Он принимает отладочное значение в качестве аргумента и возвращает отформатированное отображаемое значение.
6 пользовательских хуков
Пока что в React есть два популярных способа разделить логику состояния между компонентами: render props и компоненты более высокого порядка, теперь давайте посмотрим, как Hooks решают ту же проблему, не позволяя вам добавлять компоненты.
Пользовательский хук — это функция, имя которой начинается с «use» и может вызывать другие хуки внутри функции.
В соответствии с компонентами убедитесь, что другие хуки безоговорочно вызываются только поверх пользовательских хуков.
В отличие от компонентов React,Пользовательские хуки не обязательно должны иметь особую идентичность. Мы вольны решать, каковы его параметры и что он должен возвращать.(если нужно).
Пользовательский крючок - это естественное согласие дизайна крючка, а не React характеристики.
Вы можете создавать собственные хуки, которые охватывают множество сценариев, таких как обработка форм, анимация, объявления подписки, таймеры и, возможно, даже больше сценариев, которых мы не ожидали.
Должны ли пользовательские хуки начинаться с «использовать»?
Так и должно быть. Эта конвенция очень важна. Без него React не сможет автоматически проверить, не нарушает ли ваш хук правила хуков, поскольку невозможно определить, содержит ли функция вызов своего внутреннего хука.
Будет ли использование одного и того же хука в двух компонентах иметь общее состояние?
Не будет. Пользовательские хуки — это механизм повторного использования логики состояния (например, настройка подписки и сохранение текущего значения), поэтому каждый раз, когда используется пользовательский хук, все состояния и побочные эффекты в нем полностью изолированы.
import React, { useState, useEffect } from 'react';
// 自定义钩子
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
// 组件
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
// 组件
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
6.1 usePrevious
Получите свойства или состояние предыдущего раунда, учитывая, что это относительно распространенный сценарий использования, вполне вероятно, что React будет иметь встроенный этот хук в будущем.
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
6.2 useForceUpdate
Используется для принудительного обновления компонента, обычно при использовании useRef для управления изменяемым состоянием, но его необходимо повторно отобразить.
function useForceUpdate() {
const [, forceUpdate] = useReducer(v => v + 1, 0);
return forceUpdate;
}
function Demo() {
const counter = useRef(0);
const forceUpdate = useForceUpdate();
const handleClick = () => {
counter.current++;
forceUpdate();
}
return <div onClick={handleClick}>{counter.current}</div>;
}
6.3 Использование сторонних библиотек
Хуки, официально предоставляемые React, очень просты, и многие логики в реальном бизнесе можно использовать повторно. В реальном бою настоятельно рекомендуется использовать стороннюю библиотеку хуков для повышения эффективности. Внутренний проект Byte еще не является открытым исходным кодом, поэтому мы рекомендуем Али в первую очередь.ahooks.js.org/
7 правил крючка
React требует, чтобы использование хуков соответствовало двум правилам, которые ограничены текущей базовой реализацией хуков, и, возможно, в будущем таких правил не будет. Дело не в том, что я не хочу, а в том, что это временно не может быть реализовано.
Включая некоторые из подводных камней, упомянутых выше, все они связаны с текущим дизайном или реализацией Hook. Крючок уже очень хорош, но порога использования нет, или другими словами, до совершенства еще далеко.
Хуки — это просто функции JavaScript, но есть два дополнительных правила их использования.
7.1 Используйте хуки только на верхнем уровне
Не вызывайте хуки в циклах, условных выражениях или вложенных функциях, всегда вызывайте их на верхнем уровне вашей функции React. Следуя этому правилу, вы можете гарантировать, что хук будет выполняться при каждом рендеринге.вызываются в том же порядке. Это позволяет React поддерживать правильное состояние ловушки при нескольких вызовах useState и useEffect.
7.2 Перехватчики вызовов только в функциональных компонентах
Не вызывайте хуки в обычных функциях JavaScript. Ты сможешь:
- Вызов хуков в функциональных компонентах React
- Вызов других хуков в пользовательских хуках
Следование этому правилу гарантирует, что логика состояния компонента четко видна в коде.
7.3 Почему эти два правила
Обратитесь к часто задаваемым вопросам:How does React associate Hook calls with components?
Когда мы можем использовать несколько хуков состояния или хуков эффектов в одном компоненте, React сбрасывает все хуки в массив, а React полагается на порядок, в котором вызываются хуки, чтобы различать каждый хук.
Если мы хотим выполнить эффект условно, мы можем поместить суждение внутри хука:
useEffect(function persistForm() {
// 将条件判断放置在 effect 中
if (name !== '') {
localStorage.setItem('formData', name);
}
});