为什么会无限重新渲染?
代码如下:
import React, { useState, useEffect } from 'react';
function EffectTest() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(count);
setTimeout(() => {
setCount(1);
setCount(0);
});
});
return (
<div>
<span>{count}</span>
</div>
)
}
export default EffectTest;
控制台:0,1,0,1,0,1......(无限循环)
求大佬解答,感激不尽。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
(1)问题描述: 以下代码为什么会打印两个
0
和两个1
由于react真正在运行时,真正的打印的结果是一个
0
,和两个1
,前面会多打印出一个零应该是和hot reladong
机制有关,实际上直接在浏览器中引入umd版本是不会多打印一个零的,所以我们现在的问题就是 为什么以上代码会打印一个零和两个一(2)需要的前置知识
要理解接下来的内容,需要你至少对react的原理已经有了一定的了解,如果还不知道的话可以去通读一下 react技术揭秘
(3)更新终止
更新何时会终止
查看两颗树的lanes
如果lanes都为NoLanes则比较前后的状态,如果相同就直接返回
(4)相关描述的解释(接下来分析代码运行会用到)
我把每次渲染在图中分成了三个阶段
NoLanes
,current树上的lanes信息会被保留1
, 没有值(也就是NoLanes
)记为0
(5)更新流程(在此过程中我们会记录两个fiber树的变化,两颗fiber树中Foo组件的lanes和setTimeout派发的任务的任务队列)
0
渲染到页面,然渲染完成后,两棵树交换,并在执行Foo Component的时候setTimout将setNum(1)
推入任务队列(渲染后的workInProgress树中的Foo组件对应的fiber节点的lanes
一定为NoLanes
也就是0
,原因通过render阶段的所有workInPorgress节点的lanes都会被赋值为NoLanes
setNum(1)
并执行,此时的队列为空,虽然此时的两棵树的lanes都为NoLanes
但是前后状态并不相同所以达不到终止条件(详情看(3)终止条件),会继续调度该更新,渲染后会将两颗fiber树的lanes设为有值,然后workInProgress树中的lanes又会被置为NoLanes
,最有交换两棵树(详细看(4)相关解释),并且由于又执行了一次Foo Component所以在此向任务队列中推进去一个setNum(1)
3.队列中任然不为空,再次出队
setNum(1)
并且执行,在dispatchAction
中由于此时workInProgress树中的lanes不为NoLanes
所以即使前后的状态并没有改变,也并没有触发更新的终止条件所以会继续进行接下来的render和commit阶段,所以又再次执行了一次Foo Component,但是这次执行Foo Componet有所不同,由于Foo Component的父组件HostRoot没有更新所以Foo Component更新前后的props没有变化,且在执行到组件内部useState的时候react发现前后的状态并没有改变所以不会将
didReceiveUpdate
置为true, 然后didReceiveUpdate
的值将一直保持为false 所以会执行Foo Component的bailoutHooks
逻辑将current Fiber树中Foo Component的lanes置为NoLanes
,所以完成render阶段后两颗树中的Foo Component的lanes都为NoLanes
可以发现此时两颗树的lanes都为
NoLanes
了,不过由于再次执行了Function Component setTimeout又再次向任务队列中推入了一个setNum(1)
4.任务队列中仍然不为空,出队
setNum(1)
执行,在dispatchAction
中发现两棵树的lanes都为NoLanes
且前后的状态都一样,直接return
不会再调度该次任务,到此整个更新流程就完整的结束了(6)打印信息分析
根据(5)中的更新流程我们可以分析一下打印信息其中
(5)-1中初次渲染时,num与初始值相等也就是
0
然后被打印出来(5)-2 dispatchAction触发新的更新,并且action中的
1
替代了原来的0
此次执行Foo Component是 num为1
然后被打印出来(5)-3中没有触发终止条件又执行了一次 Foo Component但是此次的 num并没有变化还是
1
,然后被打印出来(5)-4中触发终止条件,直接退出
(7)更多
在(5)-3中虽然前后state都没有变化但是还是进行了render和commit阶段,会不会有性能浪费,实际上在(5)-3中执行了Foo Component的
bailout
逻辑后此时出触发该更新的fiber节点,也就是Foo Component本身的lanes
也会被设置为NoLanes
所以本次更新并没有实质性的更新所以在commit阶段是不用进行commit工作beforeMutation -> scheduleEffect -> layout -> effect执行 -> dispatchAction -> beforeMutation 永远无法停下,而加上dependenciesArr react发现dependencies没有变所以会在dispatchAction之前停止,还有一个细节是setCount是放在settimeout中的不在BatchedExecuteContext中,不然是只会参考最有一个setCount的值
为useEffect加上dependenciesArr
因为组件一直在重新渲染。
你可以观察我写的 demo。