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-22
    目录

    react commit阶段

    # 01. commit 概念

    commitRoot 是commit 阶段的起点, fiberRootNode 作为参数

    commitRoot(root);
    

    在rootFiber.firstEffect上保存了一条需要执行副作用的Fiber节点的单向链表effectList,这些Fiber节点的updateQueue中保存了变化的props。 这些副作用对应的DOM操作都是在Commit 阶段中进行的。

    # commit阶段的主要工作(即Renderer的工作流程)分为三部分:
    1. before mutation阶段(执行DOM操作前)
    2. mutation阶段(执行DOM操作)
    3. layout阶段(执行DOM操作后)

    # 02. before mutation阶段

    commitBeforeMutationEffects(finishedWork); 主题函数

    // 保存之前的优先级,以同步优先级执行,执行完毕后恢复之前优先级
    const previousLanePriority = getCurrentUpdateLanePriority();
    setCurrentUpdateLanePriority(SyncLanePriority);
    
    // 将当前上下文标记为CommitContext,作为commit阶段的标志
    const prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    
    // 处理focus状态
    focusedInstanceHandle = prepareForCommit(root.containerInfo);
    shouldFireAfterActiveInstanceBlur = false;
    
    // beforeMutation阶段的主函数
    commitBeforeMutationEffects(finishedWork);
    
    focusedInstanceHandle = null;
    

    # commitBeforeMutationEffects主要用途

    function commitBeforeMutationEffects() {
      while (nextEffect !== null) {
        const current = nextEffect.alternate;
    
        if (!shouldFireAfterActiveInstanceBlur && focusedInstanceHandle !== null) {
          // ...focus blur相关
        }
    
        const effectTag = nextEffect.effectTag;
    
        // 调用getSnapshotBeforeUpdate
        if ((effectTag & Snapshot) !== NoEffect) {
          commitBeforeMutationEffectOnFiber(current, nextEffect);
        }
    
        // 调度useEffect
        if ((effectTag & Passive) !== NoEffect) {
          if (!rootDoesHavePassiveEffects) {
            rootDoesHavePassiveEffects = true;
            scheduleCallback(NormalSchedulerPriority, () => {
              flushPassiveEffects();
              return null;
            });
          }
        }
        nextEffect = nextEffect.nextEffect;
      }
    }
    

    整体分为三部分:

    • 处理DOM节点渲染/删除后的 autoFocus、blur 逻辑。
    • 调用getSnapshotBeforeUpdate生命周期钩子。
    • 调度useEffect。

    # 调用getSnapshotBeforeUpdate

    Stack Reconciler重构为Fiber Reconciler后,render阶段的任务可能中断/重新开始,对应的组件在render阶段的生命周期钩子(即componentWillXXX)可能触发多次。 React提供了替代的生命周期钩子getSnapshotBeforeUpdate。

    getSnapshotBeforeUpdate是在commit阶段内的before mutation阶段调用的,由于commit阶段是同步的,所以不会遇到多次调用的问题。

    scheduleCallback 由scheduler模块提供,用于以某个优先级异步调度一个回调函数

    # 调度useEffect

    // 调度useEffect
    if ((effectTag & Passive) !== NoEffect) {
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          // 触发useEffect
          flushPassiveEffects();
          return null;
        });
      }
    }
    

    被异步调度的回调函数就是触发useEffect的方法flushPassiveEffects。

    在flushPassiveEffects方法内部会从全局变量rootWithPendingPassiveEffects获取effectList。

    # 03. mutation阶段

    mutation阶段 也是遍历effectList,执行函数commitMutationEffects。

    nextEffect = firstEffect;
    do {
      try {
          commitMutationEffects(root, renderPriorityLevel);
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
    } while (nextEffect !== null);
    

    # commitMutationEffects主要用途

    function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
      // 遍历effectList
      while (nextEffect !== null) {
    
        const effectTag = nextEffect.effectTag;
    
        // 根据 ContentReset effectTag重置文字节点
        if (effectTag & ContentReset) {
          commitResetTextContent(nextEffect);
        }
    
        // 更新ref
        if (effectTag & Ref) {
          const current = nextEffect.alternate;
          if (current !== null) {
            commitDetachRef(current);
          }
        }
    
        // 根据 effectTag 分别处理
        const primaryEffectTag =
          effectTag & (Placement | Update | Deletion | Hydrating);
        switch (primaryEffectTag) {
          // 插入DOM
          case Placement: {
            commitPlacement(nextEffect);
            nextEffect.effectTag &= ~Placement;
            break;
          }
          // 插入DOM 并 更新DOM
          case PlacementAndUpdate: {
            // 插入
            commitPlacement(nextEffect);
    
            nextEffect.effectTag &= ~Placement;
    
            // 更新
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          // SSR
          case Hydrating: {
            nextEffect.effectTag &= ~Hydrating;
            break;
          }
          // SSR
          case HydratingAndUpdate: {
            nextEffect.effectTag &= ~Hydrating;
    
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          // 更新DOM
          case Update: {
            const current = nextEffect.alternate;
            commitWork(current, nextEffect);
            break;
          }
          // 删除DOM
          case Deletion: {
            commitDeletion(root, nextEffect, renderPriorityLevel);
            break;
          }
        }
    
        nextEffect = nextEffect.nextEffect;
      }
    }
    

    commitMutationEffects 会先遍历effectList,对每个fiber节点 执行如下操作:

    1. 根据ContentReset effectTag 重置文本节点
    2. 更新ref
    3. 根据 effectTag 分别处理 增删改

    # Placement effect

    该Fiber节点对应的DOM节点需要插入到页面中,调用的方法是:commitPlacement

    1. 获取父级DOM节点。其中finishedWork为传入的Fiber节点
    const parentFiber = getHostParentFiber(finishedWork);
    const parentStateNode = parentFiber.stateNode; // 父级DOM节点
    
    1. 获取Fiber节点的DOM兄弟节点
    const before = getHostSibling(finishedWork);
    
    1. 根据DOM兄弟节点是否存在决定调用parentNode.insertBefore或parentNode.appendChild执行DOM插入操作。
    // parentStateNode是否是rootFiber
    if (isContainer) {
      insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
    } else {
      insertOrAppendPlacementNode(finishedWork, before, parent);
    }
    

    # Update effect

    当Fiber节点含有Update effectTag,意味着该Fiber节点需要更新。调用的方法为commitWork,他会根据Fiber.tag分别处理。

    # FunctionComponent mutation

    当fiber.tag为FunctionComponent,会调用commitHookEffectListUnmount。该方法会遍历effectList,执行所有useLayoutEffect hook的销毁函数。

    useLayoutEffect(() => {
      // ...一些副作用逻辑
    
      return () => {
        // ...这就是销毁函数
      }
    
    # HostComponent mutation

    当fiber.tag为HostComponent,会调用commitUpdate函数。最终会在updateDOMProperties中将render阶段 completeWork中为Fiber节点赋值的updateQueue对应的内容渲染在页面上。

    for (let i = 0; i < updatePayload.length; i += 2) {
      const propKey = updatePayload[i];
      const propValue = updatePayload[i + 1];
    
      // 处理 style
      if (propKey === STYLE) {
        setValueForStyles(domElement, propValue);
      // 处理 DANGEROUSLY_SET_INNER_HTML
      } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
        setInnerHTML(domElement, propValue);
      // 处理 children
      } else if (propKey === CHILDREN) {
        setTextContent(domElement, propValue);
      } else {
      // 处理剩余 props
        setValueForProperty(domElement, propKey, propValue, isCustomComponentTag);
      }
    }
    

    # Deletion effect

    fiber节点对应的DOM 节点需要从页面中移除,使用方法: commitDeletion

    1. 递归调用Fiber节及其子孙fiber节点中fiber.tag 为ClassComponent的componentWillUnmount生命周期钩子,从页面移除DOM节点
    2. 解绑ref
    3. 调度useEffect的销毁函数

    # 04. layout阶段

    执行时机: 在DOM渲染完成(mutation阶段完成)后执行的,所以该阶段触发的生命周期钩子和hook 可以直接访问到已经改变后的DOM。

    具体执行的是commitLayoutEffects函数

    root.current = finishedWork;
    
    nextEffect = firstEffect;
    do {
      try {
        commitLayoutEffects(root, lanes);
      } catch (error) {
        invariant(nextEffect !== null, "Should be working on an effect.");
        captureCommitPhaseError(nextEffect, error);
        nextEffect = nextEffect.nextEffect;
      }
    } while (nextEffect !== null);
    
    nextEffect = null;
    

    # commitLayoutEffects

    function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
      while (nextEffect !== null) {
        const effectTag = nextEffect.effectTag;
    
        // 调用生命周期钩子和hook
        if (effectTag & (Update | Callback)) {
          const current = nextEffect.alternate;
          commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
        }
    
        // 赋值ref
        if (effectTag & Ref) {
          commitAttachRef(nextEffect);
        }
    
        nextEffect = nextEffect.nextEffect;
      }
    }
    

    用途:

    • commitLayoutEffectOnFiber: 调用生命周期钩子和hook相关操作
    • commitAttachRef: 赋值 ref
    # commitLayoutEffectOnFiber
    1. 触发状态更新的this.setState 回调函数在此刻更新
    2. 对于functionComponent 以相关类型(特殊处理后的FunctionComponent,比如ForwardRef、React.memo包裹的FunctionComponent)会调用useLayoutEffect hook 的回调函数,调度useEffect的回调与销毁函数。
     switch (finishedWork.tag) {
        // 以下都是FunctionComponent及相关类型
        case FunctionComponent:
        case ForwardRef:
        case SimpleMemoComponent:
        case Block: {
          // 执行useLayoutEffect的回调函数
          commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
          // 调度useEffect的销毁函数与回调函数
          schedulePassiveEffects(finishedWork);
          return;
     }
    

    useLayoutEffect hook从上一次更新的销毁函数调用到本次更新的回调函数调用是同步执行的。 而useEffect则需要先调度,在Layout阶段完成后再异步执行。 HostRoot(rootFiber) 的第三个参数回调函数,也是此刻调用。

    ReactDOM.render(<App />, document.querySelector("#root"), function() {
      console.log("i am mount~");
    });
    
    # commitAttachRef

    获取DOM实例,更新ref

    function commitAttachRef(finishedWork: Fiber) {
      const ref = finishedWork.ref;
      if (ref !== null) {
        const instance = finishedWork.stateNode;
    
        // 获取DOM实例
        let instanceToUse;
        switch (finishedWork.tag) {
          case HostComponent:
            instanceToUse = getPublicInstance(instance);
            break;
          default:
            instanceToUse = instance;
        }
    
        if (typeof ref === "function") {
          // 如果ref是函数形式,调用回调函数
          ref(instanceToUse);
        } else {
          // 如果ref是ref实例形式,赋值ref.current
          ref.current = instanceToUse;
        }
      }
    }
    
    #react
    react render阶段
    react diff

    ← react render阶段 react diff→

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