大家好,我是 ConardLi。
两个月前,备受广大开发者期待的 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
最可气的是,虽然就性能而言,这是一个非常大的变化,会影响到很多依赖这种模式的开发者,但最终却只有一个很的点提到了这一变化。
为了更好的理解,我们快速回顾一下 React
的 Suspense
的用法。
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
的很多改动都没有过多考虑 在 Meta
和 Vercel
之外的社区是如何使用的。
React
团队(尤其是 Vercel
)推动 RSC
成为 React
构建的基本组成部分就是一个这样的例子。
对此,大家怎么看?
参考: