快来看 React 渲染过程(下)

引言

书接上回,上一篇文章主要是介绍了 React 初次渲染过程中在内存中生成 workInProgress树的过程。主要是经历了两个阶段,一个是 beginWork,另一个是 completeWork。那React 是如何将已经生成的 WorkInProgress 树渲染到宿主环境中的呢?接下来说一说 commit 阶段,也就是将 workInProgress 树渲染到页面上的过程。

原理

demo

因为 commit 阶段涉及到副作用的执行,比如 useLayoutEffect 等。 所以我们改造一下调试源码用的组件代码。

jsx 复制代码
import React, { useEffect, useState } from "react"
const Index = ()=>{
    debugger;
    const [state,setState] = useState(0)
    useEffect(()=>{
        console.log(123)
    },[])
    console.log('渲染吗??')
    const [demo,setDemo] = useState('demo')

    return <div>
        {state}
        <button onClick={()=>{
            // debugger
            // setState(1)
            // setState(10)
            // setState(100)
            setState(0)
        }}>触发渲染</button>    
    </div>
}
export default Index

我们的组件大概是这个样子的。下面我们来看一下 React 整个过程是怎么样的。

过程

首先看一下当走完beginWork 和completeWork 之后,生成的树是怎么样的。

可以看到,最后是生成了一个这样的树,我们的 wip 树保存在finishedWork 中,可以打印出来看一下

进入 commit 阶段的入口函数是 commitRoot

js 复制代码
function commitRoot(root) {

  // TODO: This no longer makes any sense. We already wrap the mutation and
  // layout phases. Should be able to remove.
  var previousUpdateLanePriority = getCurrentUpdatePriority();
  var prevTransition = ReactCurrentBatchConfig$3.transition;

  try {
    ReactCurrentBatchConfig$3.transition = 0;
    setCurrentUpdatePriority(DiscreteEventPriority);
    commitRootImpl(root, previousUpdateLanePriority);
  } finally {
    ReactCurrentBatchConfig$3.transition = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority);
  }

  return null;
}

下面从这个函数出发具体来看看 React 做了什么

commitRoot

commitRoot 主要的逻辑就是调用了一个commitRootImpl,同时传入了 root 树和previousUpdateLanePriority。

commitRootImpl

commitRootImpl 的作用就大了许多,我们分别来看commitRootImpl 做了哪些事情。

js 复制代码
function commitRootImpl(root, renderPriorityLevel) {
  do {
    // 清空副作用列表
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  flushRenderPhaseStrictModeWarningsInDEV();

  
    //  判断当前 fiber 节点的子孙节点是否有副作用
  var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
  //  判断当前节点是否有副作用
  var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;

  if (subtreeHasEffects || rootHasEffect) {
   

    var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork);


    commitMutationEffects(root, finishedWork, lanes);

   

    commitLayoutEffects(finishedWork, root, lanes);

    {
      markLayoutEffectsStopped();
    }
    // opportunity to paint.


    requestPaint();
  
  return null;
}

从源码可以看到,commitRootImpl 主要做了三件事情

  1. 通过一个while 循环去调用flushPassiveEffects去清除副作用,防止因为有没有运行的副作用影响 commit过程。
  2. 判断子孙节点以及自己是否有副作用
    • 如果有副作用,依次执行commitBeforeMutationEffects,commitMutationEffects,commitLayoutEffects,requestPaint
    • 如果没有副作用,直接切树
  3. 其他一些收尾工作。

下面我们依次进行说明

判断副作用

在completeWork 阶段,React 将子组件的 flags 都冒泡到了父组件的subtreeFlags 中,所以可以通过subtreeFlags来检查子组件是否有副作用已经自己本身是否有副作用。

js 复制代码
  var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
  var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;

如果有副作用的话,就依次运行commitBeforeMutationEffects ,commitMutationEffects,commitLayoutEffects,requestPaint。下面分别进行说明。 如果友友们对位运算不太熟悉,可以自行百度了解一下~

commitBeforeMutationEffects

js 复制代码
function commitBeforeMutationEffects(root, firstChild) {
  focusedInstanceHandle = prepareForCommit(root.containerInfo);
  nextEffect = firstChild;
  commitBeforeMutationEffects_begin(); // We no longer need to track the active instance fiber

  var shouldFire = shouldFireAfterActiveInstanceBlur;
  shouldFireAfterActiveInstanceBlur = false;
  focusedInstanceHandle = null;
  return shouldFire;
}

主要流程是commitBeforeMutationEffects_begin处理的,我们直接进入commitBeforeMutationEffects_begin查看 React 做了什么。

commitBeforeMutationEffects_begin
js 复制代码
function commitBeforeMutationEffects_begin() {
  while (nextEffect !== null) {
    var fiber = nextEffect; // This phase is only used for beforeActiveInstanceBlur.

    var child = fiber.child;

    if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
    } else {
      commitBeforeMutationEffects_complete();
    }
  }
}

主要逻辑在commitBeforeMutationEffects_complete中

commitBeforeMutationEffects_complete
js 复制代码
function commitBeforeMutationEffects_complete() {
  while (nextEffect !== null) {
    var fiber = nextEffect;
    setCurrentFiber(fiber);

    try {
     
      commitBeforeMutationEffectsOnFiber(fiber);
    } catch (error) {
      reportUncaughtErrorInDEV(error);
      captureCommitPhaseError(fiber, fiber.return, error);
    }

    resetCurrentFiber();
    var sibling = fiber.sibling;

    if (sibling !== null) {
      ensureCorrectReturnPointer(sibling, fiber.return);
      nextEffect = sibling;
      return;
    }

    nextEffect = fiber.return;
  }
}

这里解释一下,这个循环的作用主要是运行了commitBeforeMutationEffectsOnFiber,同时如果当前 fiber 节点有sibling节点的话,也会运行commitBeforeMutationEffects_complete。整个逻辑是一个从下到上的过程。

commitBeforeMutationEffectsOnFiber
js 复制代码
function commitBeforeMutationEffectsOnFiber(finishedWork) {
  var current = finishedWork.alternate;
  var flags = finishedWork.flags;

  if ((flags & Snapshot) !== NoFlags) {
    setCurrentFiber(finishedWork);

    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
        {
          break;
        }

      case ClassComponent:
        {
          if (current !== null) {
            var prevProps = current.memoizedProps;
            var prevState = current.memoizedState;
            var instance = finishedWork.stateNode; // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
                }
              }
            }

            var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);

            {
              var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;

              if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
                didWarnSet.add(finishedWork.type);

                error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
              }
            }

            instance.__reactInternalSnapshotBeforeUpdate = snapshot;
          }

          break;
        }

      case HostRoot:
        {
          {
            var root = finishedWork.stateNode;
            clearContainer(root.containerInfo);
          }

          break;
        }

      case HostComponent:
      case HostText:
      case HostPortal:
      case IncompleteClassComponent:
        // Nothing to do for these component types
        break;

      default:
        {
          throw new Error('This unit of work tag should not have side-effects. This error is ' + 'likely caused by a bug in React. Please file an issue.');
        }
    }

    resetCurrentFiber();
  }
}

这里判断当前 fiber 是否有Snapshot副作用,如果有副作用的话,根据 tag 进入不同的 case,主要有作用的 case 就两个,其余都是凑数的,没什么实际用途。

js 复制代码
      case ClassComponent:
        {
          if (current !== null) {
            var prevProps = current.memoizedProps;
            var prevState = current.memoizedState;
            var instance = finishedWork.stateNode; // We could update instance props and state here,
            // but instead we rely on them being set during last render.
            // TODO: revisit this when we implement resuming.

            {
              if (finishedWork.type === finishedWork.elementType && !didWarnAboutReassigningProps) {
                if (instance.props !== finishedWork.memoizedProps) {
                  error('Expected %s props to match memoized props before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.props`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
                }

                if (instance.state !== finishedWork.memoizedState) {
                  error('Expected %s state to match memoized state before ' + 'getSnapshotBeforeUpdate. ' + 'This might either be because of a bug in React, or because ' + 'a component reassigns its own `this.state`. ' + 'Please file an issue.', getComponentNameFromFiber(finishedWork) || 'instance');
                }
              }
            }

            var snapshot = instance.getSnapshotBeforeUpdate(finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState);

            {
              var didWarnSet = didWarnAboutUndefinedSnapshotBeforeUpdate;

              if (snapshot === undefined && !didWarnSet.has(finishedWork.type)) {
                didWarnSet.add(finishedWork.type);

                error('%s.getSnapshotBeforeUpdate(): A snapshot value (or null) ' + 'must be returned. You have returned undefined.', getComponentNameFromFiber(finishedWork));
              }
            }

            instance.__reactInternalSnapshotBeforeUpdate = snapshot;
          }

          break;
        }

      case HostRoot:
        {
          {
            var root = finishedWork.stateNode;
            clearContainer(root.containerInfo);
          }

          break;
        }

如果是一个类组件的话,会执行getSnapshotBeforeUpdate这个函数来做一些事情。因为对类组件了解不够深,这里跳过说明。 如果是HostRoot,会清除节点内容。 具体可看下面代码

js 复制代码
function clearContainer(container) {
  if (container.nodeType === ELEMENT_NODE) {
    container.textContent = '';
  } else if (container.nodeType === DOCUMENT_NODE) {
    var body = container.body;

    if (body != null) {
      body.textContent = '';
    }
  }
} 

这里是为了防止本身 html 节点中有内容,影响我们 React 的整个渲染。 下面我们用图来看一下整个执行过程

commitMutationEffects

和上面的流程差不多,看代码

js 复制代码
function commitMutationEffects(root, firstChild, committedLanes) {
  inProgressLanes = committedLanes;
  inProgressRoot = root;
  nextEffect = firstChild;
  commitMutationEffects_begin(root);
  inProgressLanes = null;
  inProgressRoot = null;
}

依旧是commitMutationEffects_begin来进行一些逻辑的处理。

commitMutationEffects_begin
js 复制代码
function commitMutationEffects_begin(root) {
  while (nextEffect !== null) {
    var fiber = nextEffect; // TODO: Should wrap this in flags check, too, as optimization

    var deletions = fiber.deletions;

    if (deletions !== null) {
      for (var i = 0; i < deletions.length; i++) {
        var childToDelete = deletions[i];

        try {
          commitDeletion(root, childToDelete, fiber);
        } catch (error) {
          reportUncaughtErrorInDEV(error);
          captureCommitPhaseError(childToDelete, fiber, error);
        }
      }
    }

    var child = fiber.child;

    if ((fiber.subtreeFlags & MutationMask) !== NoFlags && child !== null) {
      ensureCorrectReturnPointer(child, fiber);
      nextEffect = child;
    } else {
      commitMutationEffects_complete(root);
    }
  }
}

该函数主要做了两件事:

  1. 如果存在deletions ,先处理这部分的逻辑。
  2. 然后进入commitMutationEffects_complete。
相关推荐
我要洋人死1 小时前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人1 小时前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人1 小时前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR1 小时前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香1 小时前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q2498596931 小时前
前端预览word、excel、ppt
前端·word·excel
小华同学ai1 小时前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風7 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#