持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第24天,点击查看活动详情
核心描述
- 为什么需要 fiber,以及其解决了什么问题:
- React 15 及以前的架构中, Reconciler (协调器)采用递归的方式创建虚拟 DOM,递归过程中是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,递归更新时间超过了 16ms,用户交互就会卡顿。
- 为了解决这个问题,React 16 将递归的无法中断更新重构为异步的可中断更新,由于曾经用于递归的虚拟 DOM 数据结构已经无法满足需要。于是产生了 React Fiber 架构。
- fiber 本质是什么
- 在广义的计算机科学概念中,Fiber 是一种协作的变成模型(协程),帮助开发者用一种既模块化又协作化的方式来编排代码。
- 在 React 中,Fiber 就是 React 16 实现的一套新的更新机制,让 React 的更新过程变得可控,避免了之前采用递归需要一气呵成影响性能的做法。
- 在 React 中 Fiber 包含的三层含义:
- 作为架构来说,之前React15的Reconciler采用递归的方式执行,数据保存在递归调用栈中,所以被称为stack Reconciler。React16的Reconciler基于Fiber节点实现,被称为Fiber Reconciler。
- 作为静态的数据结构来说,每个Fiber节点对应一个React element,保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息。
- 作为动态的工作单元来说,每个Fiber节点保存了本次更新中该组件改变的状态、要执行的工作(需要被删除/被插入页面中/被更新...)。
知识拓展
-
React 15-17 架构演变的简要概述:
- React 15 架构可以分为两层:
- Reconciler(协调器):负责找出变化的组件,递归实现,不可中断
- Renderer(渲染器):负责将变化的组件渲染到页面上
- React 16 架构可以分为三层:
- Scheduler(调度器):调度任务的优先级,高优先级的任务先进入 Reconciler
- Reconciler(协调器):负责找出变化的组件,采用 Fiber 架构实现,将递归变成了可以中断的循环
- Renderer(渲染器):负责将变化的组件渲染到页面上(此过程不可以中断)
- React 17 架构优化:
- 在 React 16 的基础上,加入了 Concurrent 模式,目的是实现一套可中断/恢复的更新机制,需要注意在稳定版本默认没有使用此模式,需要手动设置渲染模式为 Concurrent 才可以
- 将 React 16 的 expirationTimes 模型优化为 Lanes 模型
- React 15 架构可以分为两层:
-
React 和 Vue 的本质区别:
- Vue 是静态分析 template 文件,采用预编译优化,在解析模板的同时构建 AST 依赖树,同时标记出可能会变化的动态节点。利用数据双向绑定,进行数据拦截或代理,进行响应式处理。从而能够比较精准的计算出有改变的 DOM,减少计算量。
- React 是局部渲重新渲染,核心就是一堆递归的 React.createElement 的执行调用。其优化的方向是不断的优化 React.createElement 的执行速度,让其更快,更合理的创建最终的元素。
-
关于 Fiber 其他的一些知识点:
- Fiber 节点的数据结构示例:
class FiberNode { constructor(tag, pendingProps, key, mode) { // 实例属性 this.tag = tag; // 标记不同组件类型,如函数组件、类组件、文本、原生组件... this.key = key; // react 元素上的 key 就是 jsx 上写的那个 key ,也就是最终 ReactElement 上的 this.elementType = null; // createElement的第一个参数,ReactElement 上的 type this.type = null; // 表示fiber的真实类型 ,elementType 基本一样,在使用了懒加载之类的功能时可能会不一样 this.stateNode = null; // 实例对象,比如 class 组件 new 完后就挂载在这个属性上面,如果是RootFiber,那么它上面挂的是 FiberRoot,如果是原生节点就是 dom 对象 // fiber this.return = null; // 父节点,指向上一个 fiber this.child = null; // 子节点,指向自身下面的第一个 fiber this.sibling = null; // 兄弟组件, 指向一个兄弟节点 this.index = 0; // 一般如果没有兄弟节点的话是0 当某个父节点下的子节点是数组类型的时候会给每个子节点一个 index,index 和 key 要一起做 diff this.ref = null; // reactElement 上的 ref 属性 this.pendingProps = pendingProps; // 新的 props this.memoizedProps = null; // 旧的 props this.updateQueue = null; // fiber 上的更新队列执行一次 setState 就会往这个属性上挂一个新的更新, 每条更新最终会形成一个链表结构,最后做批量更新 this.memoizedState = null; // 对应 memoizedProps,上次渲染的 state,相当于当前的 state,理解成 prev 和 next 的关系 this.mode = mode; // 表示当前组件下的子组件的渲染方式 // effects this.effectTag = NoEffect; // 表示当前 fiber 要进行何种更新(更新、删除等) this.nextEffect = null; // 指向下个需要更新的fiber this.firstEffect = null; // 指向所有子节点里,需要更新的 fiber 里的第一个 this.lastEffect = null; // 指向所有子节点中需要更新的 fiber 的最后一个 this.expirationTime = NoWork; // 过期时间,代表任务在未来的哪个时间点应该被完成 this.childExpirationTime = NoWork; // child 过期时间 this.alternate = null; // current 树和 workInprogress 树之间的相互引用 } }
- Fiber 节点构成的 Fiber 树就对应 DOM 树
- 双缓存技术:
- 在 React 中最多存在两个 Fiber 树,页面上渲染的是 current Fiber 树,内存中构建的是 workInProgress Fiber 树
- 两个树的关联,是通过每个树中 Fiber 节点的 alternate 属性连接
- React 的 diff 算法就是基于 Fiber 节点的状态来实现的
- React 的 Scheduler 调度算法,也是基于 Fiber 数据结构来实现
- 最终的 render 是通过将 ReactFiberHostConfig 和 ReactDOMHostConfig 关联起来,生成页面中的实际 DOM
参考资料
- React技术揭秘:react.iamkasong.com/process/fib…
- React Fiber很难?六个问题助你理解 React Fiber:segmentfault.com/a/119000003…
- React 框架 | 深入剖析 Scheduler 原理:juejin.cn/post/704965…
浏览知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。