本文将以react 18.1.0进行讲解
相信用过react的同学对useEffect应该十分熟悉, useEffect和useState可以说是我们在日常开发中最常用的hook。工欲善其事,必先利其器,在react中useEffect就是我们的武器,因此了解它的运行原理对我们的日常开发是十分有必要的。先一起来看下下面的例子:建议你先暂停几秒钟,思考自己的答案。实例在线地址:codesandbox.io/s/spring-ha...
接下来,我们来验证你的猜想是否正确。react首次渲染,执行结果如下。
先说几个前置小知识, react的整个流程主要有render阶段、commit阶段。render阶段主要生成我们的整个fiber树,并对其完成一些标记,例如插入、删除、更新、副作用(例如useEffect)等。commit阶段主要是和宿主环境进行交互,对于我们前端来说,宿主环境通常是浏览器。useEffect函数的执行,通常是在commit阶段完成之后执行的。
fiber节点中的几个重要字段
return
(父节点)、child
(父节点的第一个子节点)、slibing
(兄弟节点)、flags
(标记各种副作用)、subtreeFlags
(所有子节点标记的flags的集合)。updateQueue
中lastEffect存储useEffect的相关参数, create为useEffect的第一个参数因此它是一个函数,deps为useEffect的第二个参数因此它是一个数组或null。
Child1组件的fiber节点示例。
目前我们整个fiber的结构如下:
首次运行时,所有包含useEffect的函数组件对应的fiber节点都会被标记副作用对应字段为flags
,同时会向父节点冒泡作用在subtreeFlags
字段。
好,言归正传,我们来分析上面例子的运行结果。 在首次渲染时候,我们主要关注 commitPassiveMountEffects
函数,commitPassiveMountEffects
中finishedWork是FiberRootNode的current属性,也就是HostRootFiber。也许你对这些名词还很陌生,没关系根据上面的fiber结构示意图,我们知道它目前在我们整个fiber架构中的位置即可。
根据上述代码我们可以知道react是深度遍历优先。接下来我们将一步一步进行分析:
在遍历过程中nextEffect的初始值为HostFiber。后面经过迭代依次为HostFiber-> 满足条件b1 -> App -> 满足条件b1 ------> Childb1 ------>.....满足条件b1 ------------> child1(text节点) 此时child1(text节点)已经没有child节点,因此进入commitPassiveMountEffects_complete
方法。
在commitPassiveMountEffects_complete
中nextEffect的初始值为child1(text节点)对应的fiber节点。单纯的文本节点的flags是不会标记 Passive
的,只有函数组件才会标记。因此会进入逻辑3,即nextEffect此时为Fragment。此时我们会回到commitPassiveMountEffects_begin
方法中,nextEffect的变化为Fragment->满足条件b1-> Chlid2 -------> 满足条件b1 ------> div(id为child2)------> commitPassiveMountEffects_complete
方法 ------> 满足条件4------> Child2------> 满足条件1------> 进入 commitPassiveMountOnFiber
方法, 在commitPassiveMountOnFiber方法中,我们执行Child2中的useEffect的第一个参数即create,此时控制台会打印出 child2
。
commitPassiveMountOnFiber
执行完成后,会回到commitPassiveMountEffects_complete
中继续执行,此时nextEffect为Child2------> 满足条件3 ------> Child6------> 进入commitPassiveMountEffects_begin
------> 满足条件b1 ------> div(id为child6)------>满足条件b2进入commitPassiveMountEffects_complete
--------->满足条件4------> Child6--------->满足条件1------------>进入 commitPassiveMountOnFiber
方法执行Child6中的useEffect的第一个参数即create,此时控制台会打印出 child6
------> 回到commitPassiveMountEffects_complete
函数------>满足条件4------> Fragment ------> ...满足条件4------> Child1 ------>满足条件1打印child1
------> 满足条件3------> Child3回到commitPassiveMountEffects_begin
方法------>满足条件b1------>div(id为child3)------> 满足条件b1------>child3(text节点)------>满足条件b2------>进入commitPassiveMountEffects_complete
------>满足条件3------> Child4------> 回到commitPassiveMountEffects_begin
------> ...满足条件b1 ------> Child4(text节点) ------> 满足条件b2 ------>commitPassiveMountEffects_complete
------> 满足条件3------>Child5------> 回到commitPassiveMountEffects_begin
------> 满足条件b1------> div(id为child5)------> 满足条件b2------>commitPassiveMountEffects_complete
------> 满足条件4------> Child5------>满足条件1打印child5------>满足条件4------> div(id为child4)------> 满足条件4------>Child4------> 满足条件1打印child4------> 满足条件4------>div(id为child3)------> 满足条件4------> Child3------>满足条件1打印child3------> 满足条件4------> App------>满足条件1打印app------>满足条件4------>HostRoot------> 满足条件3终止。
到此我们所有的effect都已经执行完毕了。打印顺序分别为child2、child6、child1、child5、child4、child3、app。