React Fiber面试宝典:深度掌握,助你一路通关无阻!

每次面试,你是否都听到这些问题回荡在耳边:"你对React Fiber了解多少?"、"React Fiber究竟是什么?"、"它在实际应用中有什么价值?"。别再被这些问题困扰了!React Fiber作为React的核心,已经成为前端面试的必考知识点。掌握React Fiber,不仅能让你在面试中脱颖而出,更能在实际工作中助力你优化应用性能,打造更流畅的用户体验。现在就开始深入探索React Fiber的奥秘吧,让面试和工作都变得更加轻松自如! 注:本文是基于React@v18.2.0版本

为什么使用 React Fiber?

React@v16 之前的版本,React 对于虚拟 DOM 是采用递归方式遍历更新的,比如一次更新,就会从应用根部递归更新,递归一旦开始,中途无法中断,随着项目越来越复杂,层级越来越深,导致更新的时间越来越长,给前端交互上的体验就是卡顿。为了解决这个问题,React就引入了Fiber架构,提高React的渲染性能和效率。它采用了一种可中断的调和算法,并引入了优先级调度的概念,可以更好地响应用户输入和动画变化,提高应用的流畅度和响应性。

React Fiber是什么?

在了解 React Fiber是个啥之前,你必须要知道,jsx、ReactElement、FiberNode、dom之间的关系?

  • 写JSX来描述React组件的结构和内容。
  • JSX被Babel转译成React.createElement(__jsx或__jsxs)的调用,生成ReactElement。
  • 在React的协调过程中,ReactElement被转换成FiberNode。
  • FiberNode是React用来进行高效渲染和更新的数据结构,它支持并发渲染和优先级调度。
  • 最终,FiberNode的信息被用来更新浏览器中的真实DOM,从而呈现用户界面。

ReactElement数据结构

源代码位置 react/src/ReactElement.js

js 复制代码
const element = { 
    $$typeof: REACT_ELEMENT_TYPE,//用来标记这个对象是一个React元素。React使用这个属性来防止XSS攻击,并确保对象是由React创建的
    type: type,//div a 元素标签 
    key: key,
    ref: ref, 
    props: props,
}

Fiber数据结构

源代码位置 react-reconciler/src/ReactFiber.js

ReactElement通过createFiberFromElement函数转为FiberNode

Fiber数据结构是一个链表,这样就为Fiber架构可中断渲染提供可能

js 复制代码
 function FiberNode(){
  this.tag = tag; //元素类型
  this.key = key;//元素的唯一标识。
  this.elementType = null; //元素类型 
  this.type = null;//元素类型
  this.stateNode = null;//元素实例的状态节点
  // Fiber
  this.return = null;//该组件实例的父级。
  this.child = null;//该组件实例的第一个子级。
  this.sibling = null;//该组件实例的下一个兄弟级
  this.index = 0;//该组件实例在父级的子级列表中的位置。
  this.ref = null;//该组件实例的ref属性
  this.refCleanup = null;//ref的清理函数
  this.pendingProps = pendingProps;//待处理的props(最新的)
  this.memoizedProps = null;//处理后的props(上一次)
  this.updateQueue = null;//TODO
  this.memoizedState = null;//类组件保存state信息,函数组件保存hooks信息
  this.dependencies = null;//该组件实例的依赖列表
  this.mode = mode;//该组件实例的模式 (DOM模式和Canvas模式)

  // Effectsx
  this.flags = NoFlags$1;//副作用标签 ,之前的版本是effectTag
  this.subtreeFlags= NoFlags$1;//子节点副作用标签。
  this.deletions = null;//待删除的子树列表。
  this.lanes = NoLanes;//任务更新的优先级区分
  this.childLanes = NoLanes;//子树任务更新的优先级区分
  this.alternate = null;//组件实例的备份实例,用于记录前一次更新的状态。更新时候    workInProgress会复用当前值
  }

React Fiber属性繁杂?至少你在面试的时候能讲出tag、key、child、sibling、return这五点,让面试官觉得你还是懂的!

以下是这些属性的简要解释:

  1. tag

    • tag用来标识Fiber节点的类型。
    • 不同的tag值代表了不同类型的React元素,比如函数组件、类组件、DOM元素等。
    • React会根据tag的值来决定如何处理该Fiber节点。
  2. key

    • key是一个可选的字符串,用于在兄弟元素之间建立唯一的身份。
    • 当列表重新排序或元素添加/删除时,key帮助React识别哪些元素发生了变化,从而高效地更新UI。
    • 在Fiber节点中,key用于在协调过程中识别节点的身份。
  3. child

    • child指向Fiber节点的第一个子节点。
    • 通过child属性,React可以遍历Fiber树,执行渲染和更新操作。
  4. sibling

    • sibling指向Fiber节点的下一个兄弟节点。
    • 当React遍历完一个Fiber节点的所有子节点后,它会通过sibling属性移动到下一个兄弟节点,继续遍历。
  5. return

    • return指向Fiber节点的父节点。
    • 通过return属性,React可以在Fiber树中向上回溯,这对于错误处理和优先级调度等功能非常重要。

Fiber树生成

每个React都有以下代码入口:

js 复制代码
function App(){
   </div>
}

ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
  <App />,
)

源代码位置

react-dom/src/client/ReactDOMRoot.js 中createRoot函数

react-reconciler/src/ReactFiberRoot.js 中createFiberRoot、FiberRootNode函数

react-reconciler/src/ReactFiber.js 中createHostRootFiber函数

执行上面代码发生了什么?

  1. 首先调用createRoot方法创建FiberRoot(应用根节点)、RootFiber(Fiber树的根节点),目前对应节点上的数据都是空的,生成的数据结构如下:
js 复制代码
FiberRoot={
        "tag": 1,//ConcurrentRoot 
        "containerInfo": "div#root",//挂载的dom节点
        "current": { // RooFiber  
                "tag": 3,//标记Fiber的类型(如类组件、函数组件、DOM组件等)
                "key": null,//用于在列表或其他需要区分子元素的场景中识别Fiber的键。
                "elementType": null,//通常与type相同,但在某些情况下(如懒加载组件)可能不同。它指的是要渲染的元素类型。
                "type": null,//组件的类型(函数、类等)或DOM节点的类型(如'div')
                "stateNode": null,//对于DOM组件,这是实际的DOM节点;对于类组件,这是组件的实例
                "return": null,//指向父Fiber的指针
                "child": null,//指向子Fiber的指针
                "sibling": null,// 指向兄弟Fiber的指针
                "index": 0,//在父Fiber的子节点列表中的索引
                "ref": null,//用于获取DOM节点或类组件实例的引用
                "refCleanup": null,
                "pendingProps": null,//新的或待处理的props
                "memoizedProps": null,//上一次渲染使用的props
                "updateQueue": null,//存储状态更新和回调的队列
                "memoizedState": null,//上一次渲染时的状态
                "dependencies": null,
                "mode": 3,//表示Fiber的渲染模式(如并发模式、阻塞模式等)
                "flags": 0,//用于跟踪Fiber位字段的状态和效果的位字段
                "subtreeFlags": 0,//用于跟踪Fiber子树的状态和效果的位字段
                "deletions": null,//指向要删除的Fiber子树的指针
                "lanes": 0,//与优先级和并发渲染相关的内部字段
                "childLanes": 0,//与优先级和并发渲染相关的内部字段
                "alternate": null,//在双缓冲系统中,指向对应Fiber的指针(用于新旧树之间的比较)
                "actualDuration": 0,
                "actualStartTime": -1,
                "selfBaseDuration": 0,
                "treeBaseDuration": 0,
        },
        //...
 }
  1. 调用render方法创建对应Fiber节点的信息,因为上一波生成都是空,我们需要把组件App(),dev节点都构建成Fiber node。后面你就需要知道React Fiber是如何工作的?

React Fiber工作原理详解

  1. 双缓冲技术: React Fiber使用了类似于图形渲染中的双缓冲技术。这意味着在构建新的UI树时,React会同时在内存中维护两棵树:当前屏幕上显示的树(current tree)和正在构建的树(work-in-progress tree)。只有当新的树完全构建完成后,它才会被一次性地渲染到屏幕上,从而实现更加流畅的用户体验。

给你举个例子吧: 你正在观看一场魔术表演。魔术师在舞台上放置了一个黑色的幕布(这可以看作是后台缓冲区),然后在幕布后面进行各种准备和操作(这相当于在后台缓冲区中进行绘制操作)。观众无法看到幕布后面的情况,只能等待魔术师准备好并拉开幕布。 当魔术师完成所有的准备后,他会迅速地将幕布拉开,展示给观众一个完整的、令人惊叹的魔术效果(这相当于将后台缓冲区的内容一次性复制到前台显示设备上)。在这个过程中,观众并没有看到魔术师在幕布后面忙碌的过程,而是直接看到了最终的魔术效果。

  1. 任务调度: React Fiber引入了任务调度的概念,允许将渲染工作拆分成多个较小的任务单元。这些任务单元可以被中断和恢复,从而实现并发渲染。React根据任务的优先级来决定它们的执行顺序,确保高优先级的任务(如用户交互)能够优先执行。

运行方式(这里太多东西了,根本讲不完)

  1. Reconciliation阶段: 当React决定要更新UI时,它会启动reconciliation(协调)过程。在这个阶段,React会比较新旧两棵树之间的差异,并为需要更新的组件生成相应的Fiber节点。这个过程是异步的,可以被中断和恢复。
  2. Commit阶段: 当所有的Fiber节点都被处理完毕后,React会进入commit阶段。在这个阶段,React会将之前在render阶段计算出的所有变化一次性应用到DOM上,并触发相关的生命周期方法(如useEffect,useLayoutEffect方法)。这个过程是不可中断的,因为它涉及到实际的DOM操作。
  3. 优先级调度: React Fiber通过优先级调度来管理任务的执行顺序。每个Fiber节点都有一个与之关联的优先级,React会根据节点的优先级来决定哪些节点需要先更新。高优先级的任务(如用户交互)会打断低优先级的任务(如定时器回调)并优先执行,从而实现更流畅的用户体验。

Reconciliation阶段

在Reconciliation阶段,React会遍历Fiber树,并执行每个Fiber节点的更新逻辑。这个过程可以被分为两个阶段:beginWork和completeWork。在beginWork阶段,React会执行组件的渲染逻辑,并计算副作用(side effects)。在completeWork阶段,是向上归并的过程,如果有兄弟节点,会返回 sibling兄弟,没有返回 return 父级,一直返回到 fiebrRoot ,期间可以形成effectList,对于初始化流程会创建 DOM ,对于 DOM 元素进行事件收集,处理style,className等,这个阶段并不直接更新DOM或触发任何用户可见的更改,而是为后续的Commit阶段做准备。

beginWork做了什么?
  • 对于组件,执行部分生命周期,执行 render ,得到最新的 children 。
  • 向下遍历调和 children ,复用 oldFiber ( diff 算法),diff 流程。
  • 打不同的副作用标签 effectTag ,比如类组件的生命周期,或者元素的增加,删除,更新 你可以看下面代码

Commit阶段

在Commit阶段,React将根据在Reconciliation阶段生成的更新计划来执行实际的DOM更新。这个过程包括更新DOM节点、处理生命周期方法(如在类组件中的useEffect)以及执行其他与渲染相关的副作用。此阶段是同步执行的,意味着一旦开始,就会一口气完成,不会被其他任务打断。

以下是我看源代码流程图:

相关推荐
Myli_ing25 分钟前
考研倒计时-配色+1
前端·javascript·考研
余道各努力,千里自同风28 分钟前
前端 vue 如何区分开发环境
前端·javascript·vue.js
软件小伟37 分钟前
Vue3+element-plus 实现中英文切换(Vue-i18n组件的使用)
前端·javascript·vue.js
醉の虾1 小时前
Vue3 使用v-for 渲染列表数据后更新
前端·javascript·vue.js
张小小大智慧1 小时前
TypeScript 的发展与基本语法
前端·javascript·typescript
hummhumm1 小时前
第 22 章 - Go语言 测试与基准测试
java·大数据·开发语言·前端·python·golang·log4j
asleep7011 小时前
第8章利用CSS制作导航菜单
前端·css
hummhumm2 小时前
第 28 章 - Go语言 Web 开发入门
java·开发语言·前端·python·sql·golang·前端框架
幼儿园的小霸王2 小时前
通过socket设置版本更新提示
前端·vue.js·webpack·typescript·前端框架·anti-design-vue
疯狂的沙粒2 小时前
对 TypeScript 中高级类型的理解?应该在哪些方面可以更好的使用!
前端·javascript·typescript