该阶段触发的生命周期钩子和hook可以直接访问到已经改变后的DOM,即该阶段是可以参与DOM layout的阶段。

与前两个阶段类似,layout阶段也是遍历effectList,执行函数。

具体执行的函数是commitLayoutEffects

commitLayoutEffects

代码如下:

  1. function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  2. while (nextEffect !== null) {
  3. const effectTag = nextEffect.effectTag;
  4. // 调用生命周期钩子和hook
  5. if (effectTag & (Update | Callback)) {
  6. const current = nextEffect.alternate;
  7. commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
  8. }
  9. // 赋值ref
  10. if (effectTag & Ref) {
  11. commitAttachRef(nextEffect);
  12. }
  13. }
  14. }

commitLayoutEffects一共做了两件事:

  1. commitLayoutEffectOnFiber(调用生命周期钩子hook相关操作)

  2. commitAttachRef(赋值 ref)

你可以在看到commitLayoutEffectOnFiber源码(commitLayoutEffectOnFiber为别名,方法原名为commitLifeCycles

触发状态更新this.setState如果赋值了第二个参数回调函数,也会在此时调用。

  • 对于及相关类型,他会调用useLayoutEffect hook回调函数,调度useEffect销毁回调函数
  1. switch (finishedWork.tag) {
  2. // 以下都是FunctionComponent及相关类型
  3. case FunctionComponent:
  4. case ForwardRef:
  5. case SimpleMemoComponent:
  6. case Block: {
  7. // 执行useLayoutEffect的回调函数
  8. commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
  9. // 调度useEffect的销毁函数与回调函数
  10. schedulePassiveEffects(finishedWork);
  11. return;
  12. }

你可以从这里layout阶段 - 图5 (opens new window)看到这段代码

在上一节介绍时介绍过,mutation阶段会执行useLayoutEffect hook销毁函数

结合这里我们可以发现,useLayoutEffect hook从上一次更新的销毁函数调用到本次更新的回调函数调用是同步执行的。

useEffect则需要先调度,在Layout阶段完成后再异步执行。

这就是useLayoutEffectuseEffect的区别。

  • 对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数,也会在此时调用。

commitAttachRef

  1. function commitAttachRef(finishedWork: Fiber) {
  2. if (ref !== null) {
  3. const instance = finishedWork.stateNode;
  4. // 获取DOM实例
  5. switch (finishedWork.tag) {
  6. case HostComponent:
  7. instanceToUse = getPublicInstance(instance);
  8. break;
  9. default:
  10. instanceToUse = instance;
  11. }
  12. if (typeof ref === "function") {
  13. // 如果ref是函数形式,调用回调函数
  14. ref(instanceToUse);
  15. } else {
  16. // 如果ref是ref实例形式,赋值ref.current
  17. ref.current = instanceToUse;
  18. }
  19. }

代码逻辑很简单:获取DOM实例,更新ref

至此,整个layout阶段就结束了。

在结束本节的学习前,我们关注下这行代码:

你可以在这里 (opens new window)看到这行代码

在我们介绍过,workInProgress Fiber树commit阶段完成渲染后会变为current Fiber树。这行代码的作用就是切换fiberRootNode指向的current Fiber树

那么这行代码为什么在这里呢?(在mutation阶段结束后,layout阶段开始前。)

我们知道componentWillUnmount会在mutation阶段执行。此时current Fiber树还指向前一次更新的Fiber树,在生命周期钩子内获取的DOM还是更新前的。

总结

从这节我们学到,layout阶段会遍历effectList,依次执行commitLayoutEffects。该方法的主要工作为“根据effectTag调用不同的处理函数处理Fiber并更新ref