第三章render函数-提交阶段:3.6 layout 阶段

123 阅读3分钟

由于 JS 的同步执行阻塞了主线程,所以此时 JS 已经可以获取到新的DOM,但是浏览器对新的DOM并没有完成渲染。该阶段触发的生命周期钩子和hook可以直接访问到已经改变后的DOM,即该阶段是可以参与DOM layout的阶段。

与前两个阶段类似,layout阶段也是遍历effectList,执行函数。具体执行的函数是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

commitLayoutEffects一共做了两件事:

  1. commitLayoutEffectOnFiber(调用生命周期钩子和hook相关操作)
  2. commitAttachRef(赋值 ref)
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

commitLayoutEffectOnFiber方法会根据fiber.tag对不同类型的节点分别处理。

  • 对于ClassComponent,他会通过current === null?区分是mount还是update,调用componentDidMountcomponentDidUpdate。触发状态更新的this.setState如果赋值了第二个参数回调函数,也会在此时调用。

  • 对于FunctionComponent及相关类型,他会调用useLayoutEffect hook的回调函数,调度useEffect的销毁与回调函数。对于HostRoot,即rootFiber,如果赋值了第三个参数回调函数(ReactDOM.render(, document.querySelector("#root"), function(){}),也会在此时调用。

    • 相关类型指特殊处理后的FunctionComponent,比如ForwardRef、React.memo包裹的FunctionComponent。
    • useLayoutEffect hook从上一次更新的销毁函数调用到本次更新的回调函数调用是同步执行的。而useEffect则需要先调度,在Layout阶段完成后再异步执行。这就是useLayoutEffect与useEffect的区别。
## commitAttachRef

获取DOM实例,更新ref

JavaScript 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;
}

} }

current Fiber树切换

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

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

componentDidMount和componentDidUpdate会在layout阶段执行。此时current Fiber树已经指向更新后的Fiber树,在生命周期钩子内获取的DOM就是更新后的。


JavaScript root.current = finishedWork;// (在mutation阶段结束后,layout阶段开始前)

参考链接

关于作者

作者:Wandra

内容:算法 | 趋势 |源码|Vue | React | CSS | Typescript | Webpack | Vite | GithubAction | GraphQL | Uniqpp。

专栏:欢迎关注🌹

本专栏致力于分析热门项目,如果本文对你有帮助的话,欢迎点赞或关注。