其中,只有
渲染器
会执行渲染视图操作。
对于浏览器环境来说,只有
渲染器
会执行类似
appendChild
、
insertBefore
这样的
DOM
操作。
协调器
如何决定更新的内容呢?
答案是:他会为需要更新的内容对应的
fiber
(可以理解为
虚拟DOM
)打上标记。
这些被打标记的
fiber
会形成一条链表
effectList
。
渲染器
会遍历
effectList
,执行标记对应的操作。
比如
Placement
标记对应插入
DOM
比如
Update
标记对应更新
DOM
属性
useEffect
也遵循同样的工作原理:
触发更新时,
FunctionComponent
被执行,执行到
useEffect
时会判断他的第二个参数
deps
是否有变化。
如果
deps
变化,则
useEffect
对应
FunctionComponent
的
fiber
会被打上
Passive
(即:需要执行useEffect)的标记。
在
渲染器
中,遍历
effectList
过程中遍历到该
fiber
时,发现
Passive
标记,则依次执行该
useEffect
的
destroy
(即
useEffect
回调函数的返回值函数)与
create
(即
useEffect
回调函数)。
其中,前两步发生在
协调器
中。
所以,
effectList
构建的顺序就是
useEffect
的执行顺序。
effectList
协调器
的工作流程是使用
遍历
实现的
递归
。所以可以分为
递
与
归
两个阶段。
我们知道,
递
是从根节点向下一直到叶子节点,
归
是从叶子节点一路向上到根节点。
effectList
的构建发生在
归
阶段。所以,
effectList
的顺序也是从叶子节点一路向上。
useEffect
对应
fiber
作为
effectList
中的一个节点,他的调用逻辑也遵循
归
的流程。
现在,我们有充足的知识回答第一个问题:
由于
归
阶段是从
Child
到
Parent
到
App
,所以相应
effectList
也是同样的顺序。
所以
useEffect
回调函数执行也是同样的顺序。
不要用生命周期钩子类比hook
我们在初学
hook
时,会用
ClassComponent
的生命周期钩子类比
hook
的执行时机。
即使官网也是这样教学的。
但是,从上文我们已经知道,
React
的执行遵循:
调度 -- 协调 -- 渲染
渲染相关工作原理是按照:
构建effectList -- 遍历effectList执行对应操作
整个过程都和生命周期钩子
没有关系。
事实上生命周期钩子
只是附着在这一流程上的钩子函数。
所以,更好的方式是从React
运行流程来理解useEffect
的执行时机。
按照流程,effectList
会在渲染器
中被处理。
对于useEffect
来说,遍历effectList
时,会找到的所有包含Passive
标记的fiber
。
依次执行对应useEffect
的destroy
。
所有destroy
执行完后,再依次执行所有create
。
整个过程是在页面渲染后异步执行的。
回答第二个问题:
如果useEffect
的deps
为[]
,由于deps
不会改变,对应fiber
只会在mount
时被标记Passive
。
这点是类似componentDidMount
的。
但是,处理Passive
effect
是在渲染完成后异步执行,而componentDidMount
是在渲染完成后同步执行,所以他们是不同的。
useEffect与useLayoutEffect
与componentDidMount
更类似的是useLayoutEffect
,他会在渲染完成后同步执行。
这里提供个在线Demo,你可以将Demo
中的useLayoutEffect
替换为useEffect
,看看他们的区别。
通过本文,我们了解了useEffect
的完整执行过程。
本系列文章接下来会继续以实例 + 源码的方式,解读业务中经常使用的React
特性。