此篇文章是自己看完大佬们的文章后的一个概括,可能会有不足的地方,欢迎指出一起探讨。
问题:在现有React中,采用的递归的遍历方式,更新过程是同步的。更新任务一旦执行,在更新完所有组件之前不停止。就无法中断,js 线程一直占用主线程,无法及时响应用户操作,导致卡顿。
解决:Fiber架构:一种基于浏览器的单线程调度算法
概念
通过和浏览器合作式调度获取控制权,在reconciliation阶段,按照优先级策略调度任务,分段更新,把渲染更新过程(diff)拆分成多个分片,通过 Diff 算法构建 WokeInProgress Tree更新操作,生成 effectList副作用列表,在commit阶段根据副作用列表更新真实DOM.
流程
1. 设置任务优先级并加入更新队列
setState后,创建 Update任务,为其设置优先级并加入更新队列。
2. 请求浏览器调度
scheduler(调度)通过 requestIdleCallback 请求浏览器调度,浏览器会在帧空闲时执行回调,交出控制权。
对于不支持这个API的浏览器,react会加上pollyfill。
3. reconciliation(可中断):
-
拿到控制权后,react根据优先级策略,决定要处理哪些任务。
-
从根节点开始遍历 Fiber Node,分片执行,通过 Diff 算法构建 WokeInProgress Tree更新操作,生成 effectList副作用列表
- 分片:在 reconciliation 阶段的每个工作循环中,把渲染更新过程(diff)拆分成多个分片,每执行完一个分片,就会检查剩余时间(当前还能占用主线程的时间),如果时间充足就继续执行分片,不足则保存现场,交出控制权,再请求浏览器调度。
- 执行fiber:通过diff比较前后fiber,每个节点更新结束时向上归并 Effect List ,构建 WokeInProgress Tree更新操作。
- 保存现场:记录下一个待处理的工作单元fiber(深度优先遍历得到)。
优先级策略的核心:优先处理高优先级的任务,低优先级的操作可以被高优先级的操作打断。 也就是说中断时正在处理的任务,在恢复时会让位给高优先级任务,原本中断的任务可能会被重用或者重做。 使用队列保证状态更新的一致性,执行所有更新任务来保证视图的最终一致性。
由于 reconciliation 阶段是可中断的,一旦中断之后恢复的时候又会重新执行,所以 reconciliation 阶段的生命周期会被多次调用
reconciliation可能调用的生命周期:
componentWillMount(弃)
componentWillReceiveProps(弃)
getDerivedStateFromProps
shouldComponentUpdate
componentWillUpdate(弃)
render
4. commit(不可中断):
根据 effect的 effectTag进行对应的插入、更新、删除操作。根据tag不同,调用不同的更新方法,更新真实DOM。 workInProgress就完全和DOM保持一致了,为了让当前的fiber-tree和DOM保持一直,react交换了current和workinProgress两个指针
双缓冲:是workInProgress tree构造完毕,得到的就是新的fiber tree,然后喜新厌旧(把current指针指向workInProgress tree,丢掉旧的fiber tree)就好了
第二阶段调用的生命周期:
getSnapshotBeforeUpdate
componentDidMount
componentDidUpdate
componentWillUnmount
合作式调度
由浏览器给我们分配执行时间片(通过requestIdleCallback实现),我们会在约定时间内执行完毕,并将控制权还给浏览器
参考文章