面试官:既然你提到了 <Suspense/>,那你知道懒加载的原理吗?我:😭

7,765 阅读4分钟

在面试过程中提到懒加载,很多的面试官都喜欢再往深一点问:也就是懒加载的原理。

笔者也是在两次面试过程中被问过,所以写了这一篇文章。

🤖 举一个最简单的例子

一个懒加载组件,promise加载完成之前显示loading,加载完成之后就显示实际的组件Lazy

懒加载就这么简单,所以面试的时候问到这个问题,一般都想考一下你是否明白他的原理,一般不懂也没关系,如果你懂一点,就可以和其他面试者区分开来了。

当然现在这行情懂不懂也无所谓了。😭

const Lazy = React.lazy(
  () =>
    new Promise((resolve) => {
      setTimeout(() => {
        resolve({ default: () => <div>Lazy</div> })
      }, 2000)
    }),
)

function App() {
  return (
    <Suspense fallback='loading'>
      <Lazy />
    </Suspense>
  )
}

🤖 小科普:科普一下react原理,不用担心,小白也能看懂

render阶段创建fiber

commit阶段遍历fiber树创建一棵dom树出来

QQ20241104-104711.png

🤖 原理解析

React.lazySuspense的源码就不放了,之前写的源码解析文章推荐到首页也没多少人看😭所以直接讲原理

初步原理

promise 没有加载完成前,Suspense Fiber 下面创建的是 fallback Fiber,也就是这个 loading

组件加载完成,suspense fiber 的子节点去挂载真正的组件,也就是这个 Lazy 组件

更细节的原理

可以看到在render阶段,react 会执行一个循环,不断的执行beginWork,一个一个的创建 fiber 节点

如上图 beginWork 创建 App Fiber,然后 Suspense Fiber,然后 Lazy Fiber,然后 div Fiber

do {
  try {
    while (workInProgress) {
      const next = beginWork(workInProgress)
      workInProgress = next
    }
    break
  } catch (thrownValue) {
    handleError(root, thrownValue)
  }
} while (true)
  1. beginWork 创建到 Lazy Fiber 的时候,只要组件未加载完成,也就是这个 React.lazy 这个promise 还在 pending, promise 就会被当做错误 throw 抛出

  2. 错误被上面的 handleError 捕获到,他会

    • 找到最近的父亲 Suspense Fiber
    • Suspense Fiber 标记 DidCapture Flag
    • 把 React.lazy 的 promise 保存在 suspense FiberupdateQueue
    • workInProgress 又指向 Suspense Fiber
  3. 处理完错误,这时候又开始 beginWork 工作循环。经过刚才的错误,workInProgressLazy Fiber 变成了他的父亲 Suspense Fiber

    也就是说我们的 beginWork 现在是第二次处理 Suspense ,这时候发现刚才标记的DidCapture Flag,他就会先创建一个 fallback Fiber

那等一下这个 fallback Fiber 创建的 dom 是不是就是我们下图中的 loading 啊,介就算齐活了,页面就显示了一个 loading 的骨架屏了。 QQ20241104-104802.png

页面 loading 是出来了,那实际的 lazy 组件怎么渲染出来呢?

  1. 我们上面说处理错误的时候,Suspense Fiber 是不是有一个 updateQueue ,保存了 React.lazypromise

  2. 那其实在 commit 阶段我们遍历 fiber 树创建 Dom 树的时候,遍历到 Suspense Fiber,他会给 updateQueue 里面的 promise 注册一个回调 promise.then(回调)

  3. promise resolved 了,也就是远程组件懒加载好了,触发回调,回调里面就是会重新开始工作循环创建新 fiber 树,并且给 Suspense 组件做标记,告诉 react 等一下 suspense 组件要重渲染。

  4. 工作循环走到 suspensebeginWork 进行重渲染,这次没有标记 DidCapture Flag,所以这次就创建 Lazy Fiber 而不是 fallback Fiber

QQ20241104-110050.png

这里面我简化了一部分,去掉了里面关于 OffScreen 这个 react 的内置组件,也就是常说的 react 版的 KeepAlive

🤖 总结

这么回答,应该能拿到六七十分,那么这个问题你在面试官那里就算过了 😊

但是其实这个问题只有一面同事面会问,二面项目面才是重点😭

🤖 相关

面试官:让你实现一个高性能 Tree 组件磕磕绊绊的,你好意思干了三年前端?我:😭

面试官:听说你简历里写的精通 React 源码,那你给我讲讲 React Scheduler 呗?我:😡 工资加 2K

面试官:你说你做过组件库,肯定了解过复杂组件状态管理的useSyncExternalStore吧?我:😭

面试官:你跟我说 setState 是同步的,它不是异步的吗?背错面试题了吧你!我:😭

面试官:你说你开发过组件库,那你怎么会不知道受控组件?面试就到这里吧。我:😭

版权归许泽川所有

如需转载,请提前询问本人的许可