React 新特性useEffect

2 篇文章 0 订阅

React16中新增的hooks特性进一步强化了函数组件的功能。本篇承接上次欧超对useState的源码的解读,分析一下另一个高频使用的hook useEffect的在react中的执行机制。

1.useEffect 解决了哪些问题?

1.函数组件没有生命周期。

2.ajax、事件绑定等业务逻辑耦合在生命周期中

3.业务逻辑散乱在不同的生命周期中

Effect Hook 可以让你在函数组件中执行副作用操作。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。类比于class component,可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

2.useEffect 用法回顾

2.1 基本用法举例

function App() {
const [count, setCount] = useState(0);
const handleScroll = () => {
    setCount(count+1);
};
useEffect(()=>{
// create
    window.addEventListener('scroll',handleScroll);
return ()=>{
// destroy
    window.removeEventListener('scroll',handleScroll);
}
// deps
},[count]);
return <div className="wrapper">
    <div className="inner">
    {`It's the ${count} times to trigger scroll.`}
    </div>
</div>
}

3.useEffect 源码解读+图解

3.1 代码执行流程图
react版本:16.13.1
下面从react中fiber的两个阶段 render 阶段和commit阶段来分析useEffect。

代码执行流程图

3.2 部分全局变量简介

1)currentlyRenderingFiber$1
本质是workInProgressFiber,当前正在更新的Fiber.这里是作者为区别于workInProgressHook

// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber = (null: any);

2)workInProgressHook,全局变量,保存work-in-process fiber上最新的一个hook
这个是对调度schedule做的一个优化,在render阶段被更新打断时,变量workInProgress hook 暂存被打断的hook。

// work-in-progress hook list is a new list that will be added to the
  // work-in-progress fiber.
var workInProgressHook = null; 

3)finishedWork 指向当前已经完成准备工作的Fiber Tree Root。render阶段结束后会生成finishedWork.

3.3 Render 阶段

3.3.1 第一次调用useEffect

1)代码位置:react > ReactHooks.js

function useEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useEffect(create, deps);
  }

① resolveDispatcher

function resolveDispatcher() {
    var dispatcher = ReactCurrentDispatcher.current;

    if (!(dispatcher !== null)) {
      {
        throw Error( "Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem." );
      }
    }

    return dispatcher;
  }

② ReactCurrentDispatcher.current

renderWithHooks中根据current !== null && current.memoizedState !== null判断是首次渲染还是更新,对ReactCurrentDispatcher.current赋值。

HooksDispatcherOnMountInDEV和HooksDispatcherOnUpdateInDEV都是包含多个方法的对象。首次渲染时ReactCurrentDispatcher.current被赋值为HooksDispatcherOnMountInDEV,所以dispatcher.useEffect就是HooksDispatcherOnMountInDEV中的useEffect方法.

3.3.2 HooksDispatcherOnMountInDEV.useEffect

代码位置:react-dom > react-reconciler > ReactFiberHooks.js

  HooksDispatcherOnMountInDEV = {
      // ... other functions
      useEffect: function (create, deps) {
        currentHookNameInDev = 'useEffect';
        mountHookTypesDev();
        checkDepsAreArrayDev(deps);
        return mountEffect(create, deps);
      },
      // ... other functions
  }
    };

HooksDispatcherOnMountInDEV中的useEffect方法实际调用的是 mountEffect 方法,入参create是我们在useEffect的第一个参数,这里是一个函数,入参deps是useEffect第二个参数,是useEffect依赖的数组。

3.3.3 mountEffect

function mountEffect(
  create: () => (() => void) | void,
  deps: Array<mixed> | void | null,
): void {
  if (__DEV__ = false) {
    // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
    if ('undefined' !== typeof jest) {
      warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber);
    }
  }
  return mountEffectImpl(
    UpdateEffect | PassiveEffect,
    HookPassive,
    create,
    deps,
  );
}

mountEffect实际调用的是 mountEffectImpl,mountEffectImpl除了透传create和deps还传入两个effectTag。
先看第一个effectTag.
UpdateEffect 、 PassiveEffect 对应 ReactSideEffectTags中的Update、Passive。

3.3.4 ReactSideEffectTags

代码位置:react-dom > react-reconciler > ReactSideEffectTags.js

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /*                 */ 0b00000000000000;
export const PerformedWork = /*            */ 0b00000000000001;

// You can change the rest (and add more).
export const Placement = /*                */ 0b00000000000010;
export const Update = /*                   */ 0b00000000000100;
export const PlacementAndUpdate = /*       */ 0b00000000000110;
export const Deletion = /*                 */ 0b00000000001000;
export const ContentReset = /*             */ 0b00000000010000;
export const Callback = /*                 */ 0b00000000100000;
export const DidCapture = /*               */ 0b00000001000000;
export const Ref = /*                      */ 0b00000010000000;
export const Snapshot = /*                 */ 0b00000100000000;
export const Passive = /*                  */ 0b00001000000000;
export const PassiveUnmountPendingDev = /* */ 0b10000000000000;
export const Hydrating = /*                */ 0b00010000000000;
export const HydratingAndUpdate = /*       */ 0b00010000000100;

// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*      */ 0b00001110100100;

// Union of all host effects
export const HostEffectMask = /*           */ 0b00011111111111;

export const Incomplete = /*               */ 0b00100000000000;
export const ShouldCapture = /*            */ 0b01000000000000;

ReactSideEffectTags 是fiber的副作用标志,用来表示fiber的副作用。
包括fiber的插入、更新、删除等等。

HookPassive对应ReactHookEffectTags中的Passive.

3.3.5 ReactHookEffectTags

代码位置:react-dom > react-reconciler > ReactHookEffectTags.js

export type HookEffectTag = number;

export const NoEffect = /*  */ 0b000;

// Represents whether effect should fire.
export const HasEffect = /* */ 0b001;

// Represents the phase in which the effect (not the clean-up) fires.
// useLayoutEffect的标志
export const Layout = /*    */ 0b010;
// useEffect的标志
export const Passive = /*   */ 0b100;

ReactHookEffectTags用来表示hook effect的副作用标志。

effectTag采用了复合类型方案设计。通过将二进制0的不同位上的数字置为1来表示不同的tag。
1)|运算可以将不同tag组合成为复合类型。
2)&运算可以用于判断复合类型中是否含有某个Tag.
3)通过A&=~B的方式,可以从复合类型A中去掉某个tagB。

3.3.6 mountEffectImpl

代码位置:react-dom > react-reconciler > ReactFiberHooks.js

 function mountEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
  // 创建一个新的hook,添加到hook链表末尾并返回作为workInProcessHook
    var hook = mountWorkInProgressHook();
    var nextDeps = deps === undefined ? null : deps;
    // 将currentlyRenderingFiber$1.effectTag上fiberEffectTag的标志位置为1
    currentlyRenderingFiber$1.effectTag |= fiberEffectTag;
    // 创建新的effect,加到currentlyRenderingFiber$1.updateQueue.lastEffect上
    // 将pushEffect返回的新effect挂在workInProgressHook的memoizedState上
    hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);
  }

1)mountWorkInProgressHook
作用:创建一个新的hook添加到hook链表末尾, 赋值给全局变量workInProcessHook,并返回workInProcessHook。

function mountWorkInProgressHook() {
    var hook = {
      memoizedState: null,
      baseState: null,
      baseQueue: null,
      queue: null,
      next: null
    };
    // 如果全局变量workInProcessHook为空
    if (workInProgressHook === null) {
      // This is the first hook in the list
      // 如果是当前fiber的第一个hook, 将hook挂到work-in-progress fiber的memoizedState上
      currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
    } else {
      // Append to the end of the list
      // 如果全局变量workInProcessHook不为空
      workInProgressHook = workInProgressHook.next = hook;
    }

    return workInProgressHook;
  }

 mountWorkInProgressHook方法

currentlyRenderingFiber 是一个全局变量,保存正在render的Fiber(也就是work-in-process fiber),hooks会以链表的形式保存在work-in-process fiber 的memoizedState字段上。

workInProgressHook,全局变量,保存work-in-process fiber上最新的一个hook。
2)将work-in-process fiber的effectTag 的 fiberEffectTag标志位置1。

currentlyRenderingFiber$1.effectTag |= fiberEffectTag;

接下来调用pushEffect这个方法.

hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);

HasEffect代表当前的useEffect hook需要更新。

位置 react-reconciler > src > ReactHookEffectTags.js

 var HasEffect =
  /* */
  1; // Represents the phase in which the effect (not the clean-up) fires.

hookEffectTag对应ReactHookEffectTags中的Passive,代表useEffect
create和deps是透传的useEffect的两个参数。
destroy 传入的是undefined.
3)pushEffect

 function pushEffect(tag, create, destroy, deps) {
    var effect = {
      tag: tag,
      create: create,
      destroy: destroy,
      deps: deps,
      // Circular
      next: null
    };
    var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;

    if (componentUpdateQueue === null) {
      // 创建函数组件更新队列挂到work-in-process fiber的updateQueue字段上。
      componentUpdateQueue = createFunctionComponentUpdateQueue();
      currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
      // 将第一个effect加入循环链表
      componentUpdateQueue.lastEffect = effect.next = effect;
    } else {
      var lastEffect = componentUpdateQueue.lastEffect;
      if (lastEffect === null) {
        componentUpdateQueue.lastEffect = effect.next = effect;
      } else {
        // 将最新的effect加入循环链表
        var firstEffect = lastEffect.next;
        lastEffect.next = effect;
        effect.next = firstEffect;
        componentUpdateQueue.lastEffect = effect;
      }
    }

    return effect;
  }

①如果workInProgressFiber的更新队列为空,会调用createFunctionComponentUpdateQueue创建一个空的更新队列

function createFunctionComponentUpdateQueue() {
    return {
      lastEffect: null
    };
  }

pushEffect这个方法的作用:
a. 将新创建的effect挂到workInProgressHook.updateQueue.lastEffect上。
b. 返回新创建的effect.

pushEffect方法

  1. pushEffect返回的新effect挂在workInProgressHook的memoizedState上
hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, undefined, nextDeps);

新effect挂在workInProgressHook的memoizedState上

首次调用时,useLayoutEffect与useEffect的代码执行流程完全相同,区别在于方法时用mountEffectImpl传入的fiberEffectTag和hookEffectTag不同。

function mountLayoutEffect(create, deps) {
    // 与mountEffect相同均调用mountEffectImpl,区别在于传入的tag不同。
    // fiberEffectTag 少传了Passive的标志;
    // hookEffectTag传入Layout,代表hookEffect中的useLayoutEffect
    return mountEffectImpl(Update, Layout, create, deps);
  }

3.3.7 第二次调用useEffect

当触发滚动事件时,首先触发setCount所绑定的dispatchAction, dispatchAction 调用 scheduleWork(fiber, expirationTime); 。。。层层调用直到在renderWithHooks调用var children = Component(props, secondArg);
Component 就是函数组件 function App. 执行完setCount,count 从0 变成1。接着调用useEffect。

 function useEffect(create, deps) {
    var dispatcher = resolveDispatcher();
    return dispatcher.useEffect(create, deps);
  }

ReactCurrentDispatcher.current的取值逻辑

更新时,dispatcher是HooksDispatcherOnUpdateInDEV,所以这里调用了HooksDispatcherOnUpdateInDEV.useEffect

    HooksDispatcherOnUpdateInDEV = {
        useEffect: function (create, deps) {
        currentHookNameInDev = 'useEffect';
        updateHookTypesDev();
        return updateEffect(create, deps);
      },
    };

HooksDispatcherOnUpdateInDEV.useEffect 调用的是updateEffect

3.3.8 updateEffect

function updateEffect(create, deps) {
    {
      // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
      if ('undefined' !== typeof jest) {
        warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
      }
    }

    return updateEffectImpl(Update | Passive, Passive$1, create, deps);
  }

updateEffect最终调用了updateEffectImpl这个方法,前两个参数是fiberEffectTag, hookEffectTag,后两个参数对应useEffect的两个入参。

3.3.9 updateEffectImpl

function updateEffectImpl(fiberEffectTag, hookEffectTag, create, deps) {
    // 更新全局变量currentHook和workInProgressHook
    var hook = updateWorkInProgressHook();
    var nextDeps = deps === undefined ? null : deps;
    var destroy = undefined;

    if (currentHook !== null) {
      var prevEffect = currentHook.memoizedState;
      // destroy 是useEffect第一参数,传入的函数中返回的函数
      // destroy取的是上次更新的currentHook中的destroy
      destroy = prevEffect.destroy;
      // 依赖数组不存在,跳过依赖比较,必然触发更新,为了优化性能可以把依赖数组加上。
      if (nextDeps !== null) {
        var prevDeps = prevEffect.deps;
        // 比较本次和上次更新中useEffect的依赖变量是否变化
        if (areHookInputsEqual(nextDeps, prevDeps)) {
          //如果依赖没有发生改变,不会将hasEffect的标志位置1,调用pushEffect
          pushEffect(hookEffectTag, create, destroy, nextDeps);
          return;
        }
      }
    }
    // 将workInProgressHook的effectTag的fiberEffectTag标志位置1,在commit阶段会被用到。
    currentlyRenderingFiber$1.effectTag |= fiberEffectTag;
    // 如果依赖发生变化,将hasEffect的标志位置1,调用pushEffect,并赋值给workInProgressHook的memoizedState
    // hook的memoizedState是用来保存需要更新的effect
    hook.memoizedState = pushEffect(HasEffect | hookEffectTag, create, destroy, nextDeps);
  }

1)首先调用了updateWorkInProgressHook这个方法

  function updateWorkInProgressHook() {
    // This function is used both for updates and for re-renders triggered by a
    // render phase update. It assumes there is either a current hook we can
    // clone, or a work-in-progress hook from a previous render pass that we can
    // use as a base. When we reach the end of the base list, we must switch to
    // the dispatcher used for mounts.
    var nextCurrentHook;
    // 与mountWorkInProgressHook创建新hook不同,
    // 这里是从currentFiber的memoizedState上取hookList,通过next指针找到当前的hook
    if (currentHook === null) {// 如果是第一个hook,先找currentFiber,然后从currentFiber的memoizedState上取第一个hook
      var current = currentlyRenderingFiber$1.alternate;

      if (current !== null) {
        nextCurrentHook = current.memoizedState;
      } else {
        nextCurrentHook = null;
      }
    } else {// 非第一个hook的情况
      nextCurrentHook = currentHook.next;
    }

    var nextWorkInProgressHook;

    if (workInProgressHook === null) {
      nextWorkInProgressHook = currentlyRenderingFiber$1.memoizedState;
    } else {
      nextWorkInProgressHook = workInProgressHook.next;
    }
    // re-render 的情况,直接使用nextWorkInProgressHook
    if (nextWorkInProgressHook !== null) {
      // There's already a work-in-progress. Reuse it.
      workInProgressHook = nextWorkInProgressHook;
      nextWorkInProgressHook = workInProgressHook.next;
      currentHook = nextCurrentHook;
    } else {
      // Clone from the current hook.
      if (!(nextCurrentHook !== null)) {
        {
          throw Error( "Rendered more hooks than during the previous render." );
        }
      }
      // nextCurrentHook是上个更新或者渲染生成的对应当前workInProgressHook的hook
      // 更新currentHook这个全局变量
      currentHook = nextCurrentHook;
      //直接用currentFiber上的currentHook的属性生成一个新的hook
      var newHook = {
        memoizedState: currentHook.memoizedState,
        baseState: currentHook.baseState,
        baseQueue: currentHook.baseQueue,
        queue: currentHook.queue,
        next: null
      };

      if (workInProgressHook === null) {
        // This is the first hook in the list.
        currentlyRenderingFiber$1.memoizedState = workInProgressHook = newHook;
      } else {
        // Append to the end of the list.
        workInProgressHook = workInProgressHook.next = newHook;
      }
    }

    return workInProgressHook;
  }
// current hook list is the list that belongs to the current fiber. The
  // work-in-progress hook list is a new list that will be added to the
  // work-in-progress fiber.

  var currentHook = null;
  var workInProgressHook = null; 

这里分为两种情况,如果 nextWorkInProgressHook 存在那么就是 re-render,如果是 re-render 说明当前更新周期中还要继续处理 workInProgressHook。

如果不是 re-render,就生成一个新的 workInProgressHook。前面提到的mountWorkInProgressHook方法是用新的hook作为workInProgressHook,updateWorkInProgressHook这个方法用于基于首次渲染生成的hookList中的currentHook,生成一个workInProgressHook.
总之,updateWorkInProgressHook的作用有:
1.生成 workInProgressHook。
2.更新全局变量currentHook.

什么是re-render?
re-render指的是当前更新周期中产生了新的更新周期。标志是fiber===currentlyRenderingFiber

  1. 接着从全局变量currentHook的memoizedState属性上获取上次的effect.调用areHookInputsEqual(nextDeps, prevDeps)比较本次和上次更新中useEffect的依赖deps是否发生了变化。
    位置 react-reconciler > src > ReactFiberHooks.js
 function areHookInputsEqual(nextDeps, prevDeps) {
    {
      if (ignorePreviousDependencies) {
        // Only true when this component is being hot reloaded.
        return false;
      }
    }

    if (prevDeps === null) {
      {
        error('%s received a final argument during this render, but not during ' + 'the previous render. Even though the final argument is optional, ' + 'its type cannot change between renders.', currentHookNameInDev);
      }

      return false;
    }

    {
      // Don't bother comparing lengths in prod because these arrays should be
      // passed inline.
      if (nextDeps.length !== prevDeps.length) {
        error('The final argument passed to %s changed size between renders. The ' + 'order and size of this array must remain constant.\n\n' + 'Previous: %s\n' + 'Incoming: %s', currentHookNameInDev, "[" + prevDeps.join(', ') + "]", "[" + nextDeps.join(', ') + "]");
      }
    }
    //遍历依赖数组,用ObjectIs来比较依赖deps是否发生变化
    for (var i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
      if (objectIs(nextDeps[i], prevDeps[i])) {
        continue;
      }

      return false;
    }

    return true;
  }

看下ObjectIs的实现:
位置react-dom.development\packages\shared\objectIs.js

  function is(x, y) {
    return x === y && (x !== 0 || 1 / x === 1 / y) || x !== x && y !== y // eslint-disable-line no-self-compare
    ;
  }

  var objectIs = typeof Object.is === 'function' ? Object.is : is;

如果比较后发现依赖deps不变,会调用pushEffect (hookEffectTag, create, destroy, nextDeps),此时没有将HasEffect标志位置1。表示没有 effect 需要执行,因此在 commit 阶段执行 unmount 和 mount时,因为effect的tag不同,不会调用 destroy 和 create 方法,然后 return.
如果 currentHook 等于 null 或是deps 发生改变,将传入的 fiberEffectTag 设置到当前 fiber 对象的 effectTag 上,最后调用 pushEffect,将hookEffectTag 的HasEffect标志位置1,(HasEffect | hookEffectTag),create,nextDeps,将 pushEffect 的返回的新effect赋值给 workInProgressHook的memoizedState。

更新调用时,useLayoutEffect与useEffect的代码执行流程完全相同,区别在于方法时用updateEffectImpl传入的fiberEffectTag和hookEffectTag不同。

function updateLayoutEffect(create, deps) {
    // 与updateEffect相同均调用updateEffectImpl,区别在于传入的tag不同。
    // fiberEffectTag 少传了Passive的标志;
    // hookEffectTag传入Layout,代表hookEffect中的useLayoutEffect
    return updateEffectImpl(Update, Layout, create, deps);
  }

到这里,useEffect就执行完了。

Render阶段流程图

可以看出初次渲染和更新时调用useEffect只是将hookList挂在workInProgressFiber上,以及将effectList挂到hook上和workInProgressFiber.updateQueue上。但是挂在workInProgressFiber.updateQueue上面的effect链表还没有被用到。

effectList会在哪里被用到呢?

React的有两个重要的阶段,render 阶段 和 commit阶段 。以上是在render阶段完成的,接下来会在commit阶段执行effectList。

3.4 commit 阶段

3.4.0 completeUnitOfWork

作用:构建effectFiber链

3.4.1 commitRoot

function commitRoot(root) {
    // 获取render时优先级
    var renderPriorityLevel = getCurrentPriorityLevel();
    // 以最高优先级ImmediatePriority执行第二个参数fn,fn会被立即执行
    runWithPriority$1(ImmediatePriority, commitRootImpl.bind(null, root, renderPriorityLevel));
    return null;
  }

commitRoot 是 commit阶段入口。
runWithPriority$1这个函数作用是先记录当前优先级,然后以指定的优先级执行函数,执行完毕会,恢复优先级。
这里会调用runWithPriority$1以最高优先级ImmediatePriority执行第二个参数commitRootImpl.bind(null, root, renderPriorityLevel)。

3.4.2 commitRootImpl

commitRootImpl 方法较为复杂,它主要负责执行 effectList上的更新,整个过程分为 before mutation,mutation 和 layout 三个阶段,每个阶段会执行不同的更新任务。
以before mutation 为例,

// ------------- before mutation 阶段开始  -----------------
      startCommitSnapshotEffectsTimer();
      prepareForCommit(root.containerInfo);
      nextEffect = firstEffect;

      do {
        {
          invokeGuardedCallback(null, commitBeforeMutationEffects, null);

          if (hasCaughtError()) {
            if (!(nextEffect !== null)) {
              {
                throw Error( "Should be working on an effect." );
              }
            }

            var error = clearCaughtError();
            captureCommitPhaseError(nextEffect, error);
            nextEffect = nextEffect.nextEffect;
          }
        }
      } while (nextEffect !== null);

      stopCommitSnapshotEffectsTimer();
      // ------------- before mutation 阶段结束  -----------------

这个阶段会遍历effectList,执行invokeGuardedCallback(null, commitBeforeMutationEffects, null);
在另外的mutation和layout2个阶段则会执行
invokeGuardedCallback(null, commitMutationEffects, null, root, renderPriorityLevel); 和
invokeGuardedCallback(null, commitLayoutEffects, null, root, expirationTime);
更新的核心是调用了commitBeforeMutationEffects,commitMutationEffects 和commitLayoutEffects三个方法。

3.4.3 commitBeforeMutationEffects

 function commitBeforeMutationEffects() {
    while (nextEffect !== null) {
      var effectTag = nextEffect.effectTag;

      if ((effectTag & Snapshot) !== NoEffect) {
        setCurrentFiber(nextEffect);
        recordEffect();
        var current = nextEffect.alternate;
        commitBeforeMutationLifeCycles(current, nextEffect);
        resetCurrentFiber();
      }
      // useEffect 的 effectTag曾传入Passive
      if ((effectTag & Passive) !== NoEffect) {
        // If there are passive effects, schedule a callback to flush at
        // the earliest opportunity.
        if (!rootDoesHavePassiveEffects) {
          rootDoesHavePassiveEffects = true;
          // NormalPriority 不是最高优先级,会推迟执行 flushPassiveEffects
          scheduleCallback(NormalPriority, function () {
            flushPassiveEffects();
            return null;
          });
        }
      }

      nextEffect = nextEffect.nextEffect;
    }
  }

flushPassiveEffects 是用于处理副作用的。最终会遍历effectList执行每个effect的create和destory函数。
NormalPriority 不是最高优先级,会推迟执行 flushPassiveEffects,所以最后再看flushPassiveEffects。

3.4.4 commitMutationEffects

function commitMutationEffects(root, renderPriorityLevel) {
    // TODO: Should probably move the bulk of this function to commitWork.
    while (nextEffect !== null) {
      setCurrentFiber(nextEffect);
      var effectTag = nextEffect.effectTag;

      if (effectTag & ContentReset) {
        commitResetTextContent(nextEffect);
      }

      if (effectTag & Ref) {
        var current = nextEffect.alternate;

        if (current !== null) {
          commitDetachRef(current);
        }
      } // The following switch statement is only concerned about placement,
      // updates, and deletions. To avoid needing to add a case for every possible
      // bitmap value, we remove the secondary effects from the effect tag and
      // switch on that value.

      var primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating);

      switch (primaryEffectTag) {
        case Placement:
          {
            commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is
            // inserted, before any life-cycles like componentDidMount gets called.
            // TODO: findDOMNode doesn't rely on this any more but isMounted does
            // and isMounted is deprecated anyway so we should be able to kill this.

            nextEffect.effectTag &= ~Placement;
            break;
          }

        case PlacementAndUpdate:
          {
            // Placement
            commitPlacement(nextEffect); // Clear the "placement" from effect tag so that we know that this is
            // inserted, before any life-cycles like componentDidMount gets called.

            nextEffect.effectTag &= ~Placement; // Update

            var _current = nextEffect.alternate;
            commitWork(_current, nextEffect);
            break;
          }

        case Hydrating:
          {
            nextEffect.effectTag &= ~Hydrating;
            break;
          }

        case HydratingAndUpdate:
          {
            nextEffect.effectTag &= ~Hydrating; // Update

            var _current2 = nextEffect.alternate;
            commitWork(_current2, nextEffect);
            break;
          }

        case Update:
          {
            var _current3 = nextEffect.alternate;
            commitWork(_current3, nextEffect);
            break;
          }

        case Deletion:
          {
            commitDeletion(root, nextEffect, renderPriorityLevel);
            break;
          }
      } // TODO: Only record a mutation effect if primaryEffectTag is non-zero.


      recordEffect();
      resetCurrentFiber();
      nextEffect = nextEffect.nextEffect;
    }
  }

commitMutationEffects 提交HostComponent的side effect,也就是DOM节点的操作(增删改), 这里和我们给出的例子中的useEffect 有关是update操作,这里会执行 commitWork函数。

3.4.5 commitWork

function commitWork(current, finishedWork) {

    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case MemoComponent:
      case SimpleMemoComponent:
      case Block:
        {
          // Layout effects are destroyed during the mutation phase so that all
          // destroy functions for all fibers are called before any create functions.
          // This prevents sibling component effects from interfering with each other,
          // e.g. a destroy function in one component should never override a ref set
          // by a create function in another component during the same commit.
          // 执行useLayoutEffect的destroy函数
          commitHookEffectListUnmount(Layout | HasEffect, finishedWork);
          return;
        }
        // 省略其他case。。。
      }
  }

commitWork作用是对DOM节点上的属性进行更新.这里对于FunctionComponent,它会执行useLayoutEffect的destroy函数,commitHookEffectListUnmount(Layout | HasEffect, finishedWork);

3.4.6 commitHookEffectListUnmount

function commitHookEffectListUnmount(tag, finishedWork) {
    var updateQueue = finishedWork.updateQueue;
    var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;

    if (lastEffect !== null) {
      var firstEffect = lastEffect.next;
      var effect = firstEffect;

      do {
        if ((effect.tag & tag) === tag) {
          // Unmount
          var destroy = effect.destroy;
          effect.destroy = undefined;

          if (destroy !== undefined) {
            destroy();
          }
        }

        effect = effect.next;
      } while (effect !== firstEffect);
    }
  }

作用: 从finishedWork 这个fiber上取下updateQueue上的effectList,遍历effectList,执行tag对应的destroy函数。

3.4.7 commitLayoutEffects

function commitLayoutEffects(root, committedExpirationTime) {
    // TODO: Should probably move the bulk of this function to commitWork.
    while (nextEffect !== null) {
      setCurrentFiber(nextEffect);
      var effectTag = nextEffect.effectTag;
      // 当有Update/Callback的effectTag,执行commitLifeCycles(),执行effect.destroy()
      if (effectTag & (Update | Callback)) {
        recordEffect();
        var current = nextEffect.alternate;
        commitLifeCycles(root, current, nextEffect);
      }

      if (effectTag & Ref) {
        recordEffect();
        commitAttachRef(nextEffect);
      }

      resetCurrentFiber();
      nextEffect = nextEffect.nextEffect;
    }
  }

commitLifeCycles(root, current, nextEffect);

3.4.8 commitLifeCycles

function commitLifeCycles(finishedRoot, current, finishedWork, committedExpirationTime) {
    switch (finishedWork.tag) {
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent:
      case Block:
        {
          // At this point layout effects have already been destroyed (during mutation phase).
          // This is done to prevent sibling component effects from interfering with each other,
          // e.g. a destroy function in one component should never override a ref set
          // by a create function in another component during the same commit.
          commitHookEffectListMount(Layout | HasEffect, finishedWork);

          return;
        }
      // 省略后面的代码。。。
    }
  }

因为当前是FunctionComponent,会执行commitHookEffectListMount(Layout | HasEffect, finishedWork); 遍历EffectList ,执行useLayoutEffect的create方法.

3.4.9 commitHookEffectListMount

function commitHookEffectListMount(tag, finishedWork) {
    var updateQueue = finishedWork.updateQueue;
    var lastEffect = updateQueue !== null ? updateQueue.lastEffect : null;

    if (lastEffect !== null) {
      var firstEffect = lastEffect.next;
      var effect = firstEffect;

      do {
        if ((effect.tag & tag) === tag) {
          // Mount
          var create = effect.create;
          effect.destroy = create();

          {
            var destroy = effect.destroy;

            if (destroy !== undefined && typeof destroy !== 'function') {
              var addendum = void 0;

              if (destroy === null) {
                addendum = ' You returned null. If your effect does not require clean ' + 'up, return undefined (or nothing).';
              } else if (typeof destroy.then === 'function') {
                addendum = '\n\nIt looks like you wrote useEffect(async () => ...) or returned a Promise. ' + 'Instead, write the async function inside your effect ' + 'and call it immediately:\n\n' + 'useEffect(() => {\n' + '  async function fetchData() {\n' + '    // You can await here\n' + '    const response = await MyAPI.getData(someId);\n' + '    // ...\n' + '  }\n' + '  fetchData();\n' + "}, [someId]); // Or [] if effect doesn't need props or state\n\n" + 'Learn more about data fetching with Hooks: https://fb.me/react-hooks-data-fetching';
              } else {
                addendum = ' You returned: ' + destroy;
              }

              error('An effect function must not return anything besides a function, ' + 'which is used for clean-up.%s%s', addendum, getStackByFiberInDevAndProd(finishedWork));
            }
          }
        }

        effect = effect.next;
      } while (effect !== firstEffect);
    }
  }

这个函数作用是遍历finishedWork.updateQueue 上面的EffectList ,执行指定符合tag条件的 effect的create方法.并创建effect的destory 函数。

3.4.10 flushPassiveEffects

function flushPassiveEffects() {
    if (pendingPassiveEffectsRenderPriority !== NoPriority) {
      var priorityLevel = pendingPassiveEffectsRenderPriority > NormalPriority ? NormalPriority : pendingPassiveEffectsRenderPriority;
      pendingPassiveEffectsRenderPriority = NoPriority;
      return runWithPriority$1(priorityLevel, flushPassiveEffectsImpl);
    }
  }

flushPassiveEffects 会计算临时的优先级,用临时优先级执行回调 flushPassiveEffectsImpl

3.4.11 flushPassiveEffectsImpl

 function flushPassiveEffectsImpl() {
    if (rootWithPendingPassiveEffects === null) {
      return false;
    }

    var root = rootWithPendingPassiveEffects;
    var expirationTime = pendingPassiveEffectsExpirationTime;
    rootWithPendingPassiveEffects = null;
    pendingPassiveEffectsExpirationTime = NoWork;

    if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
      {
        throw Error( "Cannot flush passive effects while already rendering." );
      }
    }

    var prevExecutionContext = executionContext;
    executionContext |= CommitContext;
    var prevInteractions = pushInteractions(root);

    {
      // Note: This currently assumes there are no passive effects on the root fiber
      // because the root is not part of its own effect list.
      // This could change in the future.
      var _effect2 = root.current.firstEffect;

      while (_effect2 !== null) {
        {
          setCurrentFiber(_effect2);
          invokeGuardedCallback(null, commitPassiveHookEffects, null, _effect2);

          if (hasCaughtError()) {
            if (!(_effect2 !== null)) {
              {
                throw Error( "Should be working on an effect." );
              }
            }

            var _error5 = clearCaughtError();

            captureCommitPhaseError(_effect2, _error5);
          }

          resetCurrentFiber();
        }

        var nextNextEffect = _effect2.nextEffect; // Remove nextEffect pointer to assist GC

        _effect2.nextEffect = null;
        _effect2 = nextNextEffect;
      }
    }

    {
      popInteractions(prevInteractions);
      finishPendingInteractions(root, expirationTime);
    }

    executionContext = prevExecutionContext;
    flushSyncCallbackQueue(); // If additional passive effects were scheduled, increment a counter. If this
    // exceeds the limit, we'll fire a warning.

    nestedPassiveUpdateCount = rootWithPendingPassiveEffects === null ? 0 : nestedPassiveUpdateCount + 1;
    return true;
  }

flushPassiveEffectsImpl 中会有一段类似于commitIRootImpl中的while循环
遍历effectList,执行 invokeGuardedCallback(null, commitPassiveHookEffects, null, _effect2);

3.4.6 commitPassiveHookEffects

function commitPassiveHookEffects(finishedWork) {
    if ((finishedWork.effectTag & Passive) !== NoEffect) {
      switch (finishedWork.tag) {
        case FunctionComponent:
        case ForwardRef:
        case SimpleMemoComponent:
        case Block:
          {
            // TODO (#17945) We should call all passive destroy functions (for all fibers)
            // before calling any create functions. The current approach only serializes
            // these for a single fiber.
            commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
            commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);
            break;
          }
      }
    }
  }

这个函数的作用是:
1)遍历effectList,执行useEffect 的destory方法。
2)遍历effectList,执行useEffect 的create方法。

Commit阶段流程图

4.总结

4.1 render阶段:
在workInProgressFiber上创建(memoizedState)hookList 和(updateQueue)effectList
4.2 commit阶段:
遍历finishedWork 的(updateQueue)effectList,执行副作用。


作者:迷人的洋葱葱
链接:https://www.jianshu.com/p/a838d8c22089

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值