Renderer
工作的阶段被称为commit
阶段。commit
阶段可以分为三个子阶段:
mutation阶段(执行
DOM
操作)layout阶段(执行
DOM
操作后)
本节我们看看before mutation阶段
(执行DOM
操作前)都做了什么。
before mutation阶段
的代码很短,整个过程就是遍历effectList
并调用commitBeforeMutationEffects
函数处理。
我们重点关注beforeMutation
阶段的主函数commitBeforeMutationEffects
做了什么。
大体代码逻辑:
整体可以分为三部分:
处理
DOM节点
渲染/删除后的autoFocus
、blur
逻辑。调用
getSnapshotBeforeUpdate
生命周期钩子。调度
useEffect
。
commitBeforeMutationEffectOnFiber
是commitBeforeMutationLifeCycles
的别名。
在该方法内会调用getSnapshotBeforeUpdate
。
从React
v16开始,componentWillXXX
钩子前增加了UNSAFE_
前缀。
究其原因,是因为Stack Reconciler
重构为Fiber Reconciler
后,render阶段
的任务可能中断/重新开始,对应的组件在render阶段
的生命周期钩子(即)可能触发多次。
这种行为和React
v15不一致,所以标记为UNSAFE_
。
为此,React
提供了替代的生命周期钩子getSnapshotBeforeUpdate
。
我们可以看见,getSnapshotBeforeUpdate
是在commit阶段
内的before mutation阶段
调用的,由于commit阶段
是同步的,所以不会遇到多次调用的问题。
在这几行代码内,scheduleCallback
方法由Scheduler
模块提供,用于以某个优先级异步调度一个回调函数。
在此处,被异步调度的回调函数就是触发useEffect
的方法flushPassiveEffects
。
我们接下来讨论useEffect
如何被异步调度,以及为什么要异步(而不是同步)调度。
在flushPassiveEffects
方法内部会从全局变量rootWithPendingPassiveEffects
获取effectList
。
在completeWork一节我们讲到,effectList
中保存了需要执行副作用的Fiber节点
。其中副作用包括
- 插入
DOM节点
(Placement) - 删除
DOM节点
(Deletion)
除此外,当一个FunctionComponent
含有useEffect
或useLayoutEffect
,他对应的Fiber节点
也会被赋值effectTag
。
在flushPassiveEffects
方法内部会遍历rootWithPendingPassiveEffects
(即effectList
)执行effect
回调函数。
如果在此时直接执行,rootWithPendingPassiveEffects === null
。
那么rootWithPendingPassiveEffects
会在何时赋值呢?
在上一节layout之后
的代码片段中会根据rootDoesHavePassiveEffects === true?
决定是否赋值rootWithPendingPassiveEffects
。
所以整个useEffect
异步调用分为三步:
before mutation阶段
在scheduleCallback
中调度flushPassiveEffects
layout阶段
之后将effectList
赋值给rootWithPendingPassiveEffects
scheduleCallback
触发flushPassiveEffects
,flushPassiveEffects
内部遍历rootWithPendingPassiveEffects
为什么需要异步调用
摘录自React
文档effect 的执行时机 (opens new window):
可见,useEffect
异步执行的原因主要是防止同步执行时阻塞浏览器渲染。
经过本节学习,我们知道了在before mutation阶段
,会遍历effectList
,依次执行:
处理
DOM节点
渲染/删除后的autoFocus
、blur
逻辑