想想react会怎么做(3)之 Fiber

68 阅读4分钟

前言

上一篇简单介绍了一下ReactDom创建根节点的逻辑,我看看到一个类型:FiberRoot,要讲FiberRoot也要先说一下Fiber了,所以这里我们先来简单介绍一下Fiber,它的属性很多,我觉得我们目前只需要大概了解一下即可,后续有遇到的地方再详细了解。

为什么需要Fiber

我们一起来回忆一下我们之前说的ReactElement数据结构,它的结构很简单

export type ReactElement = {
  $$typeof: any,
  type: any,
  key: any,
  ref: any,
  props: any,
};

除了有个$$typeof标识之外,其他的type,key,ref,props基本只对jsx编译后的结果做了简单的转换(提取出了key,ref,其他属性放入props)

那么它有什么问题呢?

  • 无法表示它与各个节点之间的关系(父亲,兄弟节点)
  • 字段过于简单,无法存储状态等信息,无法作为一个独立的工作单元

所以,我们需要一种新的数据结构,它就需要弥补ReactElement的缺点,它就是Fiber,它也是虚拟DOM在react中的实现

Fiber的数据结构

那么Fiber的数据结构是什么样的呢?

export type Fiber = {
  // Fiber标识,number类型
  // 比如0表示FunctionComponent,3表示HostRoot
  tag: WorkTag,

  // 元素key,也就是ReactElement上的key
  key: null | string,

  // 元素类型,也就是ReactElement上的type,比如"div"
  elementType: any,

  // Fiber真实类型,和elementType基本一样
  type: any,

  // 实例对象,比如说类组件的实例,RootFiber的FiberRoot
  stateNode: any,

  // 指向父节点Fiber
  return: Fiber | null,
  
  // 指向孩子节点Fiber
  child: Fiber | null,

  // 指向兄弟节点Fiber
  sibling: Fiber | null,

  // 同层级Fiber的索引,没有兄弟节点的话是0
  // index,key要一起用于diff
  index: number,

  // ReactElement的ref,也就是我们开发中使用的ref
  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject,

  refCleanup: null | (() => void),

  // 新Props
  pendingProps: any,

  // 旧Props
  memoizedProps: any,

  // 状态更新队列,setState就存在这上面
  updateQueue: mixed,

  // 旧State
  memoizedState: any,

  // contexts, events依赖
  dependencies: Dependencies | null,

  // 渲染模式,2进制表示
  // 比如0b0000000表示NoMode,0b0000001表示ConcurrentMode
  mode: TypeOfMode,

  // 副作用标记,二进制表示
  // 0b0000000000000000000000000000表示NoFlags(不更新,diff时跳过)
  // 0b0000000000000000000000000010表示Placement(插入)
  // 0b0000000000000000000000000100表示Update(插入更新)
  flags: Flags,

  // 子树副作用标识
  subtreeFlags: Flags,

  // 子节点中需要删除的节点
  deletions: Array<Fiber> | null,

  // 待完善
  lanes: Lanes,
  childLanes: Lanes,

   /**
   * 双缓冲:防止数据丢失,提高效率(之后Dom-diff的时候可以直接比较或者使用
   * React在进行diff更新时,会维护两颗fiber树,一个是当前正在展示的,一个是
   * 通过diff对比后要更新的树,这两棵树中的每个fiber节点通过 alternate 属性
   * 进行互相指向。
   */
  alternate: Fiber | null,
  
  actualDuration?: number,

  actualStartTime?: number,

  selfBaseDuration?: number,

  treeBaseDuration?: number,
};

FiberRoot

我这里就列出一些基础的属性,其他的我们后续如果用到再补充吧

type BaseFiberRootProperties = {
  // 标记类型:  0|1分别对应 LegacyRoot|ConcurrentRoot
  tag: RootTag,

  // 根节点元素,createRoot接受的参数
  containerInfo: Container,

  // 指向RootFiber,也就是根节点的Fiber
  current: Fiber,

  // 已经完成的标记的 FiberRoot对象, commit的对象
  finishedWork: Fiber | null,

  // 待处理的lane集合
  pendingLanes: Lanes;

  // 挂起的lane集合
  suspendedLanes: Lanes;
  
  // 已经标记过的的lane集合
  pingedLanes: Lanes;
  
  // 待清除的effect
  pendingPassiveEffects: PendingPassiveEffects
  // ...
};

FiberRoot,RootFiber的关系?

听起来有点拗口,但是其实抓住问题的本质就行:

FiberRoot本质是root,也就是所有Fiber的根节点,它的数据结构和Fiber不同

RootFiber本质是fiber,指的是根部的Fiber节点,它就是一个Fiber

FiberRoot和RootFiber的指向关系如图:

image.png

Fiber的遍历流程

通过上面的数据结构我们可以知道,Fiber是一个链表结构,可以组成一个树,其遍历的指针就在 child, sibling, return 三个属性上,child指向子Fiber,sibling指向兄弟Fiber,return指向父亲Fiber,它的遍历过程其实就是一个深度优先的遍历(DFS)

  1. 如果有child节点就优先遍历child节点,直到child节点为null为止
  2. 如果child节点为null就遍历sibling节点,如果sibling节点有child节点就继续遍历child
  3. sibling节点的child遍历完之后就遍历sibling 的sibling
  4. sibling遍历完之后就遍历return节点
  5. 直到回到根节点

image.png