<Suspense/>
在上一章中,我们展示了如何创建一个简单的加载屏幕,以便在资源加载时显示一些回退内容。
let (count, set_count) = create_signal(0);
let once = create_resource(count, |count| async move { load_a(count).await });
view! {
<h1>"My Data"</h1>
{move || match once.get() {
None => view! { <p>"Loading..."</p> }.into_view(),
Some(data) => view! { <ShowData data/> }.into_view()
}}
}
但是,如果我们有两个 resources ,并且想要等待它们都加载完成怎么办?
let (count, set_count) = create_signal(0);
let (count2, set_count2) = create_signal(0);
let a = create_resource(count, |count| async move { load_a(count).await });
let b = create_resource(count2, |count| async move { load_b(count).await });
view! {
<h1>"My Data"</h1>
{move || match (a.get(), b.get()) {
(Some(a), Some(b)) => view! {
<ShowA a/>
<ShowA b/>
}.into_view(),
_ => view! { <p>"Loading..."</p> }.into_view()
}}
}
这并不_太_糟糕,但有点烦人。如果我们可以反转控制流呢?
<Suspense/>
组件可以让我们做到这一点。你给它一个 fallback
prop 和子级,其中一个或多个通常涉及从 resource 中读取数据。从 <Suspense/>
“下”(即它的一个子级中)读取 resource 会将该 resource 注册到 <Suspense/>
。如果它仍在等待资源加载,它会显示 fallback
。当它们都加载完成后,它会显示子级。
let (count, set_count) = create_signal(0);
let (count2, set_count2) = create_signal(0);
let a = create_resource(count, |count| async move { load_a(count).await });
let b = create_resource(count2, |count| async move { load_b(count).await });
view! {
<h1>"My Data"</h1>
<Suspense
fallback=move || view! { <p>"Loading..."</p> }
>
<h2>"My Data"</h2>
<h3>"A"</h3>
{move || {
a.get()
.map(|a| view! { <ShowA a/> })
}}
<h3>"B"</h3>
{move || {
b.get()
.map(|b| view! { <ShowB b/> })
}}
</Suspense>
}
每当其中一个 resource 重新加载时,"Loading..."
回退内容将再次显示。
这种控制流的反转使得添加或删除单个 resource 变得更容易,因为你不需要自己处理匹配。它还解锁了服务器端渲染期间的一些巨大性能改进,我们将在后面的章节中讨论这些内容。
<Await/>
如果你只是想在渲染之前等待某个 Future
解析完成,你可能会发现 <Await/>
组件有助于减少样板代码。<Await/>
本质上是将一个带有源参数 || ()
的 resource 与一个没有回退内容的 <Suspense/>
组合在一起。
换句话说:
- 它只轮询一次
Future
,并且不响应任何响应式更改。 - 在
Future
解析完成之前,它不会渲染任何内容。 - 在
Future
解析完成后,它将其数据绑定到你选择的任何变量名,然后使用该变量在作用域内渲染其子级。
async fn fetch_monkeys(monkey: i32) -> i32 {
// 也许这不需要是异步的
monkey * 2
}
view! {
<Await
// `future` 提供要解析的 `Future`
future=|| fetch_monkeys(3)
// 数据绑定到你提供的任何变量名
let:data
>
// 你通过引用接收数据,并可以在此处在你的视图中使用它
<p>{*data} " little monkeys, jumping on the bed."</p>
</Await>
}
实时示例
CodeSandbox 源码
use gloo_timers::future::TimeoutFuture;
use leptos::*;
async fn important_api_call(name: String) -> String {
TimeoutFuture::new(1_000).await;
name.to_ascii_uppercase()
}
#[component]
fn App() -> impl IntoView {
let (name, set_name) = create_signal("Bill".to_string());
// 每次 `name` 更改时,这都会重新加载
let async_data = create_resource(
name,
|name| async move { important_api_call(name).await },
);
view! {
<input
on:input=move |ev| {
set_name(event_target_value(&ev));
}
prop:value=name
/>
<p><code>"name:"</code> {name}</p>
<Suspense
// 每当在 suspense“下”读取的 resource
// 正在加载时,都会显示回退内容
fallback=move || view! { <p>"Loading..."</p> }
>
// 子级将在初始时渲染一次,
// 然后每当任何 resource 解析完成后都会渲染一次
<p>
"Your shouting name is "
{move || async_data.get()}
</p>
</Suspense>
}
}
fn main() {
leptos::mount_to_body(App)
}