Во-первых, в этой статье не объясняется основное использование хуков, эта статья врезана в сцену «странного» в хуках (в реальной логике) «закрытия ловушки», пытаясь объяснить причинно-следственную связь, стоящую за этим. В то же время во многих шансах реагировать хуки вы также можете увидетьuseRef
цифра, так зачем использоватьuseRef
Сможете ли вы выбраться из этой «ловушки закрытия»? Я хочу разобраться в этих вопросах, что значительно улучшит мое понимание хуков реакции.
Реактивные хуки были востребованы многими разработчиками, как только они появились. Возможно, встреча с «ловушкой закрытия» при использовании реагирующих хуков — это то, с чем сталкивался каждый разработчик во время разработки. Другие стабильны, как старый пес, и мгновенно определяют, где проблема. является.
(Все следующие демонстрационные демонстрации реакции относятся к версии 16.8.3)
Вы, должно быть, столкнулись со следующим сценарием:
function App(){
const [count, setCount] = useState(1);
useEffect(()=>{
setInterval(()=>{
console.log(count)
}, 1000)
}, [])
}
печатать в этом таймереcount
Значение обнаружит, что независимо от других мест в этом компонентеsetCount
Будуcount
Установите любое значение или укажите, сколько раз выводить 1. Есть ли ощущение, что даже после тысячи плаваний я все еще помню, как ты выглядел? ххх... Далее я постараюсь объяснить, что я понял, почему это происходит, и расскажу о некоторых других особенностях хуков. Если есть ошибка, я надеюсь, что мои одноклассники смогут спасти детей, и не дадут мне жить с неправильным познанием. . .
1. Знакомый сценарий закрытия
Начнем со сцены, знакомой всем.
for ( var i=0; i<5; i++ ) {
setTimeout(()=>{
console.log(i)
}, 0)
}
Если подумать о ребенке, то в тот год, когда я только что закончила школу, этот вопрос все еще был популярным вопросом на собеседованиях. И сейчас...
Не буду говорить, почему в итоге печатаются все 5. Просто опубликуйте код, который печатает 0...4, используя замыкание:
for ( var i=0; i<5; i++ ) {
(function(i){
setTimeout(()=>{
console.log(i)
}, 0)
})(i)
}
Этот принцип на самом деле заключается в использовании замыкания.Функция обратного вызова таймера относится к переменной, определенной в функции немедленного выполнения, формируя замыкание для сохранения значения i при выполнении функции немедленного выполнения, а функция обратного вызова асинхронного timer печатается как мы хотим значение по порядку.
фактически,useEffect
Причина, по которой сцена точно такая же, как и эта,useEffect
Появление сцены ловушки замыкания является процессом обновления компонента реакции иuseEffect
естественным следствием реализации.
2 Разговор о принципе хуков и понимании причин «замыкания ловушки» useEffect.
На самом деле, я действительно не хочу привлекать принцип реагирования в процессе написания этой статьи, потому что это действительно слишком цело (на самом деле, главная причина - это овощи, и я просто освоил все это), вы Приходится понимать это примерно в процессе, вы должны понимать некоторые важные моменты, которые поддерживают это примерно.
Во-первых, вы, возможно, слышали об архитектуре React Fiber, фактически узел Fiber соответствует компоненту. дляclassComponent
С точки зрения естьstate
вполне нормальная вещь, объект Fiber имеетmemoizedState
для хранения компонентовstate
. хорошо, теперь посмотрим, на какие хуки нацеленыFunctionComponnet
. Что бы ни делал разработчик, объект может иметь только одинstate
собственность илиmemoizedState
свойства, однако, кто знал, что прекрасные застройщики будут вFunctionComponent
Сколько написано вuseState
,useEffect
Итак, React использует структуру данных, такую как связанный список, для храненияFunctionComponent
крючки внутри. Например:
function App(){
const [count, setCount] = useState(1)
const [name, setName] = useState('chechengyi')
useEffect(()=>{
}, [])
const text = useMemo(()=>{
return 'ddd'
}, [])
}
При первом рендеринге компонента для каждого хука создается объект.
type Hook = {
memoizedState: any,
baseState: any,
baseUpdate: Update<any, any> | null,
queue: UpdateQueue<any, any> | null,
next: Hook | null,
};
В итоге формируется связанный список.
этого объектаmemoizedState
Свойство используется для хранения компонента после последнего обновленияstate
,next
Несомненно указывает на следующий объект ловушки. В процессе обновления компонента порядок выполнения функций хуков не меняется, и соответствующие хуки можно получить по этому связанному списку.Hook
Объекты, функциональные компоненты имеют возможность констатировать таким образом. В настоящее время конкретная реализация определенно намного сложнее, чем эти три слова.
Итак, есть идеи, почему вы не можете написать хуки в операторе if else? Потому что это может привести к неупорядоченному порядку, в результате чего текущие хуки не получат соответствующие объекты хуков.
useEffect
Получил два параметра, функцию обратного вызова и массив. Внутри массиваuseEffect
Зависимость, когда [] , функция обратного вызова будет выполняться только один раз, когда компонент отображается в первый раз. Если есть зависимости от других элементов, реакция определит, изменились ли зависимости, и если да, то будет выполнена функция обратного вызова. Вернемся к исходной сцене:
function App(){
const [count, setCount] = useState(1);
useEffect(()=>{
setInterval(()=>{
console.log(count)
}, 1000)
}, [])
function click(){ setCount(2) }
}
Ну начните представлять, первый раз компонент рендерится и выполняетсяApp()
,воплощать в жизньuseState
Начальное состояние установлено на 1, поэтому в это времяcount
1. затем казненuseEffect
, функция обратного вызова выполняется, и таймер настроен на печать каждые 1 с.count
.
Тогда представьте, еслиclick
функция запущена, вызовsetCount(2)
Это определенно вызовет обновление реакции, а также будет выполнено при обновлении текущего компонента.App()
, связанный список, упомянутый ранее, был сформирован, в это времяuseState
БудуHook
Состояние, сохраненное на объекте, установлено на 2, тогда в это времяcount
Тоже за 2. затем выполнениеuseEffect
Поскольку массив зависимостей является пустым массивом, обратный вызов в это время выполняться не будет.
хорошо, этот таймер не участвует в процессе этого обновления, этот таймер все еще настаивает, молча, печатая каждую 1 секундуcount
. Обратите внимание, что печатается здесьcount
, когда компонент впервые отрисовываетсяApp()
времяcount
,count
имеет значение 1,Поскольку на него ссылается функция обратного вызова таймера, замыкание всегда сохраняется..
2 Действительно ли необходимо записывать значение в зависимый массив, чтобы получить свежее значение?
Вроде бы привычно думать, что только записав нужные нам значения в массив зависимостей, мы сможем получить самое свежее значение в процессе обновления. Итак, взгляните на эту сцену:
function App() {
return <Demo1 />
}
function Demo1(){
const [num1, setNum1] = useState(1)
const [num2, setNum2] = useState(10)
const text = useMemo(()=>{
return `num1: ${num1} | num2:${num2}`
}, [num2])
function handClick(){
setNum1(2)
setNum2(20)
}
return (
<div>
{text}
<div><button onClick={handClick}>click!</button></div>
</div>
)
}
text
ЯвляетсяuseMemo
, в его массиве зависимостей есть только num2, но нет num1, но оба состояния используются одновременно. При нажатии кнопки значения num1 и num2 меняются. Итак, можете ли вы получить самое свежее значение num1 в тексте, в котором только указано, что оно зависит от num2?
если вы установилиreact
Плагин eslint, здесь может выдать вам ошибку, потому что в тексте вы использовали num1, не добавляя его в массив зависимостей. Но выполнение этого кода обнаружит, что самое свежее значение num1 может быть получено нормально.
Если вы понимаете проблему «ловушки закрытия», упомянутую в первом пункте выше, вы определенно можете понять эту проблему.
Почему, повторюсь, смысл существования этого массива зависимостей в том, чтобы судить, реагироватьЭто обновление, нужно ли выполнять функцию обратного вызова, здесь зависит от num2, а num2 изменилось. Функция обратного вызова будет выполняться естественным образом, а замыкание, сформированное в это время, ссылается на последние num1 и num2, поэтому естественно получать свежие значения. Суть проблемы заключается в тайминге выполнения функции обратного вызова, замыкание похоже на камеру, которая сохраняет значения в момент выполнения функции обратного вызова. Я думаю, что функция обратного вызова таймера, упомянутая ранее, похожа на человека, который путешествовал из 1000 лет назад в современность.Хотя он пришел в современность, кровь и волосы на его теле были 1000 лет назад.
3 Зачем использовать useRef, чтобы каждый раз получать свежие значения?
На просторечии: потому что инициализированныйuseRef
После выполнения он возвращает тот же объект. Написав это, малыш не может не вспомнить сцену, когда он только учил js, держал в руках красную книгу сокровищ и жевал:
var A = {name: 'chechengyi'}
var B = A
B.name = 'baobao'
console.log(A.name) // baobao
Да, это самая фундаментальная причина создания этой сцены.
То есть во время каждого рендеринга компонента. Напримерref = useRef()
Все возвращаемые объекты являются одним и тем же объектом, генерируемым каждый раз при обновлении компонента.ref
Все указывают на одно и то же пространство памяти, поэтому, конечно, вы можете каждый раз получать самые свежие значения. Инуяша видел ручку? Древний колодец связывает современный мир с периодом Воюющих царств 500 лет назад, и этот же объект также связывает эти переменные, хранящиеся в разных таймингах закрытия.
Может быть проще понять на примере:
/* 将这些相关的变量写在函数外 以模拟react hooks对应的对象 */
let isC = false
let isInit = true; // 模拟组件第一次加载
let ref = {
current: null
}
function useEffect(cb){
// 这里用来模拟 useEffect 依赖为 [] 的时候只执行一次。
if (isC) return
isC = true
cb()
}
function useRef(value){
// 组件是第一次加载的话设置值 否则直接返回对象
if ( isInit ) {
ref.current = value
isInit = false
}
return ref
}
function App(){
let ref_ = useRef(1)
ref_.current++
useEffect(()=>{
setInterval(()=>{
console.log(ref.current) // 3
}, 2000)
})
}
// 连续执行两次 第一次组件加载 第二次组件更新
App()
App()
Итак, сделайте разумное предположение. Пока мы можем гарантировать, что каждый раз, когда компонент обновляется,useState
Если он возвращает тот же объект? Можем ли мы также обойти сценарий ловушки закрытия? Попробуйте.
function App() {
// return <Demo1 />
return <Demo2 />
}
function Demo2(){
const [obj, setObj] = useState({name: 'chechengyi'})
useEffect(()=>{
setInterval(()=>{
console.log(obj)
}, 2000)
}, [])
function handClick(){
setObj((prevState)=> {
var nowObj = Object.assign(prevState, {
name: 'baobao',
age: 24
})
console.log(nowObj == prevState)
return nowObj
})
}
return (
<div>
<div>
<span>name: {obj.name} | age: {obj.age}</span>
<div><button onClick={handClick}>click!</button></div>
</div>
</div>
)
}
Просто поговорите об этом коде, выполняяsetObj
При передаче функции. Мне не нужно больше говорить об этом использовании? потомObject.assign
Возвращается первый переданный объект. Короче говоря, тот же объект возвращается, когда он установлен.
Выполните этот код и обнаружите, что после того, как кнопка действительно нажата, значение, напечатанное таймером, также становится:
{
name: 'baobao',
age: 24
}
4 готово
Это конец полного текста реагирующих хуков через «ловушку закрытия». В любом случае, после написания этой статьи, детка, мое понимание хуков стало глубже, чем раньше. Надеюсь, это поможет другим, у кого есть те же сомнения, что и у меня раньше. Если это поможет вам, обещайте мне, пожалуйста, не скупитесь на похвалу.