React 16采用新的Fiber架构对React进行完全重写,同时保持向后兼容。
动机:concurrent rendering
concurrent rendering 又叫 async rendering,主要包含2个特性:
time slicing(分片)
- 为了让浏览器保持60fps,因此渲染一帧需要在16.67ms内完成,否则会造成“卡顿”
- time slicing将渲染工作切分,从而保证JavaScript的执行不会造成卡顿
- 另一个功能是,将渲染工作按重要性来排序,提高时间敏感(time-sensitive)渲染的优先级(比如text input)
suspense
- 让任何一个组件能够暂停渲染,等待数据的获取(比如懒加载组件、比如网络请求数据)
这两个特性的关键前提是:React的渲染能够被中止(interrupt)、恢复。
这就是为什么我们需要fiber架构了。
背景:JavaScript的执行模型:call stack
首先,我们先解释,为什么过去的架构无法支持渲染中止。
JavaScript原生的执行模型:通过调用栈来管理函数执行状态。
其中每个栈帧表示一个工作单元(a unit of work),存储了函数调用的返回指针、当前函数、调用参数、局部变量等信息。
因为JavaScript的执行栈是由引擎管理的,执行栈一旦开始,就会一直执行,直到执行栈清空。无法按需中止。
这与React有什么关系呢?React将视图看做函数调用的结果:
View = Component(Data)
Component会递归调用其他的Component。页面复杂的话,这个调用栈会很深,导致UI变卡。
在React Fiber之前,React的渲染就是使用原生执行栈来管理组件树的递归渲染。这意味着,整颗组件树的渲染必须一次性完成,工作无法被分片。
因此,react需要另一种可控的执行模型,让react来管理工作的调度。
React Fiber架构:可控的“调用栈”
React Fiber架构就是用JavaScript来实现的执行模型。可以将它比作由react管理的“调用栈”,一个fiber与一个函数栈帧非常类似,它们都表示一个工作单元(a unit of work)。一个组件实例对应一个Fiber。
函数栈帧 | fiber |
---|---|
返回指针 | 父组件 |
当前函数 | 当前组件 |
调用参数 | props |
局部变量 | state |
React Fiber的构造函数源码
React Fiber与调用栈的区别:
- React Fiber是链表结构,过去的递归调用变成了对fiber的链表遍历。fiber不仅有return指针,还有child、sibling指针,有这三个指针的链表就能够实现深度优先遍历(其实scheduler还能够更加灵活地调度,使得react能够优先执行重要组件的渲染)。
- fiber与调用栈的另一个区别是,栈帧在函数返回以后就销毁了,而fiber会在渲染结束以后继续存在,保存组件实例的信息。
React Fiber是使用JavaScript实现的,这意味着它的底层依然是JavaScript调用栈。
Fiber其实是计算机科学中早已存在的概念。Fiber的英文含义就是“纤维”,意指比Thread更细的线,寓意它是比线程(Thread)控制得更精密的执行模型。fiber是协作的(cooperatively)、可控的。一个fiber执行完自己的工作以后,会主动让出控制权,不会主宰(dominate)整个程序的执行。
协程(Coroutines)基本是相同的概念,它们的区别微乎其微。说白了,React Fiber就是用JavaScript重新实现了一个协程模型。
话说回来,generator函数也能够主动让出程序控制权(generator函数本质就是协程),用它也能够做到concurrent rendering。为什么react不使用generator函数而是重新实现协程,应该是因为后者能够更加灵活吧,比如generator函数不支持回到之前的yield状态,而fiber支持从任意一个fiber节点重新开始渲染。
与Fiber相反,调用栈模型则不可控、不协作(non-cooperatively)。如果函数不断地递归调用,那么会完全主宰整个程序,后续的工作(比如浏览器paint)必须等待它执行完成。
摘自React Fiber是什么:
在React Fiber中,一次更新过程会分成多个分片完成,所以完全有可能一个更新任务还没有完成,就被另一个更高优先级的更新过程打断,这时候,优先级高的更新任务会优先处理完,而低优先级更新任务所做的工作则会完全作废,然后等待机会重头再来。
因为一个更新过程可能被打断,所以React Fiber一个更新过程被分为两个阶段(Phase):第一个阶段Reconciliation Phase和第二阶段Commit Phase。
在第一阶段Reconciliation Phase,React Fiber会找出需要更新哪些DOM,这个阶段是可以被打断的;但是到了第二阶段Commit Phase,那就一鼓作气把DOM更新完,绝不会被打断。
生命周期示意图:
参考资料
Algebraic effects, Fibers, Coroutines...
React Fiber Architecture
Inside Fiber: in-depth overview of the new reconciliation algorithm in React