react更新流程
我们了解了 react 的Scheduler-Reconciler-Renderer
架构体系
Scheduler
负责任务的优先级调度Reconciler
工作的工作阶段被称为render
阶段。因为在该阶段会调用render
方法Renderer
工作的阶段被称为commit
阶段。commit
阶段会把render
阶段提交的信息渲染到页面上。
前面已经介绍了render阶段
和commit阶段
。render阶段
完成后会进入commit阶段
,而render阶段
之前就是触发状态更新阶段
。
render 阶段前阶段
触发状态更新
在 react 中,有以下方法可以触发状态更新:
- this.setState
- this.focusUpdate
- ReactDOM.render
- useState
- useReducer
每次触发状态更新都会走一遍render阶段前阶段->render阶段->commit阶段
这个流程。
创建 Update 对象
在react
中,有多种触发状态更新的方法,他们是如何保持同一套状态更新机制呢?
每次状态更新
都会创建一个保存更新状态相关内容的对象,我们叫他Update
。在render阶段
会根据Update
计算新的state
。
从 fiber 到 root,得到 rootFiber
这个阶段,触发状态更新的fiber
上已经包含了Update
对象。
我们知道,render阶段
是从rootFiber
开始向下遍历。那么如何从触发状态更新的fiber
得到rootFiber
呢?
答案是:调用markUpdateLaneFromFiberToRoot
这个方法的工作:从触发状态更新的fiber
一直向上遍历到rootFiber
,得到rootFiber
,并返回rootFiber
调度更新
现在我们拥有了一个rootFiber
,该rootFiber
对应的fiber树
中的某个fiber节点
包含一个Update
。接下来通知Scheduler
根据更新的优先级,决定以同步还是异步的方式调度本次更新。
调用的方法是ensureRootIsScheduled
。
这个方法会根据优先级
调度回调函数执行,这里调度的回调函数为:
1 | performSyncWorkOnRoot.bind(null, root); // 同步的回调函数 |
render 阶段
同步或异步调度本次更新,根据 rootFiber 得到 fiber 树
commit 阶段
副作用对应的DOM操作
在 commit 阶段执行。
执行 DOM 操作前:
- 处理
DOM节点
渲染后的autoFocus/blur
逻辑 - 会调用
getSnapshotBeforeUpdate
,能在操作 DOM 前捕获 DOM 信息(如滚动位置) - 异步调度
useEffect
执行 DOM 操作:
- 通过一次插入 DOM 操作将整颗
DOM树
插入页面。 - 执行
useEffect、useLayoutEffect
销毁函数 - 会调用
componentWillUnmount
- 解绑 ref
执行 DOM 操作后:
- 会调用
useLayoutEffect
的回调函数
- 调度
useEffect
,在Layout阶段
完成后再异步执行useEffect
的回调函数
- 调用
this.setState
的第二个参数回调函数
useLayoutEffect
从上一次更新的销毁函数
调用到本次更新的回调函数
调用是同步执行的。
而useEffect
则需要先调用,在Layout阶段
完成后再异步执行。