本文へスキップ
バージョン: 0.21

サスペンス

サスペンスは、タスクの完了を待っている間にコンポーネントのレンダリングを一時停止し、その間にフォールバック(プレースホルダー)UIを表示する方法です。

これは、サーバーからデータを取得したり、エージェントによって完了するタスクを待機したり、その他のバックグラウンドの非同期タスクを実行したりするために使用できます。

サスペンス以前は、データの取得は通常、コンポーネントのレンダリング後(Fetch-on-render)または前(Fetch-then-render)に行われていました。

レンダリングしながらフェッチ

サスペンスは、レンダリングプロセス中にコンポーネントがデータ要求を開始することを可能にする新しいアプローチを可能にします。コンポーネントがデータ要求を開始すると、レンダリングプロセスは一時停止し、要求が完了するまでフォールバックUIが表示されます。

サスペンスを使用する推奨される方法は、フックを使用することです。

use yew::prelude::*;

#[function_component(Content)]
fn content() -> HtmlResult {
let user = use_user()?;

Ok(html! {<div>{"Hello, "}{&user.name}</div>})
}

#[function_component(App)]
fn app() -> Html {
let fallback = html! {<div>{"Loading..."}</div>};

html! {
<Suspense {fallback}>
<Content />
</Suspense>
}
}

上記の例では、use_userフックは、ユーザー情報の読み込み中にコンポーネントのレンダリングを一時停止し、userが読み込まれるまでLoading...プレースホルダーが表示されます。

コンポーネントのレンダリングを一時停止するフックを定義するには、SuspensionResult<T>を返す必要があります。コンポーネントの一時停止が必要な場合、フックはErr(Suspension)を返し、ユーザーはそれを?でアンラップする必要があります。これにより、Htmlに変換されます。

use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

struct User {
name: String,
}

#[hook]
fn use_user() -> SuspensionResult<User> {
match load_user() {
// If a user is loaded, then we return it as Ok(user).
Some(m) => Ok(m),
None => {
// When user is still loading, then we create a `Suspension`
// and call `SuspensionHandle::resume` when data loading
// completes, the component will be re-rendered
// automatically.
let (s, handle) = Suspension::new();
on_load_user_complete(move || {handle.resume();});
Err(s)
},
}
}

サスペンスフックの実装に関する注意点

Suspension::new は2つの値を返します。サスペンスコンテキスト自体と、サスペンスハンドルです。後者は、一時停止されたコンポーネントを再レンダリングするタイミングを知らせる役割を果たし、そのためには2つの相互交換可能な方法を提供します。

  1. そのresumeメソッドを呼び出す。
  2. ハンドルを破棄する。
危険

サスペンスハンドルは、コンポーネントを更新する時間、つまり新しく受信したデータがあるまで保存する必要があります。そうしないと、一時停止されたコンポーネントは無限の再レンダリングループに入り、パフォーマンスを阻害します。上記の例では、サスペンスハンドルはクロージャに移動され、on_load_user_completeに渡されることで保存されます。仮説的なユーザーが読み込まれると、クロージャが呼び出され、handle.resume()が呼び出され、サスペンスコンテキストに関連付けられたコンポーネントが再レンダリングされます。

完全な例

use yew::prelude::*;
use yew::suspense::{Suspension, SuspensionResult};

#[derive(Debug)]
struct User {
name: String,
}

fn load_user() -> Option<User> {
todo!() // implementation omitted.
}

fn on_load_user_complete<F: FnOnce()>(_fn: F) {
todo!() // implementation omitted.
}

#[hook]
fn use_user() -> SuspensionResult<User> {
match load_user() {
// If a user is loaded, then we return it as Ok(user).
Some(m) => Ok(m),
None => {
// When user is still loading, then we create a `Suspension`
// and call `SuspensionHandle::resume` when data loading
// completes, the component will be re-rendered
// automatically.
let (s, handle) = Suspension::new();
on_load_user_complete(move || {handle.resume();});
Err(s)
},
}
}

#[function_component(Content)]
fn content() -> HtmlResult {
let user = use_user()?;

Ok(html! {<div>{"Hello, "}{&user.name}</div>})
}

#[function_component(App)]
fn app() -> Html {
let fallback = html! {<div>{"Loading..."}</div>};

html! {
<Suspense {fallback}>
<Content />
</Suspense>
}
}

構造体コンポーネントでのサスペンスの使用

構造体コンポーネントを直接一時停止することはできません。ただし、関数コンポーネントを高階コンポーネントとして使用して、サスペンスベースのデータフェッチを実現できます。

Yewリポジトリのサスペンスの例は、使用方法を示しています。

関連例