<Suspense/>
В предыдущей главе было рассмотрено создание простого экрана загрузки, чтобы показывать некий fallback
пока ресурс грузится.
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()
}}
}
Но что если есть два ресурса и хочется ожидать оба?
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()
}}
}
Выглядит не очень плохо, но всё же несколько раздражает. Что если использовать инверсию управления (IoC)?
Компонент <Suspense/>
позволяет сделать именно это. Вы передаёте ему свойство fallback
и дочерние элементы,
один или более из которых обычно включает в себя чтение из ресурса. Чтение из ресурса “внутри” a <Suspense/>
(т.е. в одном из элементов-потомков) регистрирует ресурс в <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>
}
Каждый раз когда любой из ресурсов перезагружается, fallback
с "Loading..." отображается снова.
Инверсия управления упрощает добавление и удаление отдельных ресурсов, избавляя от необходимости самостоятельно делать сопоставление с шаблоном. Это также открывает значительные оптимизации производительности во время серверного рендеринга (SSR), о которых мы поговорим в одной из следующих глав.
<Await/>
Если хочется просто дождаться какого-то футуры перед рендерингом, компонент <Await/>
может быть полезен в уменьшении шаблонного кода.
<Await/>
по сути сочетает в себе ресурс с источником || ()
и <Suspense/>
без fallback
.
Другими словами:
- Он poll'ит
Future
лишь раз и не реагирует ни на какие реактивные изменения. - Он ничего не рендерит пока
Future
не разрешится. - После разрешения футуры, он кладёт её данные в выбранную переменную и рендерит дочерние элементы с этой переменной в области видимости.
async fn fetch_monkeys(monkey: i32) -> i32 {
// maybe this didn't need to be async
monkey * 2
}
view! {
<Await
// `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
<p>{*data} " little monkeys, jumping on the bed."</p>
</Await>
}
[Нажмите, чтобы открыть CodeSandbox.](https://codesandbox.io/p/sandbox/11-suspense-0-5-qzpgqs?file=%2Fsrc%2Fmain.rs%3A1%2C1)
<noscript>
Пожалуйста, включите Javascript для просмотра примеров.
</noscript>
<template>
<iframe src="https://codesandbox.io/p/sandbox/11-suspense-0-5-qzpgqs?file=%2Fsrc%2Fmain.rs%3A1%2C1" width="100%" height="1000px" style="max-height: 100vh"></iframe>
</template>
Код примера 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());
// this will reload every time `name` changes
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
// 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>
"Your shouting name is "
{move || async_data.get()}
</p>
</Suspense>
}
}
fn main() {
leptos::mount_to_body(App)
}