前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之Commit第一子阶段「before mutation」

React源码解析之Commit第一子阶段「before mutation」

作者头像
进击的小进进
发布2020-04-14 16:40:33
9570
发布2020-04-14 16:40:33
举报

前言

在上篇我们介绍了 commitRoot的整体流程,那么本篇就来介绍它的第一个子阶段 before mutation

代码语言:javascript
复制
do {
      if (__DEV__) {
        invokeGuardedCallback(null, commitBeforeMutationEffects, null);
        //删除了 dev 代码
      } else {
        try {
          //调用 classComponent 上的生命周期方法 getSnapshotBeforeUpdate
          //关于getSnapshotBeforeUpdate,请看:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
          commitBeforeMutationEffects();
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
      }
    } while (nextEffect !== null);

一、commitBeforeMutationEffects()

作用: 循环effect链,对有Snapshoteffect执行commitBeforeMutationEffectOnFiber

源码:

代码语言:javascript
复制
function commitBeforeMutationEffects() {
  //循环 effect 链
  while (nextEffect !== null) {
    //如果 effectTag 里有 Snapshot 这个 effectTag 的话
    //关于&,请看[前端小知识10点(2020.2.10)](https://mp.weixin.qq.com/s/tt2XcW4GF7oBBZOPwTiCcg)中的「8、JS 中的 & 是什么意思」
    if ((nextEffect.effectTag & Snapshot) !== NoEffect) {
      //dev 可不看
      // setCurrentDebugFiberInDEV(nextEffect);
      //计 effect 的数
      recordEffect();
      //获取当前 fiber 节点
      const current = nextEffect.alternate;
      commitBeforeMutationEffectOnFiber(current, nextEffect);
      //dev 可不看
      // resetCurrentDebugFiberInDEV();
    }
    nextEffect = nextEffect.nextEffect;
  }
}

解析: (1) nextEffecteffect链上从firstEffectlastEffect的每一个需要commitfiber对象

(2) 当nextEffect上有Snapshot这个effectTag时,执行commitBeforeMutationEffectOnFiber(),让不同类型的组件执行不同的操作,来提交(commit)相关effect

二、commitBeforeMutationEffectOnFiber()

作用: (1) 如果该fiber类型是ClassComponent的话,执行getSnapshotBeforeUpdate生命周期api,将返回的值赋到fiber 对象的__reactInternalSnapshotBeforeUpdate

(2) 如果该fiber类型是FunctionComponent的话,执行hooks上的effect相关 API

(3) 关于&运算符,请看: 前端小知识10点(2020.2.10) 中的 8、JS 中的 & 是什么意思

源码:

代码语言:javascript
复制
function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    //FunctionComponent会执行commitHookEffectList()
    //FunctionComponent是 pureComponent,所以不会有副作用

    //useEffect 和 useLayoutEffect 是赋予FunctionComponent有副作用能力的 hooks
    //useEffect类似于componentDidMount,useLayoutEffect类似于componentDidUpdate
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      //提交 hooks 的 effects
      commitHookEffectList(UnmountSnapshot, NoHookEffect, finishedWork);
      return;
    }
    case ClassComponent: {
      if (finishedWork.effectTag & Snapshot) {
        if (current !== null) {
          //老 props
          const prevProps = current.memoizedProps;
          //老 state
          const prevState = current.memoizedState;
          //getSnapshotBeforeUpdate 的计时开始
          startPhaseTimer(finishedWork, 'getSnapshotBeforeUpdate');
          //获取 classComponent 的实例
          const 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 (__DEV__) {
            //删除了 dev 代码
          }
          //执行 getSnapshotBeforeUpdate 生命周期 api,在组件update前捕获一些 DOM 信息,
          //返回自定义的值或 null,统称为 snapshot
          //关于getSnapshotBeforeUpdate,请参考:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          if (__DEV__) {
            //删除了 dev 代码
          }
          //将 snapshot 赋值到__reactInternalSnapshotBeforeUpdate属性上,
          // 这种手法跟[React源码解析之updateClassComponent(上)](https://mp.weixin.qq.com/s/F_UdPgdt6wtP78eDqUesoA)
          // 中的「三、adoptClassInstance」里 instance._reactInternalFiber=workInProgress 类似
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
          //getSnapshotBeforeUpdate 的计时结束
          stopPhaseTimer();
        }
      }
      return;
    }
    case HostRoot:
    case HostComponent:
    case HostText:
    case HostPortal:
    case IncompleteClassComponent:
      // Nothing to do for these component types
      return;
    //没有副作用,不应该进入到 commit 阶段
    default: {
      invariant(
        false,
        'This unit of work tag should not have side-effects. This error is ' +
          'likely caused by a bug in React. Please file an issue.',
      );
    }
  }
}

解析: (1) 根据fibertag即类型,进行不同的操作。主要看FunctionComponentClassComponent这两种情况。

(2) 如果是FunctionComponent的话,则执行commitHookEffectList(),该方法稍后解析。

(3) 如果是ClassComponent的话,则执行生命周期方法——getSnapshotBeforeUpdate(),并将返回值赋予给fiber对象上的__reactInternalSnapshotBeforeUpdate属性上。

也就是说,类组件的子阶段 before mutation 目的是: 调用getSnapshotBeforeUpdate(),在commit前获取 DOM 相关信息

(4) getSnapshotBeforeUpdate()在最近一次渲染输出(提交到 DOM 节点)之前调用。

它使得组件能在发生更改之前从 DOM 中捕获一些信息(例如,滚动位置)。

此生命周期的任何返回值将作为参数传递给componentDidUpdate(),更多详情请参考: https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate

(5)

代码语言:javascript
复制
instance.__reactInternalSnapshotBeforeUpdate = snapshot

这种手法跟 React源码解析之updateClassComponent(上) 中的 三、adoptClassInstance 里的

代码语言:javascript
复制
instance._reactInternalFiber=workInProgress

类似

三、commitHookEffectList()

说明: useEffect是让FunctionComponent产生副作用的hooks,当使用useEffect后,会在fiber上的updateQueue.lastEffect生成effect链,具体请看ReactFiberHooks.js中的pushEffect()

作用: 循环FunctionComponent上的effect链,并根据每个effect上的effectTag,执行destroy/create操作(作用类似于componentDidMount/componentWillUnmount

源码:

代码语言:javascript
复制
function commitHookEffectList(
  unmountTag: number,
  mountTag: number,
  finishedWork: Fiber,
) {
  //FunctionComponent 的更新队列
  //补充:FunctionComponent的 side-effect 是放在 updateQueue.lastEffect 上的
  //ReactFiberHooks.js中的pushEffect()里有说明:componentUpdateQueue.lastEffect = effect.next = effect;
  const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
  let lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;
  //如果有副作用 side-effect的话,循环effect 链,根据 effectTag,执行每个 effect
  if (lastEffect !== null) {
    //第一个副作用
    const firstEffect = lastEffect.next;
    let effect = firstEffect;
    do {
      //如果包含 unmountTag 这个 effectTag的话,执行destroy(),并将effect.destroy置为 undefined
      if ((effect.tag & unmountTag) !== NoHookEffect) {
        // Unmount
        const destroy = effect.destroy;
        effect.destroy = undefined;
        if (destroy !== undefined) {
          destroy();
        }
      }
      //如果包含 mountTag 这个 effectTag 的话,执行 create()
      if ((effect.tag & mountTag) !== NoHookEffect) {
        // Mount
        const create = effect.create;
        effect.destroy = create();

        if (__DEV__) {
          //删除了 dev 代码
        }
      }
      effect = effect.next;
    } while (effect !== firstEffect);
  }
}

解析: (1) 不同于ClassComponent的 effect 链直接取fiber.firstEffect—>fiber.firstEffect.next......,

FunctionComponent的 effect 链取的是fiber.updateQueue.lastEffect—>fiber.updateQueue.lastEffect. next....

(2) 循环FunctionComponent上的 effect 链, 如果effectTagunmountTag的话,就执行effect.destroy()方法,感觉跟componentWillUnmount()作用类似; 如果effectTagmountTag的话,就执行effect.create()方法,感觉跟componentDidMount()作用类似;

总结

before mutation 子阶段上, ClassComponent会执行getSnapshotBeforeUpdate(),捕获 DOM 信息; FunctionComponent会执行effect.create()/effect.destory(),类似于componentDidMount()/componentWillUnmount()

流程图

GitHub

commitBeforeMutationEffects()https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js

commitBeforeMutationLifeCycles()/commitHookEffectList()https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberCommitWork.js

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、commitBeforeMutationEffects()
  • 二、commitBeforeMutationEffectOnFiber()
  • 三、commitHookEffectList()
  • 总结
  • 流程图
  • GitHub
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档