React 中的 VDOM 和 Fiber 到底是什么?

1,292 阅读4分钟

最近正好在学 React18 的源码,借此机会来归纳一些学习到的相关知识点。

1. 认识 VDOM

VDOM(Virtual DOM)是一种编程概念。在这个概念里,UI 以一种理想化的,或者说“虚拟的”表现形式被保存于内存中,并通过 ReactDOM 这样的类库使之渲染为 “真实的” DOM。这一过程叫做协调(Reconcile)。

在 React 中,它赋予了 React 声明式的 API:你只需要告诉 React 希望让 UI 是什么状态,React 就确保 DOM 匹配该状态。

2. 认识 Fiber

2.1 什么是 Fiber

Fiber 一个数据结构,指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。

这里有一个介绍视频

React Conf 2017 Fiber Video

2.2 为什么需要 Fiber

对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验

为了解决这一问题,react团队重写了react的核心算法reconciliation,在v16中发布,为了区分reconciler(协调器),将之前的reconciler称为stack reconciler,之后称作fiber reconciler(简称:fiber)

2.3 为什么要任务分解

就是为了解决上述的问题【主线程被持续占用,使得周期性任务无法立即得到处理

2.4 实现 Fiber 解决的各种问题

  • 增量渲染(把渲染任务拆分成块,匀到多帧)
  • 更新时能够暂停,终止,复用渲染任务
  • 给不同类型的更新赋予优先级
  • 并发方面新的基础能力
  • 更加流畅

image.png

image.png

2.5 Fiber 的构建与任务执行

2.5.1 组件类型

  • 文本节点
  • 原生节点
  • 函数组件
  • 类组件
  • ...

在源码中,各种节点的定义位于 ReactWorkTags.js

// src/react/packages/react-reconciler/src/ReactWorkTags.js
// 函数组件
export const FunctionComponent = 0;
// 类组件
export const ClassComponent = 1;
// 不明确的组件(类组件或者函数组件)
export const IndeterminateComponent = 2; 
// 树的根节点,内部可以嵌套其他的节点
export const HostRoot = 3; 
// A subtree. Could be an entry point to a different renderer.
export const HostPortal = 4;
// 原生组件
export const HostComponent = 5;
// 文本节点
export const HostText = 6;
// 空节点
export const Fragment = 7;
...

2.5.2 fiber 结构

如图:

image.png

代码如下:

export default function createFiber(vnode, returnFiber) {
  const fiber = {
    // 节点类型
    type: vnode.type,
    // diff 时需要用到的 key
    key: vnode.key,
    // 从父组件传递过来的数据
    props: vnode.props,
    // 保存的是当前组件的 DOM 节点或 class 实例
    stateNode: null, 
    // 第一个子fiber
    child: null, 
    // 下一个兄弟fiber
    sibling: null, 
    // 父fiber
    return: returnFiber, 
    // 标记节点是什么类型的
    flags: Placement,
    // 老节点
    alternate: null,
    // 要删除子节点 null或者[]
    deletions: null, 
    //当前层级下的下标,从0开始
    index: null, 
  };
  
  const { type } = vnode
  
  if(isStr(type)) {
      fiber.tag = HostComponent
  } else if(isFn(type)){
      fiber.tag = FunctionComponent
  }
  
  return fiber;
}

3. Fiber 执行阶段

  • Reconcile(render) 阶段
  • Commit 阶段

3.1 Reconcile 阶段

这个阶段可以认为是 diff 阶段,是可中断的。这个阶段会找出所有变更的节点,例如节点增删改等等,这些变更在 React 中被称为 Effect(副作用)

3.2 Commit 阶段

将上一个阶段计算出来后将需要处理的副作用一次性执行一次,这个阶段不可被中断,必须同步且一次执行完。

3.3 注意点

这两个阶段后续会重点讲解,这里先理解下大致的原理。

4. 组件更新机制

React 组件的更新流程是:

找出执行当前任务的节点并更新,随后通过 子 => 兄 => 叔 的关系【深度遍历】进行查找下一个需要更新的节点

 // ReactWorkLoop.ts
 let wip = null;

function performUnitOfWork() {
  // todo 1. 执行当前任务wip(更新当前节点)
  // 判断wip是什么类型的组件
  const { type } = wip;
  
  switch(type) {
       case HostComponnet:
          updateHostComponent(wip)
          break
       case FunctionComponent:
          updateFunctionComponent(wip)
          break
       case ClassComponent:
          updateClassComponent(wip)
          break
       case Fragment:
          updateFragmentComponent(wip)
          break
       case HostText:
          updateHostTextComponent(wip)
          break
      default:
          break
  }
 
  // todo 2. 更新wip【找到下一个更新节点】
  if (wip.child) {
    wip = wip.child;
    return;
  }
  let next = wip;
  while (next) {
    if (next.sibling) {
      wip = next.sibling;
      return;
    }
    next = next.return;
  }
  wip = null;
}

总结

本文介绍了 VDOM 和 Fiber的一些基本概念,以及 React18 源码中对组件的更新机制 同时也纠正一个知识误区:VDOM 和 Fiber 是一个同一个东西

VDOM(Virtual DOM)是一种编程概念。

Fiber 其实是一个数据结构,指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。

如有存在的错误,可以留言提出哦~