React Fiber架构的原理和工作模式
React16 => React引入Fiber架构,解决过去更新机制的问题 =》 在长时间的更新过程中,主线程会被阻塞,导致应用无法即时响应用户输入。
核心内容: 【Fiber是什么】【Fiber的底层原理】
Fiber是什么?
当编写React组件并使用JSX时,React在底层会将JSX转换为元素的对象结构
转换的过程是一个工具链完成,由Babel或类似JS编译器处理JSX的编译工作
-
JSX的本质
JSX是JS的语法扩展,和HTML有点像,但实际是JS代码,在React中,JSX会被转换为标准的JS函数调用,即React.createElement() -
转换机制
-
React.createElement是React提供的核心函数,用来创建虚拟DOM(React Element)
-
第一个参数:创建的HTML标签,eg: h1
-
第二个参数:属性对象 props, 上图无属性即null
-
第三个参数:标签包含的内容,eg: 'Hello, world'
Babel使用插件 eg: @babel/plugin-transform-react-jsx完成转换
{ "presets": ["@babel/preset-react"] }
运行完babel之后,输出的代码就是React.createElement形式
Babel对JSX的转换逻辑
React.createElement作用:【构建虚拟DOM树】
React使用React.createElement来构建虚拟DOM树,即一个轻量级的JS对象,用来描述真实的DOM结构
虚拟 DOM 的结构,React 会根据这个结构最终生成并更新真实的 DOM。
- JSX 是语法糖,它最终被编译为 React.createElement 调用。
- React.createElement 的核心作用是生成描述 UI 的虚拟 DOM。
- Babel 等工具负责处理 JSX 的转换,使开发更直观高效。
虚拟DOM是React根据JSX创建一种内部实例,用来追踪该组件的所有信息和状态。
早期称之为实例或虚拟DOM对象,在Fiber架构中,称之为【Fiber】
Fiber是一个JS对象,代表React的一个工作单元,用来追踪对应组件的所有信息和状态
Eg: Fiber对象结构
{ type: 'h1', // 组件类型(标签类型 key: null, // React key props: {}, // 输入的props state: {}, // 组件的state child: Fiber | null, // 第一个子元素的Fiber sibling: Fiber | null, // 下一个兄弟元素的Fiber return: Fiber | null, // 父元素的Fiber ... 其他属性 }
React的工作是沿着Fiber树进行,试图完成每个Fiber工作,eg: 比较新旧props决定是否更新组件等
如果主线程有工作(eg: 响应用户输入)则React可随时终止当前工作并返回执行主线程上的任务。
Fiber不仅是代表组件的一个JS对象也是React调度和更新机制的核心
为啥要Fiber???
React16之前的版本,使用递归的方式处理组件更新,称之为堆栈调和(Stack Reconciliation)
该方法一旦开始无法中断,必须直到整个组件树都遍历完。
当遇到大量数据或负责视图组件时可能导致主线程阻塞,React应用无法即时响应用户输入或其他高优先级任务的执行。
React16 Fiber的引入解决了上述问题, Fiber是React创建的一个带有链接关系的DOM树 每个Fiber代表一个工作单元,React处理任何Fiber之前,会判断当前是否有足够时间完成该工作,并可以在必要时中断当前工作。
React16之前版本采用的循环递归更新组件
function render(element, rootParent) { console.log('output-> render 执行',) // 根据元素类型创建节点 let dom = document.createElement(element.type) rootParent.appendChild(dom) // 添加dom的属性 Object.keys(element.props) .filter(prop => prop !== 'children') .forEach(attr => { dom[attr] = element.props[attr] }) // 将子元素进行渲染 if(Array.isArray(element.props.children)) { element.props.children.forEach(child => { render(child, dom) }) } else { dom.innerHTML = element.props.children } }
Fiber的结构
function FiberNode( this: $FlowFixMe, pendingProps: mixed, key: null | string, mode: TypeOfMode, ) { // 基本属性 this.tag = tag; // 描述此Fiber的启动模式的值(LegacyRoot=0;ConcurrentRoot=1) this.key = key; // React key this.elementType = null; // 描述React元素的类型,eg: JSX<App/> 则elementType是App this.type = null; // 组件类型 this.stateNode = null; // 对于类组件,这是类组件的实例;对于DOM元素,是对应的DOM节点 // Fiber链接 this.child = null; // 指向第一个子Fiber this.sibling = null; // 指向其兄弟Fiber this.return = null; // 指向父Fiber this.index = 0; // 子Fiber中的索引位置 this.ref = null; // 如果组件上有ref属性则该属性指向它 this.refCleanup = null; // 如果组件上的ref属性在更新中被删除或更改,此字段会用于追踪需要清理的旧ref // Props & State this.pendingProps = pendingProps; // 正在等待处理的新的props this.memoizedProps = null; // 上一次渲染时的props this.updateQueue = null; // 一个队列,包含了该Fiber上的状态更新和副作用 this.memoizedState = null; // 上一次渲染时的state this.dependencies = null; // 该Fiber订阅的上下文或其他资源的描述 // 工作模式 this.mode = mode; // 描述Fiber工作模式的标志(eg: Concurrent模式、Blocking模式等 // Effects this.flags = NoFlags; // 该Fiber发生的副作用标志 this.subtreeFlags = NoFlags; // 描述该Fiber子树中发生的副作用的标志 this.deletions = null; // 在commit阶段要删除的子Fiber数组 this.lanes = NoLanes; // 与React的并发模式有关的调度概念。 this.childLanes = NoLanes; // 与React的并发模式有关的调度概念。 this.alternate = null; // Current Tree和Work-in-progress (WIP) Tree的互相指向对方tree里的对应单元 }
Fiber是一个更加强大的虚拟DOM
Fiber工作原理
Fiber工作原理最核心的:可以中断和恢复,该特性增强了React的【并发性和响应性】
实现可中断和恢复的原因: Fiber的提供的信息让React可以追踪工作进度、管理调度和同步更新到DOM
Fiber工作原理的关键点:
-
工作单元:每个Fiber节点代表一个工作单元,所有Fiber节点共同组成一个Fiber链表树(有链接属性和树的结构),让React可细粒度控制节点的行为
-
链接属性:child、sibling和return字段构成Fiber之间的链接关系,使得React能够遍历组件树并知道从哪里开始、继续或停止工作
-
双双缓冲技术:React在更新时,会根据现有的Fiber树(Current Tree)创建一个新的临时树(Work-in-progress (WIP) Tree),WIP-Tree包含了当前更新受影响的最高节点直至其所有子孙节点。Current Tree是当前显示在页面上的视图,WIP-Tree则是在后台进行更新,WIP-Tree更新完成后会复制其它节点,并最终替换掉Current Tree,成为新的Current Tree。因为React在更新时总是维护了两个Fiber树,所以可以随时进行比较、中断或恢复等操作,而且这种机制让React能够同时具备拥有优秀的渲染性能和UI的稳定性。
-
State和Props: memoizedProps、pendingProps 和 memoizedState 字段让React知道组件的上一个状态和即将应用的状态。通过比较这些值,React可以决定组件是否需要更新,从而避免不必要的渲染,提高性能。
-
副作用的追踪:flags 和 subtreeFlags 字段标识Fiber及其子树中需要执行的副作用,例如DOM更新、生命周期方法调用等。React会积累这些副作用,然后在Commit阶段一次性执行,从而提高效率。
Fiber的工作流程 分为两个阶段: 【Reconciliation-调和】【Commit-提交】
第一 调和
目标: 确定哪些部分的UI需要更新。
原理: 这是React构建工作进度树的阶段,会比较新的props和旧的Fiber树来确定哪些部分需要更新。
调和细分为2个小阶段
- 创建与标记更新节点:beginWork
- 判断Fiber节点是否要更新:
- 判断Fiber子节点是更新还是复用:
- 收集副作用列表:completeUnitOfWork和completeWork
第二 提交
目标: 更新DOM并执行任何副作用。
原理: 遍历在Reconciliation阶段创建的副作用列表进行更新。
码里 commitRoot 和 commitRootImpl 是提交阶段的入口方法,在两个方法中,可以看出来提交阶段也有三个核心小阶段
- 遍历副作用列表:BeforeMutation
- 正式提交:CommitMutation
- 处理layout effects:commitLayout
从源码里我们可以看到,一旦进入提交阶段后,React是无法中断的。
https://weijunext.com/article/dive-into-react-fiber#第二阶段:Commit(提交)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2022-11-23 CSS实现标题和图片混合