メインコンテンツまでスキップ
Version: Next

サスペンス (Suspense)

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

これは、サーバーからデータを取得したり、プロキシがタスクを完了するのを待ったり、他のバックグラウンド非同期タスクを実行したりするために使用できます。

サスペンスが表示される前に、データ取得は通常、コンポーネントのレンダリング後(レンダリング時取得)またはレンダリング前(取得後レンダリング)に発生します。

レンダリングしながらダウンロード

サスペンス (Suspense) は、新しい方法を提供し、コンポーネントがレンダリング中にデータリクエストを発行できるようにします。コンポーネントがデータリクエストを発行すると、レンダリングプロセスが一時停止され、リクエストが完了するまでフォールバック UI が表示されます。

サスペンスを使用するには、フック (Hook) を使用することをお勧めします。

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() {
// ユーザーが読み込まれたら、それを Ok(user) として返します。
Some(user) => Ok(user),
None => {
// ユーザーがまだ読み込まれていない場合、`Suspension` を作成し、
// データの読み込みが完了したときに `SuspensionHandle::resume` を呼び出します。
// これにより、コンポーネントは自動的に再レンダリングされます。
let (s, handle) = Suspension::new();
on_load_user_complete(move || {handle.resume();});
Err(s)
},
}
}

サスペンスフック (Hook) の実装に関する注意事項

Suspension::new は 2 つの値を返します:サスペンスコンテキスト自体とサスペンスハンドル。後者はサスペンスされたコンポーネントを再レンダリングするタイミングを管理し、2 つの方法で操作できます:

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

サスペンスハンドルは、新しいデータを受け取ってコンポーネントを更新するまで保存する必要があります。そうしないと、サスペンスされたコンポーネントが無限再レンダリングループに入り、パフォーマンスに影響を与えます。 上記の例では、サスペンスハンドルはクロージャに移動し、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!() // 省略
}

fn on_load_user_complete<F: FnOnce()>(_fn: F) {
todo!() // 省略
}

#[hook]
fn use_user() -> SuspensionResult<User> {
match load_user() {
// ユーザーが読み込まれたら、それを Ok(user) として返します。
Some(user) => Ok(user),
None => {
// ユーザーがまだ読み込まれていない場合、`Suspension` を作成し、
// データの読み込みが完了したときに `SuspensionHandle::resume` を呼び出します。
// これにより、コンポーネントは自動的に再レンダリングされます。
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 リポジトリのプレースホルダーの例は、このコンポーネントの使用方法を示しています。

関連例