Fiber异步调和机制
缘起
React虚拟DOM的调和和渲染可以简单粗暴的递归,但是这个过程是同步的,如果需要处理的节点过多,可能会阻塞用户输入和动画播放,布局等造成卡顿,Fiber是16.x引入的新特性,用处是将同步的调和变成异步的。
1. 简单概述
它是React16之后新加入的一种异步VDom调和机制(当前默认没有开启这种异步模式),它把diff过程分解为多个可中断的任务,像一个链表结构,任务之间可以被优先级更高的动画,用户事件打断,让浏览器运行得更浏览畅,不容易出现卡顿现象。
React15时,整个渲染逻辑是同步的,采用递归方式调用组件的生命周期函数和diff算法,React16之后把diff过程分解成多个小的可打断的时间片,中间使用window.requestIdleCallback(chrome支持,其他浏览器需要模拟类似行为)这个实验性的API解决任务调度的问题,让浏览器在空闲时才计算diff并渲染。
用requestIdleCallback
来进行任务调度,它进行任务调度的思想是将任务拆分成多个小任务,requestIdleCallback
里面不断的把小任务拿出来执行,当所有任务都执行完或者超时了就结束本次执行,同时要注册下次执行。
Fibe的思想总结来说,就是把一个大的diff任务分成很多小片,当分配给这个小片的时间用尽的时候,就把JS渲染进程的控制区交出去,让浏览器去检查有没有优先级更高的任务,有就做这个新任务,没有就继续做原来的任务,这种方式被叫做异步渲染(Async Rendering)。
所以用原来VDom这个单一的大对象就不好做暂停和继续操作,所以引入了fiber这个新的可中断可恢复的数据结构,那fiber结构大致是什么样的?
fiber节点对象
,有child
和sibing
属性,指向第一个子节点和相邻的兄弟节点,return
属性指向其父节点,外形上构成了一个fiber树,fiber树本质上是一个链表结构。- 每个fiber(当前fiber可以称为
current
)有一个属性alternate
,开始时指向一个自己的clone体,update
的变化会先更新到alternate
上,当更新完毕,alternate
替换current
。 - fiber tree是根据vDOM tree构造出来的,他们的树结构一模一样,只是节点携带的信息有差异,工作循环中,每次处理一个fiber,处理完可以中断/挂起整个工作循环。
2. 基本原理
React 框架内部的运作可以分为 3 层
- Virtual DOM 层,描述页面长什么样。
- Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等。
- Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative。
Fiber改动最大的当属 Reconciler 层了,React 团队也给它起了个新的名字,叫Fiber Reconciler(调和),之前叫Stack Reconfciler
。
Fiber Reconciler 在执行过程中,会分为 2 个阶段
- 阶段一,生成 Fiber 树,得出需要更新的节点信息。这一步是一个渐进的过程,可以被打断。
- 阶段二,将需要更新的节点批量更新,这个过程不能被打断。
阶段一可被打断,让优先级更高的任务先执行,从框架层面大大降低了页面掉帧的概率,当然被打断时,fiber会记录很多中间状态,会记录当前执行的位置,哪些节点已经完成diff计算了,下一次从哪里开始等。
在render函数中创建的React Element树在第一次渲染的时候会创建一颗结构一模一样的Fiber节点树。不同的React Element类型对应不同的Fiber节点类型。一个React Element的工作就由它对应的Fiber节点来负责。
一个React Element可以对应不止一个Fiber,因为Fiber在update的时候,会从原来的Fiber(我们称为current)clone出一个新的Fiber(我们称为alternate)。两个Fiber diff出的变化(side effect)记录在alternate上,所以一个组件在更新时最多会有两个Fiber与其对应,在更新结束后alternate会取代之前的current的成为新的current节点。
3. Fiber的基本规则
更新任务分成两个阶段,Reconciliation Phase和Commit Phase。
Reconciliation Phase的任务做的事情是,找出要做的更新工作(Diff Fiber Tree),就是一个计算阶段,计算结果可以被缓存,也就可以被打断;
Commmit Phase 需要提交所有更新并渲染,为了防止页面抖动,被设置为不能被打断。
4. 平时编程需要注意些什么?
componentWillMount,
componentWillReceiveProps,shouldComponentUpdate(它没啥大问题,不会产生副作用),
componentWillUpdate
几个生命周期方法不再安全,由于任务执行过程可以被打断,这几个生命周期可能会执行多次,如果它们包含副作用(比如aJax),会有意想不到的bug,React16.3团队提供了一些新的周期函数,建议用新的周期函数做替待 when you have to do something with these life functions。
static getDerivedStateFromProps(props, state) { // 代替componentWillReceiveProps } //新的静态 getDerivedStateFromProps 生命周期方法在组件实例化之后以及重新渲染之前调用。它可以返回一个对象来更新 state,或者返回 null 来表示新的 props 不需要任何 state 的更新。 getSnapshotBeforeUpdate(prevProps, prevState) // 它代替componentWillUpdate。建议如果使用以上方法,尽量用纯函数,避免以后采坑