浅谈React Fiber

图片描述

背景

前段时间准备前端招聘事项,复习前端React相关知识;复习React16新的生命周期:弃用了componentWillMountcomponentWillReceivePorpscomponentWillUpdate三个生命周期, 新增了getDerivedStateFromPropsgetSnapshotBeforeUpdate来代替弃用的三个钩子函数。
发现React生命周期的文章不多说到 React 官方为何要弃用这三生命周期的缘由, 查阅相关资料了解到根本缘由是V16版本重构核心算法架构:React Fiber;查阅资料过程当中对React Fiber有了必定了解,本文就相关资料整理出我的对Fiber的理解, 与你们一块儿简单认识下 React Fiberhtml

React Fiber是什么?

官方的一句话解释是“React Fiber是对核心算法的一次从新实现”。Fiber 架构调整很早就官宣了,但官方通过两年时间才在V16版本正式发布。官方概念解释太笼统, 其实简单来讲 React Fiber 是一个新的任务调和器(Reconciliation), 本文后续将详细解释。前端

为何叫 “Fiber”?

你们应该都清楚进程(Process)和线程(Thread)的概念,进程是操做系统分配资源的最小单元,线程是操做系统调度的最小单元,在计算机科学中还有一个概念叫作Fiber,英文含义就是“纤维”,意指比Thread更细的线,也就是比线程(Thread)控制得更精密的并发处理机制。
上面说的Fiber和React Fiber不是相同的概念,可是,React团队把这个功能命名为Fiber,含义也是更加紧密的处理机制,比Thread更细。react

Fiber 架构解决了什么问题?

为何官方要花2年多的时间来重构React 核心算法?
首先要从Fiber算法架构前 React 存在的问题提及!提及React算法架构避不开“Reconciliaton”。算法

Reconciliation

React 官方核心算法名称是 Reconciliation , 中文翻译是“协调”!React diff 算法的实现 就与之相关。
先简单回顾下React Diff: React独创了“虚拟DOM”概念, “虚拟DOM”能火并流行起来主要缘由在于该概念对前端性能优化的突破性创新;
稍微了解浏览器加载页面原理的前端同窗都知道网页性能问题大都出如今DOM节点频繁操做上;
而React经过“虚拟DOM” + React Diff算法保证了前端性能;编程

传统Diff算法

经过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——若是要展现1000个节点,得执行上亿次比较。。即使是CPU快能执行30亿条命令,也很难在一秒内计算出差别。segmentfault

React Diff算法

将Virtual DOM树转换成actual DOM树的最少操做的过程 称为 协调(Reconciliaton)。
React Diff三大策略
1.tree diff;
2.component diff;
3.element diff;
PS: 以前H5开发遇到的State 中变量更新但视图未更新的Bug就是element diff检测致使。解决方案:1.两种业务场景下的DOM节点尽可能避免雷同; 2.两种业务场景下的DOM节点样式避免雷同;react-native

在V16版本以前 协调机制Stack reconciler, V16版本发布Fiber 架构后是 Fiber reconciler浏览器

Stack reconciler

Stack reconciler 源码

// React V15: react/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js
/**
 * ------------------ The Life-Cycle of a Composite Component ------------------
 *
 * - constructor: Initialization of state. The instance is now retained.
 *   - componentWillMount
 *   - render
 *   - [children's constructors]          // 子组件constructor()
 *     - [children's componentWillMount and render]   // 子组件willmount render
 *     - [children's componentDidMount]  // 子组件先于父组件完成挂载didmount
 *     - componentDidMount
 *
 *       Update Phases:
 *       - componentWillReceiveProps (only called if parent updated)
 *       - shouldComponentUpdate
 *         - componentWillUpdate
 *           - render
 *           - [children's constructors or receive props phases]
 *         - componentDidUpdate
 *
 *     - componentWillUnmount
 *     - [children's componentWillUnmount]
 *   - [children destroyed]
 * - (destroyed): The instance is now blank, released by React and ready for GC.
 *
 * -----------------------------------------------------------------------------

Stack reconciler 存在的问题

Stack reconciler的工做流程很像函数的调用过程。父组件里调子组件,能够类比为函数的递归(这也是为何被称为stack reconciler的缘由)。
在setState后,react会当即开始reconciliation过程,从父节点(Virtual DOM)开始遍历,以找出不一样。将全部的Virtual DOM遍历完成后,reconciler才能给出当前须要修改真实DOM的信息,并传递给renderer,进行渲染,而后屏幕上才会显示这次更新内容。
对于特别庞大的DOM树来讲,reconciliation过程会很长(x00ms),在这期间,主线程是被js占用的,所以任何交互、布局、渲染都会中止,给用户的感受就是页面被卡住了。缓存

clipboard.png

网友测试使用React V15,当DOM节点数量达到100000时, 加载页面时间居然要7秒;详情
固然以上极端状况通常不会出现,官方为了解决这种特殊状况。在Fiber 架构中使用了Fiber reconciler。性能优化

Fiber reconciler

原来的React更新任务是采用递归形式,那么如今若是任务想中断, 在递归中是很难处理, 因此React改为了大循环模式,修改了生命周期也是由于任务可中断

Fiber reconciler 源码

React的相关代码都放在packages文件夹里。(PS: 源码一直在更新,如下路径有时效性不必定准确)

├── packages --------------------- React实现的相关代码
│   ├── create-subscription ------ 在组件里订阅额外数据的工具
│   ├── events ------------------- React事件相关
│   ├── react -------------------- 组件与虚拟DOM模型
│   ├── react-art ---------------- 画图相关库
│   ├── react-dom ---------------- ReactDom
│   ├── react-native-renderer ---- ReactNative
│   ├── react-reconciler --------- React调制器
│   ├── react-scheduler ---------- 规划React初始化,更新等等
│   ├── react-test-renderer ------ 实验性的React渲染器
│   ├── shared ------------------- 公共代码
│   ├── simple-cache-provider ---- 为React应用提供缓存

这里面咱们主要关注 reconciler 这个模块, packages/react-reconciler/src

├── react-reconciler ------------------------ reconciler相关代码
│   ├── ReactFiberReconciler.js ------------- 模块入口
├─ Model ----------------------------------------
│   ├── ReactFiber.js ----------------------- Fiber相关
│   ├── ReactUpdateQueue.js ----------------- state操做队列
│   ├── ReactFiberRoot.js ------------------- RootFiber相关
├─ Flow -----------------------------------------
│   ├── ReactFiberScheduler.js -------------- 1.整体调度系统
│   ├── ReactFiberBeginWork.js -------------- 2.Fiber解析调度
│   ├── ReactFiberCompleteWork.js ----------- 3.建立DOM 
│   ├── ReactFiberCommitWork.js ------------- 4.DOM布局
├─ Assist ---------------------------------------
│   ├── ReactChildFiber.js ------------------ children转换成subFiber
│   ├── ReactFiberTreeReflection.js --------- 检索Fiber
│   ├── ReactFiberClassComponent.js --------- 组件生命周期
│   ├── stateReactFiberExpirationTime.js ---- 调度器优先级
│   ├── ReactTypeOfMode.js ------------------ Fiber mode type
│   ├── ReactFiberHostConfig.js ------------- 调度器调用渲染器入口

Fiber reconciler 优化思路

clipboard.png

Fiber reconciler 使用了scheduling(调度)这一过程, 每次只作一个很小的任务,作完后可以“喘口气儿”,回到主线程看下有没有什么更高优先级的任务须要处理,若是有则先处理更高优先级的任务,没有则继续执行(cooperative scheduling 合做式调度)。

网友测试使用React V16,当DOM节点数量达到100000时, 页面能正常加载,输入交互也正常了;详情

因此Fiber 架构就是用 异步的方式解决旧版本 同步递归致使的性能问题。

Fiber 核心算法

编程最重要的是思想而不是代码,本段主要理清Fiber架构内核算法的编码思路;

Fiber 源码解析

以前一个师弟问我关于Fiber的小问题:
Fiber 框架是否会自动给 Fiber Node打上优先级?
若是给Fiber Node打上的是async, 是否会给给它设置 expirationTime
带着以上问题看源码, 结论:
框架给每一个 Fiber Node 打上优先级(nowork, sync, async), 不论是sync 仍是 async都会给 该Fiber Node 设置expirationTime, expirationTime 越小优先级越高。

我的阅读源码细节就不放了, 由于发现网上有更系统的Fiber 源码文章,虽然官方源码已更新至Flow语法, 但算法并没太大改变:
React Fiber源码分析 (介绍)
React Fiber源码分析 第一篇
React Fiber源码分析 第二篇(同步模式)
React Fiber源码分析 第三篇(异步状态)

优先级

clipboard.png

module.exports = {
  NoWork: 0, // No work is pending.
  SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
  AnimationPriority: 2, // Needs to complete before the next frame.
  HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
  LowPriority: 4, // Data fetching, or result from updating stores.
  OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};

React Fiber 每一个工做单元运行时有6种优先级:
synchronous 与以前的Stack reconciler操做同样,同步执行
task 在next tick以前执行
animation 下一帧以前执行
high 在不久的未来当即执行
low 稍微延迟(100-200ms)执行也不要紧
offscreen 下一次render时或scroll时才执行

生命周期

clipboard.png

生命周期函数也被分为2个阶段了:

// 第1阶段 render/reconciliation
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate

// 第2阶段 commit
componentDidMount
componentDidUpdate
componentWillUnmount

第1阶段的生命周期函数可能会被屡次调用,默认以low优先级 执行,被高优先级任务打断的话,稍后从新执行。

Fiber 架构对React开发影响

本段主要探讨React V16 后Fiber架构对咱们使用React业务编程的影响有哪些?实际编码须要注意哪些内容。

1.不使用官方宣布弃用的生命周期。

为了兼容旧代码,官方并无当即在V16版本废弃三生命周期, 用新的名字(带上UNSAFE)仍是能使用。 建议使用了V16+版本的React后就不要再使用废弃的三生命周期。
由于React 17版本将真正废弃这三生命周期:

到目前为止(React 16.4),React的渲染机制遵循同步渲染:
1) 首次渲染: willMount > render > didMount,
2) props更新时: receiveProps > shouldUpdate > willUpdate > render > didUpdate
3) state更新时: shouldUpdate > willUpdate > render > didUpdate
3) 卸载时: willUnmount
期间每一个周期函数各司其职,输入输出都是可预测,一路下来很顺畅。
BUT 从React 17 开始,渲染机制将会发生颠覆性改变,这个新方式就是 Async Render。
首先,async render不是那种服务端渲染,好比发异步请求到后台返回newState甚至新的html,这里的async render仍是限制在React做为一个View框架的View层自己。
经过进一步观察能够发现,预废弃的三个生命周期函数都发生在虚拟dom的构建期间,也就是render以前。在未来的React 17中,在dom真正render以前,React中的调度机制可能会不按期的去查看有没有更高优先级的任务,若是有,就打断当前的周期执行函数(哪怕已经执行了一半),等高优先级任务完成,再回来从新执行以前被打断的周期函数。这种新机制对现存周期函数的影响就是它们的调用时机变的复杂而不可预测,这也就是为何”UNSAFE”。
做者:辰辰沉沉大辰沉
来源:CSDN

2.注意Fiber 优先级致使的bug;

了解Fiber原理后, 业务开发注意高优先级任务频率,避免出现低优先级任务延迟过久执行或永不执行bug(starvation:低优先级饿死)。

3.业务逻辑实现别太依赖生命周期钩子函数;

在Fiber架构中,task 有可能被打断,须要从新执行,某些依赖生命周期实现的业务逻辑可能会受到影响。

参考文档
React 新生命周期;
深刻理解进程和线程;
彻底理解React Fiber;
React Fiber;
如何阅读大型项目源码;
React 源码解析;
React Fiber 源码解析;
React Fiber 是什么?
React diff 算法策略和实现
React 新引擎, React Fiber 是什么?

相关文章
相关标签/搜索