React在v16推出了很多让我很惊喜的新特性:memo,lazy,Suspense,Hook等当然,其中最让我惊奇的是Fiber和Hook,本文主要讲一下Fiber,它是对React diff算法的重构,是一种基于浏览器的单线程调度算法。
先理解v16以前的渲染顺序
假如有A,B,C,D组件,层级结构为:
挂载阶段A,B,C,D的生命周期渲染顺序:
以render()函数为分界线。从顶层组件开始,一直往下,直至最底层子组件。然后再往上。
组件update阶段同理。
我们知道React 通过自顶向下的递归比对新旧virtual dom来渲染或更新组件的,这里我们看到这个过程是同步的。就是当一次更新或者一次加载开始以后,对比和渲染是一口气完成的。虽然React已经对这个diff算法做了很多优化,但是如果组件层级比较深,相应的堆栈也会很深,浏览器中的渲染引擎是单线程的,除了网络操作,几乎所有的操作都在这个单线程中执行,此时如果主线程上用户交互、动画等周期性任务无法立即得到处理,影响体验。
Fiberl
我们知道React 框架内部的运作可以分为 3 层:
- Virtual DOM 层,描述页面长什么样
- Reconciler 层,负责调用组件生命周期方法,进行 Diff 运算等
- Renderer 层,根据不同的平台,渲染出相应的页面,比较常见的是 ReactDOM 和 ReactNative
Fiberl将 recocilation拆分成无数个小任务的算法,随时能够停止,恢复,每次执行完一个小任务后就看看有没有优先级比较高的任务,有的话就去执行优先级比较高的任务,没有就继续执行下一个分片任务,这样能避免主线程长时间阻塞,使得渲染更加流畅。停止恢复的时机取决于当前的一帧(16ms)内,还有没有足够的时间允许计算。
任务的优先级有六种:
- synchronous,与之前的Stack Reconciler操作一样,同步执行
- task,在next tick之前执行
- animation,下一帧之前执行
- high,在不久的将来立即执行
- low,稍微延迟执行也没关系
- offscreen,下一次render时或scroll时才执行
原理:从Stack Reconciler到Fiber Reconciler,源码层面其实就是干了一件递归改循环的事情。Fiber实现了自己的组件调用栈,它以链表的形式遍历组件树,可以灵活的暂停、继续和丢弃执行的任务。实现方式是使用了浏览器的requestIdleCallback这一 API。
问题:一个更新任务还没有完成,就被另一个更高优先级的更新过程打断了怎么办?
这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
React Fiber更新过程分为两个阶段(Phase):
- Reconciliation Phase(render前的生命周期--React Fiber会找出需要更新哪些DOM,这个阶段是可以被打断的)
- Commit Phase(render后的生命周期--DOM更新,绝不会被打断)
这就对我们使用React的生命周期函数产生一些影响:
componentWillMount componentWillReceiveProps componentWillUpdate 几个生命周期方法不再安全,由于任务执行过程可以被打断,这几个生命周期可能会执行多次,如果它们包含副作用(比如AJax),会有意想不到的bug。React团队提供了替换的生命周期方法。建议如果使用以上方法,尽量用纯函数,避免以后采坑。