【React】2091- React 19 可能会让你的网站变得更慢!

科技   2024-07-02 23:16   日本  

两个月前,备受广大开发者期待的 React 19 宣布发布:

但除了各种亮眼的新功能和一些改进优化之外,还有一个小的改变直到上周才被大家注意到,这这个改动可能会显著降低许多依赖 React 的网站的性能。

一切都始于这条推文:

Dominik(又名 TkDodo),是被广泛使用的 TanStack Query 的核心维护者之一。

他主要提出的问题就是:React 19 禁用了在同一 <Suspense> 包裹的下的同级元素的并行渲染,然后改成了瀑布流获取数据的方式。

随后引起了广大开发者非常热烈的讨论:

https://x.com/AdamRackis/status/1800588094560772224
https://x.com/tannerlinsley/status/1800903098464096664
https://x.com/AdamRackis/status/1800663066922963264

下面是一个实际的例子:

https://github.com/facebook/react/pull/26380#issuecomment-2166178673

最可气的是,虽然就性能而言,这是一个非常大的变化,会影响到很多依赖这种模式的开发者,但最终却只有一个很的点提到了这一变化。

为了更好的理解,我们快速回顾一下 ReactSuspense 的用法。

Suspense 是一个 React 组件,可以让网站在一个需要异步加载的组件渲染完成之前显示一个备用组件(一般用来显示 Loading 组件)。

它的使用方式如下:

<Suspense fallback={<Loading />}>
  <ComponentThatFetchesDataOrIsLazyLoaded />
</Suspense>

尽管 Suspense 成为 React API 的一部分已经有一段时间了,但很长一段时间以来,它的唯一官方推荐的用法是使用 React.lazy 来进行组件懒加载,这对于拆分代码并在需要时仅加载拆分的部分组件非常有用。

当与 React.lazy 一起使用时,当第一次尝试渲染懒加载的组件时(即在懒加载之前),它将触发 Suspense boundary(即包裹组件的 Suspense)并渲染 fallback 组件,直到获取组件的代码完成了,然后它会渲染组件本身。

虽然我们很久以来一直期待官方在客户端上为 Suspense 提供数据获取的支持(使用 RSC 时已经可以在服务器上运行了),但直到现在我们还没有真的能用上它,尽管如此,许多库(其中之一就是 TanStack Query)通过研究 React 的内部实现了它。所以,目前有很多生产环境中的网站会使用 Suspense 在客户端获取数据。

截至目前(React 18.3.1),当我们使用支持 Suspense 的数据获取或在同一 Suspense boundary 内使用多个组件进行延迟加载时,React 将在退出渲染之前尝试渲染所有同级的组件,即使是第一个同级组件中断。

这意味着这些同级中发生的数据获取或延迟加载将全部 并行 启动。

我们看下面这个例子:

function App({
  return (
    <>
      <Suspense fallback={"Loading..."}>
        <ComponentThatFetchesData val={1} />
        <ComponentThatFetchesData val={2} />
        <ComponentThatFetchesData val={3} />
      </Suspense>
    </>

  );
}

const ComponentThatFetchesData = ({ val }) => {
  const result = fetchSomethingSuspense(val);

  return <div>{result}</div>;
};

在此示例中(在 React 18 中),即使 fetchSomethingSuspense 导致第一个 ComponentThatFetchesData 渲染中断,React 仍会尝试渲染其他同级的组件,这将会触发并行获取每个组件的数据。

通过查看控制台打印的日志我们可以很明显的看出这一点:

所有数据获取几乎同时启动。

现在让我们看看当我们在 React 19 (canary) 中运行完全相同的代码时会发生什么:

很明显请求变成了瀑布流(串行),每个数据获取仅在前一个数据获取完成后才启动。

发生这种情况就是因为下面这个 PR:https://github.com/facebook/react/pull/26380

在本次 PR 合并之后,React 将不再尝试渲染同一 Suspense 内的所有同级组件,而是会在第一个 Suspense 的组件上退出,直到第一个组件的数据准备完成后才会继续获取下一个组件的数据。

此外,这种新行为不仅会影响 Suspense 数据获取的使用,还会影响 React.lazy 的使用,React.lazy 已得到官方支持,并且使用非常广泛。

幸运的是,这个故事有了一个圆满的结局。在经历了大量的公开反对、激烈的讨论之后,React 团队打消了这个念头,决定暂时搁置这一变更。

这并不是社区第一次对 React 中引入的更改提出抵制了,React 的很多改动都没有过多考虑 在 MetaVercel 之外的社区是如何使用的。

React 团队(尤其是 Vercel)推动 RSC 成为 React 构建的基本组成部分就是一个这样的例子。

对此,大家怎么看?

最后

参考:

  • https://github.com/facebook/react/pull/26380
  • https://blog.codeminer42.com/how-react-19-almost-made-the-internet-slower/
  • https://stackblitz.com/edit/vitejs-vite-55rddj?file=src%2FApp.jsx

前端自习课
每日清晨,享受一篇前端优秀文章。
 最新文章