Wsh's blog Wsh's blog
首页
  • 基础知识
  • ArkUI
  • UIAbility
  • 组件通信方式
  • 前端缓存
  • React
  • typescript
  • javascript
  • flutter
  • node
  • webpack
web3D😉
宝库📰
  • 分类
  • 标签
  • 归档
龙哥的大🐂之路 (opens new window)
GitHub (opens new window)

wsh

热爱前端的程序媛
首页
  • 基础知识
  • ArkUI
  • UIAbility
  • 组件通信方式
  • 前端缓存
  • React
  • typescript
  • javascript
  • flutter
  • node
  • webpack
web3D😉
宝库📰
  • 分类
  • 标签
  • 归档
龙哥的大🐂之路 (opens new window)
GitHub (opens new window)
  • react基础

  • react更新特性

  • react进阶

    • react 架构演变
    • react render阶段
      • react commit阶段
      • react diff
      • react 状态更新
      • react hook
      • Concurrent Mode
    • react
    • react进阶
    2022-04-19
    目录

    react render阶段

    # 01. 整体流程概述

    架构体系体现如图所示:

    workInProgress 代表当前已创建的 workInProgress fiber。

    performUnitOfWork 方法会创建下一个 Fiber 节点并赋值给 workInProgress,并将 workInProgress 与已创建的 Fiber 节点连接起来构成 Fiber 树。

    function performUnitOfWork(unitOfWork) {
      var current = unitOfWork.alternate;
      setCurrentFiber(unitOfWork);
      var next;
    
      if ( (unitOfWork.mode & ProfileMode) !== NoMode) {
        startProfilerTimer(unitOfWork);
        next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
        stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
      } else {
        next = beginWork$1(current, unitOfWork, subtreeRenderLanes);
      }
    
      resetCurrentFiber();
      unitOfWork.memoizedProps = unitOfWork.pendingProps;
    
      if (next === null) {
        completeUnitOfWork(unitOfWork);
      } else {
        workInProgress = next;
      }
    
      ReactCurrentOwner$2.current = null;
    }
    
    

    我们知道 Fiber Reconciler 是从 Stack Reconciler 重构而来,通过遍历的方式实现可中断的递归,所以 performUnitOfWork 的工作可以分为两部分:“递”和“归”。

    举例说明整个调用顺序

    function App() {
      return (
        <div>
          <span>wsh</span>
          age: 18
        </div>
      )
    }
    
    ReactDOM.render(<App />, document.getElementById("root"));
    

    render 阶段执行顺序如下:

    rootFiber beginWork
    App Fiber beginWork
    div Fiber beginWork
    "age: 18" Fiber beginWork beginWork
    "age: 18" Fiber completeWork
    span Fiber completeWork
    div Fiber completeWork
    App Fiber completeWork
    rootFiber Fiber completeWork
    

    # 02. 递阶段

    首先从 RootFiber 开始向下深度优先遍历。为遍历到的每个 Fiber 节点调用 beginWork 方法。 该方法会根据传入的 Fiber 节点创建子 Fiber 节点,并将这两个 Fiber 节点连接起来。 当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。

    源码大致如下:

    function beginWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderLanes: Lanes,
    ){
      // ...
    }
    
    • current:当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,即 workInProgress.alternate
    • workInProgress:当前组件对应的 Fiber 节点
    • renderLanes:优先级相关,Scheduler 使用

    在 mount 首次渲染时,不存在当前组件对应的 Fiber 节点在上一次更新时的 Fiber 节点,current === null 组件 update 时,current !== null。

    通过【current === null】来区分组件是处于 mount 还是 update。

    beginWork 的工作可以划分为两部分:

    1. mount 时:除 fiberRootNode 以外,current === null。会根据 fiber.tag 不同,创建不同类型的子 Fiber 节点

    2. update 时:如果 current 存在,在满足一定条件时可以复用 current 节点,这样就能 clone current.child 作为 workInProgress.child,而不需要新建 workInProgress.child。

    function beginWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderLanes: Lanes
    ): Fiber | null {
    
      // update时:如果current存在,可能存在优化路径,复用current(即上一次更新的Fiber节点)
      if (current !== null) {
        // ...省略
    
        // 复用current
        return bailoutOnAlreadyFinishedWork(
          current,
          workInProgress,
          renderLanes,
        );
      } else {
        didReceiveUpdate = false;
      }
    
      // mount时:根据tag不同,创建不同的子Fiber节点
      switch (workInProgress.tag) {
        case IndeterminateComponent:
          // ...省略
          return mountIndeterminateComponent
        case LazyComponent:
          // ...省略
          return mountLazyComponent
        case FunctionComponent:
          // ...省略
          return updateFunctionComponent
        case ClassComponent:
          // ...省略
          return updateClassComponent()
        case HostRoot:
          // ...省略
          return updateHostRoot()
        case HostComponent:
          // ...省略
        case HostText:
          // ...省略
          return updateHostText$1()
      }
    }
    

    # update 时:

    1. oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
    2. !includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够
    3. didReceiveUpdate = false 可以直接复用前一次更新的子Fiber,不需要新建子Fiber
    if (current !== null) {
        const oldProps = current.memoizedProps;
        const newProps = workInProgress.pendingProps;
    
        if (
          oldProps !== newProps ||
          hasLegacyContextChanged() ||
          (__DEV__ ? workInProgress.type !== current.type : false)
        ) {
          didReceiveUpdate = true;
        } else if (!includesSomeLane(renderLanes, updateLanes)) {
          didReceiveUpdate = false;
          switch (workInProgress.tag) {
            // ...
          }
          return bailoutOnAlreadyFinishedWork(
            current,
            workInProgress,
            renderLanes,
          );
        } else {
          didReceiveUpdate = false;
        }
      } else {
        didReceiveUpdate = false;
      }
    

    # mount时:

    当不满足路径优化时,我们会创建子fiber节点 根据fiber.tag不同,进入不同类型Fiber的创建逻辑,tag (opens new window)

    // mount时:根据tag不同,创建不同的Fiber节点
    switch (workInProgress.tag) {
      case IndeterminateComponent: 
        // ...省略
      case LazyComponent: 
        // ...省略
      case FunctionComponent: 
        // ...省略
      case ClassComponent: 
        // ...省略
      case HostRoot:
        // ...省略
      case HostComponent:
        // ...省略
      case HostText:
        // ...省略
      // ...省略其他类型
    }
    

    对于我们常见的组件类型,如(FunctionComponent/ClassComponent/HostComponent),最终会进入reconcileChildren方法。

    # reconcileChildren

    • 对于mount的组件,创建新的子Fiber节点

    • 对于update的组件,会将当前组件与该组件在上次更新时对应的Fiber节点比较(Diff算法),将比较的结果生成新Fiber节点

    export function reconcileChildren(
      current: Fiber | null,
      workInProgress: Fiber,
      nextChildren: any,
      renderLanes: Lanes
    ) {
      if (current === null) {
        // 对于mount的组件,不会为生成的Fiber节点带上effectTag属性
        workInProgress.child = mountChildFibers(
          workInProgress,
          null,
          nextChildren,
          renderLanes,
        );
      } else {
        // 对于update的组件,为生成的Fiber节点带上effectTag属性
        workInProgress.child = reconcileChildFibers(
          workInProgress,
          current.child,
          nextChildren,
          renderLanes,
        );
      }
    }
    

    通过代码可以看到 无论是mount或者update ,最终都是将新的子fiber节点赋值给workInProgress.child,作为本次beginWork的返回值,并作为下次performUnitOfWork执行时workInProgress的传参。 其流程图下:

    # 03. 归阶段

    在“归”阶段会调用 completeWork 处理 Fiber 节点。 当某个 Fiber 节点执行完 completeWork,如果其存在兄弟 Fiber 节点(即 fiber.sibling !== null),会进入其兄弟 Fiber 的“递”阶段。 如果不存在兄弟 Fiber,会进入父级 Fiber 的“归”阶段。 “递”和“归”阶段会交错执行直到“归”到 rootFiber。至此,render 阶段的工作就结束了。

    function completeWork(
      current: Fiber | null,
      workInProgress: Fiber,
      renderLanes: Lanes,
    ): Fiber | null {
      const newProps = workInProgress.pendingProps;
    
      switch (workInProgress.tag) {
        case IndeterminateComponent:
        case LazyComponent:
        case SimpleMemoComponent:
        case FunctionComponent:
        case ForwardRef:
        case Fragment:
        case Mode:
        case Profiler:
        case ContextConsumer:
        case MemoComponent:
          return null;
        case ClassComponent: {
          // ...省略
          return null;
        }
        case HostRoot: {
          // ...省略
          updateHostContainer(workInProgress);
          return null;
        }
        case HostComponent: {
          // ...省略
          return null;
        }
      // ...省略
    

    以HostComponent 为例:

    case HostComponent: {
      popHostContext(workInProgress);
      const rootContainerInstance = getRootHostContainer();
      const type = workInProgress.type;
      // workInProgress.stateNode != null 该Fiber节点是否存在对应的DOM节点
      if (current !== null && workInProgress.stateNode != null) {
        // update的情况
      } else {
        // mount的情况
      }
      return null;
    }
    

    # update

    update,fiber节点山已经存在对应的DOM节点,所以不需要生成DOM节点,需要做的就是关注props的变化,比如:onClick等回调函数的注册,处理style props。 主要逻辑如下:

    if (current !== null && workInProgress.stateNode != null) {
      // update的情况
      updateHostComponent(
        current,
        workInProgress,
        type,
        newProps,
        rootContainerInstance,
      );
      if (current.ref !== workInProgress.ref) {
        markRef(workInProgress);
      }
    }
    
    

    updateHostComponent内部处理逻辑,被处理完的props会被赋值给workInProgress.updateQueue,并最终在commit阶段被渲染在页面。

    const updatePayload = prepareUpdate(
      instance,
      type,
      oldProps,
      newProps,
      rootContainerInstance,
      currentHostContext,
    );
    // updatePayload 数据结构 为:[key1, value1, key2, value2]
    workInProgress.updateQueue = (updatePayload: any);
    
    

    # mount

    • 为Fiber节点生成对应的DOM节点
    • 将子孙DOM节点插入刚生成的DOM节点中
    • 与update逻辑中的updateHostComponent类似的处理props的过程

    代码简化逻辑如下:

    // mount的情况
    
    const currentHostContext = getHostContext();
    // 为fiber创建对应DOM节点
    const instance = createInstance(
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
        workInProgress,
      );
    // 将子孙DOM节点插入刚生成的DOM节点中
    appendAllChildren(instance, workInProgress, false, false);
    // DOM节点赋值给fiber.stateNode
    workInProgress.stateNode = instance;
    
    // 与update逻辑中的updateHostComponent类似的处理props的过程
    if (
      finalizeInitialChildren(
        instance,
        type,
        newProps,
        rootContainerInstance,
        currentHostContext,
      )
    ) {
      markUpdate(workInProgress);
    }
    

    # effectList

    作为DOM操作的依据,commit阶段需要找到所有有effectTag的Fiber节点并依次执行effectTag对应操作。这个时候我们不需要再次深度遍历。因为 在completeWork的上层函数completeUnitOfWork (opens new window)中,每个执行完completeWork且存在effectTag的Fiber节点会被保存在一条被称为effectList的单向链表中。

    effectList中第一个Fiber节点保存在fiber.firstEffect,最后一个元素保存在fiber.lastEffect。

    if (returnFiber.firstEffect === null) {
      returnFiber.firstEffect = completedWork.firstEffect;
    }
    if (completedWork.lastEffect !== null) {
      if (returnFiber.lastEffect !== null) {
        returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
      }
      returnFiber.lastEffect = completedWork.lastEffect;
    }
    

    在“归”阶段,所有有effectTag的Fiber节点都会被追加在effectList中,最终形成一条以rootFiber.firstEffect为起点的单向链表

                           nextEffect         nextEffect
    rootFiber.firstEffect -----------> fiber -----------> fiber
    

    hostComponet 归流程:

    #react
    react 架构演变
    react commit阶段

    ← react 架构演变 react commit阶段→

    最近更新
    01
    组件通信方式
    01-07
    02
    UIAbility
    01-07
    03
    ATKTS
    01-06
    更多文章>
    Theme by Vdoing | Copyright © 2022-2025 Wsh | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式