<Suspense/>
<Suspense/>
在上一章中,我们展示了如何创建一个简单的加载界面,在资源加载时显示一些回退内容(fallback)。
In the previous chapter, we showed how you can create a simple loading screen to show some fallback while a resource is loading.
let (count, set_count) = signal(0);
let once = Resource::new(move || count.get(), |count| async move { load_a(count).await });
view! {
<h1>"My Data"</h1>
{move || match once.get() {
// 加载中...
// Loading...
None => view! { <p>"Loading..."</p> }.into_any(),
Some(data) => view! { <ShowData data/> }.into_any()
}}
}
但是,如果我们有两个资源,并且想等待它们两个都加载完呢?
But what if we have two resources, and want to wait for both of them?
let (count, set_count) = signal(0);
let (count2, set_count2) = signal(0);
let a = Resource::new(move || count.get(), |count| async move { load_a(count).await });
let b = Resource::new(move || count2.get(), |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_any(),
// 加载中...
// Loading...
_ => view! { <p>"Loading..."</p> }.into_any()
}}
}
这看起来还算可以,但确实有点烦人。如果我们能反转控制流(invert the flow of control)呢?
That’s not so bad, but it’s kind of annoying. What if we could invert the flow of control?
<Suspense/> 组件正是为了实现这一点。你给它一个 fallback 属性和子组件,其中一个或多个子组件通常涉及从资源中读取数据。在 <Suspense/> “下方”(即在其子组件之一中)读取资源会将该资源注册到 <Suspense/>。如果它仍在等待资源加载,它就会显示 fallback。当资源全部加载完成后,它就会显示子组件。
The <Suspense/> component lets us do exactly that. You give it a fallback prop and children, one or more of which usually involves reading from a resource. Reading from a resource “under” a <Suspense/> (i.e., in one of its children) registers that resource with the <Suspense/>. If it’s still waiting for resources to load, it shows the fallback. When they’ve all loaded, it shows the children.
let (count, set_count) = signal(0);
let (count2, set_count2) = signal(0);
let a = Resource::new(count, |count| async move { load_a(count).await });
let b = Resource::new(count2, |count| async move { load_b(count).await });
view! {
<h1>"My Data"</h1>
<Suspense
// 加载中...
// Loading...
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>
}
每当其中一个资源重新加载时,"Loading..." 回退内容就会再次显示。
Every time one of the resources is reloading, the "Loading..." fallback will show again.
这种控制流的反转使得添加或删除单个资源变得更加容易,因为你不需要自己处理匹配逻辑。它还在服务端渲染期间开启了一些巨大的性能提升,我们将在后面的章节中讨论这一点。
This inversion of the flow of control makes it easier to add or remove individual resources, as you don’t need to handle the matching yourself. It also unlocks some massive performance improvements during server-side rendering, which we’ll talk about during a later chapter.
使用 <Suspense/> 还允许我们使用一种非常有用的方式直接 .await 资源,从而消除上述代码中的一层嵌套。Suspend 类型允许我们创建一个可在视图中使用的可渲染 Future:
Using <Suspense/> also gives us access to a useful way to directly .await resources, allowing us to remove a level of nesting, above. The Suspend type lets us create a renderable Future which can be used in the view:
view! {
<h1>"My Data"</h1>
<Suspense
// 加载中...
// Loading...
fallback=move || view! { <p>"Loading..."</p> }
>
<h2>"My Data"</h2>
{move || Suspend::new(async move {
let a = a.await;
let b = b.await;
view! {
<h3>"A"</h3>
<ShowA a/>
<h3>"B"</h3>
<ShowB b/>
}
})}
</Suspense>
}
Suspend 让我们能够避免对每个资源进行空值检查,并从代码中消除了一些额外的复杂性。
Suspend allows us to avoid null-checking each resource, and removes some additional complexity from the code.
<Await/>
<Await/>
如果你只是想在渲染之前等待某个 Future 解析,你可能会发现 <Await/> 组件在减少样板代码方面很有帮助。<Await/> 实际上是将 OnceResource 与没有回退内容的 <Suspense/> 结合在了一起。
If you’re simply trying to wait for some Future to resolve before rendering, you may find the <Await/> component helpful in reducing boilerplate. <Await/> essentially combines a OnceResource with a <Suspense/> with no fallback.
换句话说:
In other words:
- 它只轮询(poll)
Future一次,并且不响应任何响应式变化。 - It only polls the
Futureonce, and does not respond to any reactive changes. - 在
Future解析之前,它不会渲染任何内容。 - It does not render anything until the
Futureresolves. - 在
Future解析后,它将数据绑定到你选择的任何变量名上,然后在该变量的作用域内渲染其子组件。 - After the
Futureresolves, it binds its data to whatever variable name you choose and then renders its children with that variable in scope.
async fn fetch_monkeys(monkey: i32) -> i32 {
// 也许这并不需要是异步的
// maybe this didn't need to be async
monkey * 2
}
view! {
<Await
// `future` 提供要解析的 `Future`
// `future` provides the `Future` to be resolved
future=fetch_monkeys(3)
// 数据被绑定到你提供的任何变量名
// the data is bound to whatever variable name you provide
let:data
>
// 你通过引用接收数据,并可以在此处的视图中使用它
// you receive the data by reference and can use it in your view here
// 只小猴子在床上跳。
// little monkeys, jumping on the bed.
<p>{*data} " little monkeys, jumping on the bed."</p>
</Await>
}
CodeSandbox 源码 (CodeSandbox Source)
use gloo_timers::future::TimeoutFuture;
use leptos::prelude::*;
async fn important_api_call(name: String) -> String {
TimeoutFuture::new(1_000).await;
name.to_ascii_uppercase()
}
#[component]
pub fn App() -> impl IntoView {
let (name, set_name) = signal("Bill".to_string());
// 每当 `name` 变化时,这里都会重新加载
// this will reload every time `name` changes
let async_data = LocalResource::new(move || important_api_call(name.get()));
view! {
<input
on:change:target=move |ev| {
set_name.set(ev.target().value());
}
prop:value=name
/>
<p><code>"name:"</code> {name}</p>
<Suspense
// 每当在 suspense “下方”读取的资源正在加载时,
// 就会显示回退内容。
// the fallback will show whenever a resource
// read "under" the suspense is loading
fallback=move || view! { <p>"Loading..."</p> }
>
// Suspend 允许你在视图中使用异步块
// Suspend allows you use to an async block in the view
<p>
"Your shouting name is "
{move || Suspend::new(async move {
async_data.await
})}
</p>
</Suspense>
<Suspense
// 每当在 suspense “下方”读取的资源正在加载时,
// 就会显示回退内容。
// the fallback will show whenever a resource
// read "under" the suspense is loading
fallback=move || view! { <p>"Loading..."</p> }
>
// 子组件最初会渲染一次,
// 然后在任何资源解析后再次渲染
// the children will be rendered once initially,
// and then whenever any resources has been resolved
<p>
"Which should be the same as... "
{move || async_data.get().as_deref().map(ToString::to_string)}
</p>
</Suspense>
}
}
fn main() {
leptos::mount::mount_to_body(App)
}