Components

<Suspense>

编辑此页面

一个组件,用于跟踪其下所有读取的资源,并在这些资源解析完成之前显示一个后备占位符状态。SuspenseShow 的不同之处在于它是非阻塞的,即两个分支同时存在,即使当前不在 DOM 中。这意味着在子组件加载时可以渲染后备内容。这对于加载状态和其他异步操作非常有用。

import { Suspense } from "solid-js"
import type { JSX } from "solid-js"
function Suspense(props: {
fallback?: JSX.Element
children: JSX.Element
}): JSX.Element

这是一个 Suspense 组件的示例,在 User 组件加载时显示一个加载旋转器。

<Suspense fallback={<LoadingSpinner />}>
<AsyncComponent />
</Suspense>

嵌套 Suspense

<Suspense> 在 suspense 边界下读取资源时触发,并等待直到在 suspense 边界下读取的所有资源都已解析。然而,通常你可能不想要这种行为。例如,如果你的整个页面都被 suspense 包裹,你可能不希望只用于填充页面某部分的资源触发 suspense。在这种情况下,你可以将该资源使用包裹在它自己的 suspense 边界中,这样资源只会触发最近的 suspense 边界。

例如,在下面的代码中,只有 title() 资源会触发顶层的 suspense 边界,而只有 data() 资源会触发嵌套的 suspense 边界:

const MyComponent = () => {
const [title] = createResource(async () => { /* 获取器代码在此 */ })
const [data] = createResource(async () => { /* 获取器代码在此 */ })
<Suspense>
<h1>{title()}</h1>
<Suspense>
<DataComponent>{data()}</DataComponent>
</Suspense>
</Suspense>
}

<Suspense> 的目的

为了理解 suspense 的目的,让我们考虑以下代码片段。这些片段会有一些缺点,我们将通过使用 suspense 来解决这些问题。我们还将看到如何可能使用 suspense 但却没有获得它的好处。

我们的示例用例是显示用户配置文件。一个简单的片段如下所示:

const MyComponentWithOptionalChaining = () => {
const [profile] = createResource(async () => {
/* 获取器代码在此 */
})
return (
<>
<div>{profile()?.name}</div>
<div>{profile()?.email}</div>
</>
)
}

在这段代码中,profile() 开始时为 undefined,当获取器代码完成时,解析为具有 nameemail 属性的对象。虽然资源尚未解析,但两个 div 已经创建并附加到文档主体,尽管是带有空文本节点。一旦资源解析,div 就会用适当的数据更新。

这种方法的缺点是用户会看到一个空组件 - 让我们看看在下一个片段中是否可以做得更好:

const MyComponentWithShow = () => {
const [profile] = createResource(async () => {
/* 获取器代码在此 */
})
return (
<Show when={profile()} fallback={<div>正在获取用户数据</div>}>
<div>{profile().name}</div>
<div>{profile().email}</div>
</Show>
)
}

在这段代码中,当资源尚未解析时,我们首先显示一个后备内容,然后在资源解析后切换到显示配置文件数据。这带来了更好的用户体验。

另一方面,这种方法也有轻微的缺点。在我们的第一个例子(使用可选链接)中,div 立即创建,一旦资源解析,只需要填充 div 的文本。但在我们的第二个例子(使用 <Show>)中,div 只有在资源解析后才创建,这意味着在资源解析后需要做更多的工作才能向用户显示数据(当然,在这个简单的例子中,DOM 工作量相对较小)。

我们可以通过使用 <Suspense> 来实现两全其美:

const MyComponentWithSuspense = () => {
const [profile] = createResource(async () => {
/* 获取器代码在此 */
})
return (
<Suspense fallback={<div>正在获取用户数据</div>}>
<div>{profile()?.name}</div>
<div>{profile()?.email}</div>
</Suspense>
)
}

在这种情况下,div 立即创建,但不是附加到文档主体,而是显示后备内容。一旦资源解析,div 中的文本会更新,然后它们会附加到文档(并移除后备内容)。

需要注意的是,使用 suspense 时_组件的执行不会暂停_。相反,当在 suspense 边界下读取资源时,它确保节点在资源解析之前不会附加到文档。Suspense 允许我们两全其美:在资源解析_之前_尽可能多地完成工作,同时在此之前显示后备内容。

考虑到这一点,我们可以理解在以下代码中使用 suspense 并没有太大收益:

const MyComponentWithSuspenseAndShow = () => {
const [profile] = createResource(async () => {
/* 获取器代码在此 */
})
return (
<Suspense fallback={<div>正在获取用户数据</div>}>
<Show when={profile()}>
<div>{profile().name}</div>
<div>{profile().email}</div>
</Show>
</Suspense>
)
}

在这段代码中,在资源解析之前,我们在 <Suspense> 内部不创建_任何_ DOM 节点,所以它与只使用 <Show> 的第二个例子几乎相同。


属性

名称类型描述
fallbackJSX.Element在子组件加载时要渲染的后备组件。
报告此页面问题