添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
腼腆的西瓜  ·  React-admin - ...·  2 周前    · 
大力的荒野  ·  Getting started with ...·  2 周前    · 
追风的花生  ·  Map in React js with ...·  2 周前    · 
温柔的保温杯  ·  How to Use the ...·  2 周前    · 
奔跑的凳子  ·  Loading in Rive Files ...·  1 周前    · 
体贴的拐杖  ·  [Identification of ...·  1 月前    · 
慷慨大方的跑步鞋  ·  验证码_哔哩哔哩·  3 月前    · 
听话的核桃  ·  Solved: Need to print ...·  3 月前    · 
爱热闹的金鱼  ·  福岛县概况·  1 年前    · 
点亮⭐不迷路

该阶段之所以称为 layout ,因为该阶段的代码都是在 DOM 修改完成( mutation阶段 完成)后执行的。

注意:由于 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 源码

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;

commitLayoutEffects 一共做了两件事:

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

  2. commitAttachRef(赋值 ref)

# commitLayoutEffectOnFiber

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

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

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

this.setState({ xxx: 1 }, () => {
  console.log("i am update~");
});
  • 对于 FunctionComponent 及相关类型,他会调用 useLayoutEffect hook 回调函数 ,调度 useEffect 销毁 回调 函数

相关类型 指特殊处理后的 FunctionComponent ,比如 ForwardRef React.memo 包裹的 FunctionComponent

  switch (finishedWork.tag) {
    // 以下都是FunctionComponent及相关类型
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
      // 执行useLayoutEffect的回调函数
      commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
      // 调度useEffect的销毁函数与回调函数
      schedulePassiveEffects(finishedWork);
      return;

你可以从 这里 看到这段代码

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

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

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

这就是 useLayoutEffect useEffect 的区别。

  • 对于 HostRoot ,即 rootFiber ,如果赋值了第三个参数 回调函数 ,也会在此时调用。
ReactDOM.render(<App />, document.querySelector("#root"), function() {
  console.log("i am mount~");
});

# commitAttachRef

commitLayoutEffects 会做的第二件事是 commitAttachRef

你可以在 这里 看到 commitAttachRef 源码

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;

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

# current Fiber树切换

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

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

root.current = finishedWork;

你可以在 这里 看到这行代码

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

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

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

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

# 总结

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

# 参考资料

useeffect-vs-uselayouteffect-examples

hooks-reference.html#uselayouteffect