Подгрузка данных с помощью ресурсов
Ресурс (Resource) это реактивная структура данных, отражающая текущее состояние асинхронной задачи, и
позволяющая интегрировать асинхронные футуры (Future
) в синхронную реактивную систему. Вместо ожидания данных через .await
,
мы превращаем футуру в сигнал, возвращающий Some(T)
если она уже разрешилась и None
если она ещё в ожидании.
Это делается с помощью функции create_resource
. Она принимает два аргумента:
- сигнал-источник, порождающий новую футуру при любом изменении
- функция, принимающая как аргумент данные из сигнала и возвращающая футуру
Вот пример:
// our source signal: some synchronous, local state
let (count, set_count) = create_signal(0);
// our resource
let async_data = create_resource(
count,
// every time `count` changes, this will run
|value| async move {
logging::log!("loading data from API");
load_data(value).await
},
);
Для создания ресурса, который выполняется единожды, можно передать нереактивный, пустой сигнал-источник:
let once = create_resource(|| (), |_| async move { load_data().await });
Для доступа к значению можно использовать .get()
или .with(|data| /* */)
. Они работают так же как .get()
и .with()
у сигнала: get
возвращает клонированное значение, а with
применяет замыкание. Однако для всякого Resource<_, T>
,
они возвращают Option<T>
, а не T
, поскольку всегда возможно, что ресурс всё ещё грузится.
Так можно отобразить текущее состояние ресурса во view
:
let once = create_resource(|| (), |_| async move { load_data().await });
view! {
<h1>"My Data"</h1>
{move || match once.get() {
None => view! { <p>"Loading..."</p> }.into_view(),
Some(data) => view! { <ShowData data/> }.into_view()
}}
}
Ресурсы также предоставляют метод refetch()
, позволяющий вручную перезапросить данные (например, в ответ на нажатие кнопки)
и метод loading()
, возвращающий ReadSignal<bool>
— индикатор того загружается ли в данный момент сигнал.
[Нажмите, чтобы открыть CodeSandbox.](https://codesandbox.io/p/sandbox/10-resources-0-5-x6h5j6?file=%2Fsrc%2Fmain.rs%3A2%2C3)
<noscript>
Пожалуйста, включите Javascript для просмотра примеров.
</noscript>
<template>
<iframe src="https://codesandbox.io/p/sandbox/10-resources-0-5-9jq86q?file=%2Fsrc%2Fmain.rs%3A2%2C3" width="100%" height="1000px" style="max-height: 100vh"></iframe>
</template>
Код примера CodeSandbox
use gloo_timers::future::TimeoutFuture;
use leptos::*;
// Here we define an async function
// This could be anything: a network request, database read, etc.
// Here, we just multiply a number by 10
async fn load_data(value: i32) -> i32 {
// fake a one-second delay
TimeoutFuture::new(1_000).await;
value * 10
}
#[component]
fn App() -> impl IntoView {
// this count is our synchronous, local state
let (count, set_count) = create_signal(0);
// create_resource takes two arguments after its scope
let async_data = create_resource(
// the first is the "source signal"
count,
// the second is the loader
// it takes the source signal's value as its argument
// and does some async work
|value| async move { load_data(value).await },
);
// whenever the source signal changes, the loader reloads
// you can also create resources that only load once
// just return the unit type () from the source signal
// that doesn't depend on anything: we just load it once
let stable = create_resource(|| (), |_| async move { load_data(1).await });
// we can access the resource values with .get()
// this will reactively return None before the Future has resolved
// and update to Some(T) when it has resolved
let async_result = move || {
async_data
.get()
.map(|value| format!("Server returned {value:?}"))
// This loading state will only show before the first load
.unwrap_or_else(|| "Loading...".into())
};
// the resource's loading() method gives us a
// signal to indicate whether it's currently loading
let loading = async_data.loading();
let is_loading = move || if loading() { "Loading..." } else { "Idle." };
view! {
<button
on:click=move |_| {
set_count.update(|n| *n += 1);
}
>
"Click me"
</button>
<p>
<code>"stable"</code>": " {move || stable.get()}
</p>
<p>
<code>"count"</code>": " {count}
</p>
<p>
<code>"async_value"</code>": "
{async_result}
<br/>
{is_loading}
</p>
}
}
fn main() {
leptos::mount_to_body(App)
}