315 - 《Suspense》

发布于 2023年6月27日

1、为啥会有 Suspense?

其实最初是为了解 Client 的 CLS 问题。CLS 全称 Cumulative Layout Shift(累积布局偏移),可以看 web.dev 的详细介绍 ,如果没时间,通过下面两个形象的例子应该也能感受到是啥问题。

例子 1 来源于 Facebook Ad Manager,大家可以数数有多少个 Loading/Spinner 占位符,这应该是 Ajax 出来后比较典型的问题之一,也称「Spinner Hell」;例子 2 可太有趣了,哈哈,坑用户没商量。

Suspense 是 React 针对这个问题的解。比如下面代码的 Foo 和 Bar,如果都有 loading 状态,会统一先渲染 LoadingComponent,等 Foo 和 Bar 都渲染完成之后,再一下子显示出来。当然,在 Suspense 之外也自行通过维护大量的 Loading 状态来处理,只不过比较麻烦。

<Suspense fallback={<Spinner />}>
  <Foo />
  <Bar />
</Suspense>

大家可能会好奇的是,Suspense 是如何知道子组件里的 Foo 和 Bar 的加载状态的?

2、Suspense 原理。

Suspense 原理类似 Error Boundary,在通过 throw + try…catch 实现子组件消息的上报,这用于 Error Boundary 还算合理,用于 Suspense 总感觉有些奇怪。不同的是,用于 Suspense 时,throw 的是 Promise,然后 Suspense 基于 Promise 的状态觉得子组件的加载状态。

function Foo() {
  const [fetchRequest] = React.useState(() => {
    return fetch('/api/foo');
  });
  if (fetchRequest.status === 'pending') {
    throw fetchRequest;
  }
  return </>;
}

React Query 和 SWR 都可通过配置切换到 Suspense 模式。

import useSWR from 'swr';
function Profile () {
  const { data } = useSWR('/api/user', fetcher, { suspense: true })
  return <div>hello, {data.name}</div>
}

3、Suspense 应用场景。

react.dev 官网 提供了大量 Suspense 的应用场景。

举一些场景的:

1)等一组组件全部加载完成后,一次性渲染
2)加载新内容时显示老内容(基于 useDeferredValue)

function Foo() {
  const deferredQuery = useDeferredValue(query);
  const isStale = query !== deferredQuery;
}

3)路由切换时等新页面的组件加载完成后再

内容预览已结束

此内容需要会员权限。请先登录以查看完整内容。