react 中 ReactElement 与 Fiber 的关系

1,113 阅读3分钟

阅读 react 之前你需要了解的几种对象

react 之中有很多重要的类型,在整个源码中扮演者非常重要的角色。本文旨在对这几种类型的作用以及关系进行分析,让你阅读 react 源码事半功倍。

JSX & ReactElement

jsx 是平时使用 react 进行开发能接触到最多的概念。在 react 官网上也强调了,jsx 并不是什么模板语言,而是语法糖。react 会使用 React.createElementjsx 转换为 ReactElement 对象。例如:

<div className="banner">
    <p> 
        hello world
    </p>
</div>

实际上会转换为:

React.createElement('div', {'className': 'banner'}, React.createElement('p', null, 'hello world'))

React.createElement 一共有 3 个参数:

  • type: 表示 ReactElement 的类型,如果是 dom 类型则是对应的字符串例如 'div', 'p'。如果是 class 组件则是对应的组件类,如果是函数式组件则是对应的组件函数。
  • props: 组件的属性
  • children: 组件的子组件,可以是多个

返回值为 ReactElement 类型的对象,其包含的属性如下:

const element = {
    // 表示这是一个 react element 对象
    $$typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type, // createElement 传入的 type
    key: key, // createElement 传入的 props 中的 key
    ref: ref, // createElement 传入的 props 中的 ref
    props: props, // createElement 传入的 props

    // Record the component responsible for creating this element.
    _owner: owner,
  };

打印上面的例子创建的对象:

image.png 这里有一点需要注意的是,children 虽然作为 createElement 中一个独立的参数传入,但是 ReactElement 并没有一个单独的 children 属性来代表子元素。传入的 children 最终会合并到 props 中,也就是 props.children。

Fiber

Fiber 对象是 Fiber 架构中的基础。Fiber 对象在内存中描述了用户编写的界面,react 会使用 fiber 对象进行 diff 并将 diff 结果应用到真实 dom 上。因此 fiber 对象在 react 中其着虚拟 DOM的作用。Fiber 对象实际上是通过 ReactElement 对象产生的。在 react 中有一个 createFiberFromElement 来通过 ReactElement 创建 fiber。因此 jsx, react, element 的关系为:

jsx ---- creatElement ----> ReactElement ---- createFiberFromElement ----> Fiber

Fiber 类型在 源码中的定义为:

export type Fiber = {

  tag: WorkTag, // 表示 fiber 的类型

  key: null | string, // 从 ReactElement 获取的 key


  elementType: any, // 从 ReactElement 获取的 type

  type: any, // 从 ReactElement 获取的 type
 
  stateNode: any, 

  return: Fiber | null, // 该 fiber 的父节点

  child: Fiber | null, // 该 fiber 的第一个子节点
  sibling: Fiber | null, // 该 fiber 的第一个兄弟节点
  index: number, // 


  ref:
    | null
    | (((handle: mixed) => void) & {_stringRef: ?string, ...})
    | RefObject, // fiber 关联的 ref

  pendingProps: any, // 更新后的 props
  memoizedProps: any, // 更新前的 props

  // A queue of state updates and callbacks.
  updateQueue: mixed, // 

  // The state used to create the output
  memoizedState: any,

  // Dependencies (contexts, events) for this fiber, if it has any
  dependencies: Dependencies | null,

  mode: TypeOfMode,

  flags: Flags,
  subtreeFlags: Flags,
  deletions: Array<Fiber> | null,


  nextEffect: Fiber | null,
  firstEffect: Fiber | null,
  lastEffect: Fiber | null,

  lanes: Lanes,
  childLanes: Lanes,

  // This is a pooled version of a Fiber. Every fiber that gets updated will
  // eventually have a pair. There are cases when we can clean up pairs to save
  // memory if we need to.
  alternate: Fiber | null,

  // Time spent rendering this Fiber and its descendants for the current update.
  // This tells us how well the tree makes use of sCU for memoization.
  // It is reset to 0 each time we render and only updated when we don't bailout.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualDuration?: number,

  // If the Fiber is currently active in the "render" phase,
  // This marks the time at which the work began.
  // This field is only set when the enableProfilerTimer flag is enabled.
  actualStartTime?: number,

  // Duration of the most recent render time for this Fiber.
  // This value is not updated when we bailout for memoization purposes.
  // This field is only set when the enableProfilerTimer flag is enabled.
  selfBaseDuration?: number,

  // Sum of base times for all descendants of this Fiber.
  // This value bubbles up during the "complete" phase.
  // This field is only set when the enableProfilerTimer flag is enabled.
  treeBaseDuration?: number,

};

这里列举几个比较复杂的属性:

  • stateNode:
    1. 类组件: 组件实例
    2. 函数组件: null
    3. DOM 组件: 该 dom 对应的真实 dom 对象
  • memoizedState:
    1. 类组件: 组件 state
    2. 函数组件: hook 链的第一个 hook
    3. DOM 组件: null
  • updateQueue:
    1. 类组件: 存放 setState 产生的 update 以及更新优先级相关内容的 updateQueue
    2. 函数组件: null
    3. DOM 组件: 该 dom 组件 diff 之后的属性。会以 [key1, value1, key2, values,...,keyn, valuen] 的方式存储属性。例如该 DOM 有一个新的属性 class=banner,则为 ["className", "banner"]

关系图

类组件组件实例/ReactElement 以及 Fiber 的关系比较复杂,这里单独画一张关系图来表示: Untitled Diagram.drawio.png