React Source Code

    4.React Fiber架构:链表、Fiber节点与Fiber树

    Published
    November 20, 2022
    Reading Time
    5 min read
    Author
    Felix

    React Fiber是React的核心架构,它基于链表结构组织和管理组件树。本文将从链表基础开始,逐步深入探讨Fiber节点、Fiber树和双缓冲机制,以及它们之间的关系。

    1. 链表基础

    在深入Fiber架构之前,我们需要先理解链表这一基础数据结构。链表是由一系列节点组成的线性集合,每个节点包含数据和指向下一个节点的指针。

    1.1 链表的基本操作及复杂度分析

    以下是链表的基本操作实现及其时间复杂度分析:

    class Node {
      constructor(data) {
        this.data = data
        this.next = null
      }
    }
    
    class LinkedList {
      constructor() {
        this.head = null
      }
    
      // 在链表末尾添加节点 - O(n)
      append(data) {
        const newNode = new Node(data)
        if (!this.head) {
          this.head = newNode
          return
        }
        let current = this.head
        while (current.next) {
          current = current.next
        }
        current.next = newNode
      }
    
      // 在链表开头插入节点 - O(1)
      prepend(data) {
        const newNode = new Node(data)
        newNode.next = this.head
        this.head = newNode
      }
    
      // 删除指定数据的节点 - O(n)
      delete(data) {
        if (!this.head) return
        if (this.head.data === data) {
          this.head = this.head.next
          return
        }
        let current = this.head
        while (current.next) {
          if (current.next.data === data) {
            current.next = current.next.next
            return
          }
          current = current.next
        }
      }
    
      // 遍历链表 - O(n)
      traverse() {
        let current = this.head
        while (current) {
          console.log(current.data)
          current = current.next
        }
      }
    }
    

    复杂度分析:

    • 插入操作:
      • 在头部插入(prepend): O(1)
      • 在尾部插入(append): O(n)
    • 删除操作: 平均O(n)
    • 查找操作: O(n)

    链表的主要优势在于插入和删除操作的灵活性,特别是在已知位置的插入和删除可以达到O(1)的时间复杂度。

    2. Fiber节点结构

    Fiber节点是React内部用来表示组件的核心数据结构。每个Fiber节点对应一个React元素,包含了该元素的类型、属性、状态等信息。Fiber节点构成了一个双向链表树结构,这种结构允许React实现可中断的渲染过程。

    2.1 Fiber节点的完整结构

    以下是Fiber节点的完整结构:

    function FiberNode(
      tag: WorkTag,
      pendingProps: mixed,
      key: null | string,
      mode: TypeOfMode,
    ) {
      // Instance
      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;
    
      this.pendingProps = pendingProps;
      this.memoizedProps = null;
      this.updateQueue = null;
      this.memoizedState = null;
      this.dependencies = null;
    
      this.mode = mode;
    
      // Effects
      this.flags = NoFlags;
      this.subtreeFlags = NoFlags;
      this.deletions = null;
    
      this.lanes = NoLanes;
      this.childLanes = NoLanes;
    
      this.alternate = null;
    
      // ... 性能计时相关代码(省略)
    
      // ... 开发环境相关代码(省略)
    }
    

    2.2 Fiber节点属性解释

    1. Instance 相关

      • tag: 标识 Fiber 节点类型(如函数组件、类组件、原生 DOM 等)
      • key: 用于优化更新过程的唯一标识符
      • elementType: 元素的类型(如对于 DOM 元素是字符串,对于组件是函数或类)
      • type: 与 elementType 类似,但可能会被特殊处理(如在懒加载时)
      • stateNode: 保存与 Fiber 相关的本地状态(如对于 DOM 元素,它是实际的 DOM 节点)
    2. Fiber 树结构

      • return: 指向父 Fiber 节点
      • child: 指向第一个子 Fiber 节点
      • sibling: 指向下一个兄弟 Fiber 节点
      • index: 在父节点的子节点列表中的索引
    3. 数据相关

      • ref: 如果元素定义了 ref,这里存储 ref 的值
      • pendingProps: 新的待处理的 props
      • memoizedProps: 上一次渲染时的 props
      • updateQueue: 存储状态更新的队列
      • memoizedState: 上一次渲染时的 state
      • dependencies: 依赖项(如 context、事件等)
    4. 模式与副作用

      • mode: 标识当前 Fiber 树的渲染模式(如 Concurrent、Strict 等)
      • flags: 标记需要执行的副作用(如需要插入、更新、删除等)
      • subtreeFlags: 子树中的副作用标记
      • deletions: 需要删除的子节点列表
    5. 调度相关

      • lanes: 表示更新的优先级
      • childLanes: 子树中的优先级
    6. 其他

      • alternate: 指向内存中的另一个 Fiber,用于双缓冲

    2.3 创建Fiber节点的源码

    React 使用 createFiberFromElement 函数来创建 Fiber 节点:

    function createFiberFromElement(element: ReactElement, mode: TypeOfMode, lanes: Lanes): Fiber {
      let owner = null;
      if (__DEV__) {
        owner = element._owner;
      }
      const type = element.type;
      const key = element.key;
      const pendingProps = element.props;
      const fiber = createFiberFromTypeAndProps(
        type,
        key,
        pendingProps,
        owner,
        mode,
        lanes,
      );
      if (__DEV__) {
        fiber._debugSource = element._source;
        fiber._debugOwner = element._owner;
      }
      return fiber;
    }
    

    这个函数根据 React 元素创建对应的 Fiber 节点,处理元素的类型、key、props 等信息,并创建一个新的 Fiber 对象。

    2.4 深入理解React元素

    React元素是描述UI中一部分的普通JavaScript对象。它们是React应用的最小构建块,用于表示界面上应该渲染的内容。

    2.4.1 React元素的结构

    一个典型的React元素对象结构如下:

    const element = {
      type: 'div',
      props: {
        className: 'container',
        children: [
          { type: 'h1', props: { children: 'Hello, World!' } },
          { type: 'p', props: { children: 'This is a paragraph.' } }
        ]
      },
      key: null,
      ref: null,
      $$typeof: Symbol.for('react.element'),
      _owner: null,
      _store: {}
    }
    

    2.4.2 React元素的主要属性

    1. type: 指定了React元素的类型。它可以是:

      • 字符串(如 'div', 'span')表示原生DOM元素
      • 函数或类表示自定义React组件
      • React内置组件(如React.Fragment)
    2. props: 包含了传递给组件的所有属性,包括:

      • 常规属性(如className, style等)
      • 特殊属性children,表示子元素
    3. key: 用于在列表中唯一标识元素,帮助React高效地更新DOM

    4. ref: 用于获取对DOM节点或组件实例的引用

    5. $$typeof: 一个Symbol,用于标识这是一个React元素,防止XSS攻击

    6. _owner: 内部使用,指向创建该元素的组件

    2.4.3 创建React元素

    通常,我们使用JSX来创建React元素:

    const element = (
      <div className="container">
        <h1>Hello, World!</h1>
        <p>This is a paragraph.</p>
      </div>
    )
    

    JSX会被Babel等工具转译为React.createElement()调用:

    const element = React.createElement(
      'div',
      { className: 'container' },
      React.createElement('h1', null, 'Hello, World!'),
      React.createElement('p', null, 'This is a paragraph.')
    )
    

    2.4.4 React元素与Fiber节点的关系

    • React元素是描述UI的静态结构,而Fiber节点是React内部用于管理组件树和执行更新的动态数据结构。
    • 每个React元素最终都会对应到一个Fiber节点。

    3. Fiber树结构

    Fiber树是由Fiber节点构成的树形结构,它反映了React组件的层次关系。Fiber树使用三个主要指针构建链表结构:

    1. child: 指向第一个子节点
    2. sibling: 指向下一个兄弟节点
    3. return: 指向父节点

    3.1 Fiber树的构建过程

    以下是一个简化的Fiber树构建过程:

    function createFiberFromElement(element) {
      const { type, props } = element
      const fiber = new FiberNode(getFiberTag(type), props, element.key, NoMode)
      fiber.type = type
      return fiber
    }
    
    function reconcileChildrenArray(returnFiber, currentFirstChild, newChildren) {
      let prevSibling = null
      let oldFiber = currentFirstChild
      let newFiber = null
      let index = 0
    
      for (; index < newChildren.length; index++) {
        const newChild = newChildren[index]
        if (shouldTrackSideEffects && oldFiber && newChild && !sameNode(newChild, oldFiber)) {
          deleteChild(returnFiber, oldFiber)
        }
    
        if (oldFiber) {
          oldFiber = oldFiber.sibling
        }
    
        newFiber = createFiberFromElement(newChild)
        newFiber.return = returnFiber
    
        if (index === 0) {
          returnFiber.child = newFiber
        } else {
          prevSibling.sibling = newFiber
        }
        prevSibling = newFiber
      }
    }
    

    3.2 实际代码例子

    考虑以下React组件结构:

    function App() {
      return (
        <div>
          <Header />
          <Main>
            <Article />
            <Sidebar />
          </Main>
          <Footer />
        </div>
      )
    }
    

    对应的Fiber树结构可能如下:

    const AppFiber = {
      tag: FunctionComponent,
      type: App,
      child: DivFiber
      // ...其他属性
    }
    
    const DivFiber = {
      tag: HostComponent,
      type: 'div',
      return: AppFiber,
      child: HeaderFiber
      // ...其他属性
    }
    
    const HeaderFiber = {
      tag: FunctionComponent,
      type: Header,
      return: DivFiber,
      sibling: MainFiber
      // ...其他属性
    }
    
    const MainFiber = {
      tag: FunctionComponent,
      type: Main,
      return: DivFiber,
      child: ArticleFiber,
      sibling: FooterFiber
      // ...其他属性
    }
    
    const ArticleFiber = {
      tag: FunctionComponent,
      type: Article,
      return: MainFiber,
      sibling: SidebarFiber
      // ...其他属性
    }
    
    const SidebarFiber = {
      tag: FunctionComponent,
      type: Sidebar,
      return: MainFiber
      // ...其他属性
    }
    
    const FooterFiber = {
      tag: FunctionComponent,
      type: Footer,
      return: DivFiber
      // ...其他属性
    }
    

    这个例子展示了Fiber节点如何通过childsiblingreturn指针相互连接,形成一个完整的树结构。

    4. 双缓冲机制

    React Fiber 架构中的双缓冲机制是优化渲染性能的关键策略。

    4.1 双缓冲的核心概念

    • current 树: 当前在屏幕上显示的 UI 对应的 Fiber 树。
    • workInProgress 树: 用于构建下一个状态的 Fiber 树。

    每个 Fiber 节点都有一个 alternate 属性,指向另一棵树中对应的节点。

    4.2 双缓冲的创建和更新流程

    1. 初始渲染:

      • React 创建 current 树。
      • 同时创建一个对应的 workInProgress 树,但不使用。
    2. 更新过程:

      • 当有更新时,React 不会创建新的 workInProgress 树。
      • 而是重用已存在的 workInProgress 树,基于 current 树进行更新。
    3. 树的切换:

      • 更新完成后,workInProgress 树变为新的 current 树。
      • 原来的 current 树变为新的 workInProgress 树,等待下次更新。

    4.3 源码实现

    React 中的 createWorkInProgress 函数展示了这一机制:

    function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
      let workInProgress = current.alternate;
      if (workInProgress === null) {
        // 首次渲染时创建 workInProgress
        workInProgress = createFiber(
          current.tag,
          pendingProps,
          current.key,
          current.mode,
        );
        workInProgress.elementType = current.elementType;
        workInProgress.type = current.type;
        workInProgress.stateNode = current.stateNode;
    
        workInProgress.alternate = current;
        current.alternate = workInProgress;
      } else {
        // 后续更新时重用已存在的 workInProgress
        workInProgress.pendingProps = pendingProps;
        workInProgress.type = current.type;
        workInProgress.flags = NoFlags;
        // ... 重置其他属性
      }
    
      // 复制必要的属性
      workInProgress.child = current.child;
      workInProgress.memoizedProps = current.memoizedProps;
      workInProgress.memoizedState = current.memoizedState;
      workInProgress.updateQueue = current.updateQueue;
      // ... 复制其他属性
    
      return workInProgress;
    }
    

    4.4 实际例子:局部替换和双缓存

    让我们通过一个具体的例子来说明 React 如何使用双缓存和局部替换来更新 UI。

    假设我们有以下组件结构:

    function App() {
      const [count, setCount] = useState(0)
      return (
        <div>
          <Header />
          <Main count={count} />
          <Footer />
        </div>
      )
    }
    
    function Main({ count }) {
      return (
        <div>
          <h2>Count: {count}</h2>
          <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
      )
    }
    

    初始渲染后,Fiber 树结构可能如下:

    // current 树
    const currentRoot = {
      tag: HostRoot,
      child: {
        tag: FunctionComponent,
        type: App,
        child: {
          tag: HostComponent,
          type: 'div',
          child: {
            tag: FunctionComponent,
            type: Header,
            sibling: {
              tag: FunctionComponent,
              type: Main,
              memoizedState: { count: 0 },
              sibling: {
                tag: FunctionComponent,
                type: Footer
              }
            }
          }
        }
      }
    }
    
    // workInProgress 树(初始为 null)
    let workInProgressRoot = null
    

    当用户点击 "Increment" 按钮时,React 会触发更新。这时,React 会创建或重用 workInProgress 树:

    // 更新过程中的 workInProgress 树
    workInProgressRoot = {
      tag: HostRoot,
      child: {
        tag: FunctionComponent,
        type: App,
        child: {
          tag: HostComponent,
          type: 'div',
          child: {
            tag: FunctionComponent,
            type: Header,
            sibling: {
              tag: FunctionComponent,
              type: Main,
              memoizedState: { count: 1 }, // 注意这里的状态更新
              sibling: {
                tag: FunctionComponent,
                type: Footer
              }
            }
          }
        }
      }
    }
    

    在这个过程中,React 只需要更新 Main 组件对应的 Fiber 节点,其他节点可以直接复用 current 树中的节点。这就是局部替换的体现。

    更新完成后,React 会交换 current 和 workInProgress 的引用:

    // 交换引用
    const temp = currentRoot
    currentRoot = workInProgressRoot
    workInProgressRoot = temp
    

    这样,新的 current 树就反映了更新后的 UI 状态,而旧的 current 树成为了新的 workInProgress 树,等待下一次更新。

    这个例子展示了 React 如何通过双缓存机制和局部替换来高效地管理更新。它只修改必要的部分,复用大部分现有结构。