🗒️react fiber
type
status
date
slug
summary
tags
category
icon
password
Blocking
Blocked by
top
URL
Sub-item
Parent item
背景
react-fiber 产生的根本原因,是大量的同步计算任务阻塞了浏览器的 UI 渲染。
在
React15
及以前,Reconciler
采用递归的方式创建虚拟DOM,递归过程是不能中断的。如果组件树的层级很深,递归会占用线程很多时间,造成卡顿。为了解决这个问题,react 16.18.0 版本引入 fiber 架构,实现异步可中断更新。先把 vdom 树转成 fiber 链表,然后再渲染 fiber。主要是解决之前由于直接递归遍历 vdom,不可中断,导致当 vdom 比较大的,频繁调用耗时 dom api 容易产生性能问题。
浏览器一帧的流程
一个task(宏任务) -- 队列中全部job(微任务) -- requestAnimationFrame -- 浏览器重排/重绘 -- requestIdleCallback
是什么
React Fiber 是 Facebook 花费两年余时间对 React 做出的一个重大改变与优化,是对 React 核心算法的一次重新实现React Fiber 在React 16 版本发布
在
react
中,主要做了以下的操作:- 为每个增加了优先级,优先级高的任务可以中断低优先级的任务。然后再重新,注意是重新执行优先级低的任务
- 增加了异步任务,调用requestIdleCallback api,浏览器空闲的时候执行
- dom diff树变成了链表,一个dom对应两个fiber(一个链表),对应两个队列,这都是为找到被中断的任务,重新执行
从架构角度来看,
Fiber
是对 React
核心算法(即调和过程)的重写从编码角度来看,
Fiber
是 React
内部所定义的一种数据结构,它是 Fiber
树结构的节点单位,也就是 React 16
新架构下的虚拟DOM
一个
fiber
就是一个 JavaScript
对象,包含了元素的信息、该元素的更新操作队列、类型,其数据结构如下:如何解决
Fiber
把渲染更新过程拆分成多个子任务,每次只做一小部分,做完看是否还有剩余时间,如果有继续下一个任务;如果没有,挂起当前任务,将时间控制权交给主线程,等主线程不忙的时候在继续执行即可以中断与恢复,恢复后也可以复用之前的中间状态,并给不同的任务赋予不同的优先级,其中每个任务更新单元为
React Element
对应的 Fiber
节点实现的上述方式的是
requestIdleCallback
方法window.requestIdleCallback()
方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应首先 React 中任务切割为多个步骤,分批完成。在完成一部分任务之后,将控制权交回给浏览器,让浏览器有时间再进行页面的渲染。等浏览器忙完之后有剩余时间,再继续之前 React 未完成的任务,是一种合作式调度。
该实现过程是基于
Fiber
节点实现,作为静态的数据结构来说,每个 Fiber
节点对应一个 React element
,保存了该组件的类型(函数组件/类组件/原生组件等等)、对应的 DOM 节点等信息。作为动态的工作单元来说,每个
Fiber
节点保存了本次更新中该组件改变的状态、要执行的工作。每个 Fiber 节点有个对应的
React element
,多个 Fiber
节点根据如下三个属性构建一颗树:通过这些属性就能找到下一个执行目标
Fiber 的主要工作流程
ReactDOM.render()
引导 React 启动或调用setState()
的时候开始创建或更新 Fiber 树。
- 从根节点开始遍历 Fiber Node Tree, 并且构建 workInProgress Tree(reconciliation 阶段)。
- 本阶段可以暂停、终止、和重启,会导致 react 相关生命周期重复执行。
- React 会生成两棵树,一棵是代表当前状态的 current tree,一棵是待更新的 workInProgress tree。
- 遍历 current tree,重用或更新 Fiber Node 到 workInProgress tree,workInProgress tree 完成后会替换 current tree。
- 每更新一个节点,同时生成该节点对应的 Effect List。
- 为每个节点创建更新任务。
- 将创建的更新任务加入任务队列,等待调度。
- 调度由 scheduler 模块完成,其核心职责是执行回调。
- scheduler 模块实现了跨平台兼容的 requestIdleCallback。
- 每处理完一个 Fiber Node 的更新,可以中断、挂起,或恢复。
- 根据 Effect List 更新 DOM (commit 阶段)。
- React 会遍历 Effect List 将所有变更一次性更新到 DOM 上。
- 这一阶段的工作会导致用户可见的变化。因此该过程不可中断,必须一直执行直到更新完成。
- Render 阶段: 异步可中断的 diff 新老 DOM,找到差异后并不及时立刻更新。而是对该 Fiber 节点打上一个
tag
(Update/Placement/Delete)。 - Commit 阶段: 遍历存在 tag 的 Fiber ,根据
tag
的类型执行对应的 DOM 更新。 beginWork
阶段:顺着child
属性向下遍历,找到变化地地方,打上标记
complateWork
阶段:顺着return
属性向上回归,将有标记
的地方更新
,此时就是更新workInProgress
对应地Fiber tree
commitRoot
阶段:将workInProgress
对应的Fiber tree
渲染到页面,同时完成上述指针的切换工作- 先从更新的节点,一直return到hostRootFiber
- 再return stateNode到FiberRootNode
- 生成 wip fiberNode树
- 标记副作用的flags
- 触发更新
- 创建Update对象插入对应fiber节点的updateQueue上
- 并加入调度update
- 从fiber到root
- 调度更新
- render阶段
- commit阶段
- 事件优先级、
- 更新优先级(lane)、
- 任务优先级、
- 调度优先级(scheulder)
- 过期任务或者同步任务使用
同步
优先级 - 用户交互产生的更新(比如点击事件)使用高优先级
- 网络请求产生的更新使用一般优先级
Suspense
使用低优先级- 可以表示
优先级
的不同 - 可能同时存在几个同
优先级
的更新
,所以还得能表示批
的概念 - 方便进行
优先级
相关计算 - Render 阶段: 异步可中断的 diff 新老 DOM,找到差异后并不及时立刻更新。而是对该 Fiber 节点打上一个
tag
(Update/Placement/Delete)。 - Commit 阶段: 遍历存在 tag 的 Fiber ,根据
tag
的类型执行对应的 DOM 更新。 - 获取新的Fiber根节点
- 与上一轮状态进行对比,通过diff算法找出差异
- 生成所有子Fiber节点,构建新Fiber树
- 检查是否需要更新(shouldComponentUpdate)
- 在Fiber节点上记录必要的DOM操作
- 处理所有子节点,冒泡回调相关钩子
- 创建 DOM 节点
- 将 DOM 节点插入到其父节点对应的DOM节点里
- 为 DOM 节点更新属性
- 如果还有未处理的子节点,就对其调用beginWork
- 构建或更新HostComponent对应的DOM节点,
- 构建过程中,会自下而上将子节点插入到当前节点。
- 更新过程中,会计算DOM节点的属性,一旦属性需要更新,会为DOM节点对应的workInProgress节点标记Update的effectTag。
- 自下而上收集effectList,最终收集到root上
- 否则从父节点向上冒泡返回effectTag
- DOM节点删除以后的autoFous, blur的逻辑
- 针对类组件,调用getSnapshotBeforeUpdate,让我们可以在DOM变更前获取组件实例的信息;
- 针对函数组件,异步调度useEffect。
- 针对HostComponent,进行相应的DOM操作
- 针对类组件,调用componentWillUnmount;
- 针对函数组件,执行useLayoutEffect的销毁函数。
- 在DOM操作完成后,读取组件的状态,
- 针对类组件,调用生命周期componentDidMount和componentDidUpdate,调用setState的回调;
- 针对函数组件填充useEffect 的 effect执行数组,并调度useEffect
- BeforeMuation,没做太多事,主要是类组件实例调用 getSnapshotBeforeUpdate 生成快照对象保存起来;
- Muation:
- Layout:
- 调用函数组件 useLayoutEffect 的 create;
- 最后更新 ref。
- Fiber双缓存可以在构建好wip Fiber树之后切换成current Fiber,内存中直接一次性切换,提高了性能
- Fiber的存在使异步可中断的更新成为了可能,作为工作单元,可以在时间片内执行工作,没时间了交还执行权给浏览器,下次时间片继续执行之前暂停之后返回的Fiber
- effect函数会被记录在Fiber节点的updateQueue中。
- 在commit阶段,会遍历updateQueue依次执行队列中的effect函数。
- effect函数执行后,会清空当前节点的副作用队列。
- 如果effect依赖项有变化,下次渲染就会再次插入新的函数入队。
- 清除函数会在即将卸载组件时调用,清理原先设置的副作用。
- react元素$$typeof属性什么
- 用来表示react元素的类型,是一个symbol类型
- 非React元素对象不会有这个属性。
- react怎么区分Class组件和Function组件
- 函数组件和类组件的相同点和不同点
- 声明式 代码结构简洁 可读性强 结构样式和事件可以实现高内聚 低耦合 、复用和组合 不需要引入新的概念和语法.
- 只写js, 虚拟dom快平台
- React把事件委托到document上(v17是container节点上)
- 先处理原生事件 冒泡到document上在处理react事件
- React事件绑定发生在reconcile阶段 会在原生事件绑定前执行
- 进行了浏览器兼容。顶层事件代理,能保证冒泡一致性(混合使用会出现混乱)
- 默认批量更新
- 避免事件对象频繁创建和回收,react引入事件池,在事件池中获取和释放对象(react17中废弃) react17事件绑定在容器上了
- 我们写的事件是绑定在
dom
上么,如果不是绑定在哪里? 答:v16绑定在document上,v17绑定在container上 - 为什么不能用
return false
来阻止事件的默认行为? react
怎么通过dom
元素,找到与之对应的fiber
对象的?
渲染流程
Fiber
将 React
的一次更新流程拆成了两个阶段:渲染流程
将更新内容都放到updateQueue里,
然后在updateQueue中,一起执行
执行完后进行调度。
递归到最上层
创建workInProgress tree
如果是初始化,则创建新的Fiber节点
如果是更新,则更新Fiber节点,复用原本的Fiber节点的一些属性
更新流程目的:
react 状态更新流程

effectTag(flags)
ffectTag
实际上就是需要对节点需要执行的 DOM 操作(也可认为是副作用,即 sideEffect )render 阶段是在内存中进行的,render 阶段需要做的是为需要执行 DOM 操作的节点打上标记也就是 effectTag。 当工作结束后会通知 renderer 渲染器需要执行的 DOM 操作,要执行的 DOM 操作的具体类型就保存在 fiber.effectTag 中
采用二进制表示
effectTag
的类型,可以方便的使用位操作为 fiber.effectTag 赋值多个 effectreact 优先级
react中有四种优先级:
Lane事件优先级
可以看到React的事件优先级的值还是使用的Lane的值,那为什么不直接使用Lane呢?我觉得可能是为了不与Lane机制耦合,后面事件优先级有什么变动的话,可以直接修改而不会影响到Lane。
Scheduler优先级
React
需要设计一套满足如下需要的优先级
机制:为了满足如上需求,
React
设计了lane
模型。接下来我们来看lane
模型如何满足以上3个条件。
react 渲染两阶段
Fiber
将 React
的一次更新流程拆成了两个阶段:render阶段:

beginWork工作流程(从上往下):
beginWork
的是一个从上至下,从左至右的深度遍历顺序。beginWork负责向下生成并连接Fiber Node,shengch新Fiber树,通过diff找到差异并记录更新
工作:


complateWork(从下往上):
completeWork
是一个从叶子节点由左至右,由下至上的遍历过程,比较类似于后续遍历的方式。主要处理 Fiber 节点到 DOM 节点的映射逻辑,并向上收集Fiber的副作用关键动作:
completeWork(向上收集Fiber的副作用)负责真实地执行这些更新,如更改DOM和调用生命周期
工作:

commit阶段
beforeMutation(执行DOM
操作前)
先向下遍历找到最深节点,找到标记flags的节点,再向上遍历
mutation(执行DOM
操作)
在这里更新 DOM
workInProgress 树切换到current树的时机是在mutation结束后,layout开始前。这
layout(执行DOM
操作后)

总结一下。
commit 分成三个阶段:BeforeMuation、Muation 以及 Layout 阶段。
DOM 更新阶段 React会对组件的子树进行更新,包括创建、销毁和移动子节点等操作。
1. 调用类组件的 componentDidMount、componentDidUpdate、setState 的回调函数
面试题
Fiber
jsx与fiber关系
答:
jsx
映射出 静态结构Fiber
(保存了该组件的类型(函数组件/类组件/原生组件...)、对应的DOM节点等信息); 动态结构Fiber
动态的工作单元来说,保存了本次更新中该组件改变的状态、优先级、要执行的工作(需要被删除/被插入页面中/被更新...)等; 静态 + 动态
组成完整的Fiber结构
Fiber是什么,它为什么能提高性能
答:Fiber是一个js对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。
Hooks
为什么hooks不能写在条件判断中
hook会按顺序存储在链表中,如果写在条件判断中,就没法保持链表的顺序
useEffect 原理是什么
状态/生命周期
setState是同步的还是异步的
答:legacy模式下:命中batchedUpdates时是异步 未命中batchedUpdates时是同步的
组件
答:Class组件prototype上有isReactComponent属性
答:相同点:都可以接收props返回react元素
不同点:
编程思想:类组件需要创建实例,面向对象,函数组件不需要创建实例,接收输入,返回输出,函数式编程
内存占用:类组建需要创建并保存实例,占用一定的内存
值捕获特性:函数组件具有值捕获的特性
开放题
优化手段
shouldComponentUpdate
不可变数据结构
列表key
pureComponent:是 React 的一个高阶组件。如果 props 和 state 没有变化,则返回 false 跳过更新。
react.memo
useEffect、useCallback、useMemo、b
ailoutOnAlreadyFinishedWork
react为什么引入jsx
jsx是js语法的扩展 可以很好的描述ui
jsx是React.createElement的语法糖
想实现什么目的:
有哪些可选方案:模版语法 vue ag引入了控制器 作用域 服务等概念
jsx原理:babel抽象语法树 classic是老的转换 automatic新的转换
合成事件
类型 | 原生事件 | 合成事件 |
命名方式 | 全小写 | 小驼峰 |
事件处理函数 | 字符串 | 函数对象 |
阻止默认行为 | 返回false | event.preventDefault() |
ㅤ | ㅤ | ㅤ |
优势:
说到底还是合成事件和原生事件触发时机不一样
通过internalInstanceKey对应
编程题
打印顺序是什么
useLayout/componentDidMount和useEffect的区别是什么
在commit阶段不同时机执行,useEffect在commit阶段结尾异步调用,useLayout/componentDidMount同步调用
