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

コンテキスト (Contexts)

通常、データは props を介して親コンポーネントから子コンポーネントに渡されます。 しかし、多くの中間コンポーネントを介してデータを渡す必要がある場合や、アプリケーション内の多くのコンポーネントが同じ情報を必要とする場合、props を介してデータを渡すことは冗長で煩わしいものになります。 コンテキストはこの問題を解決し、親コンポーネントがデータをその下のツリー内の任意のコンポーネントに渡すことを可能にし、props を介してデータを渡す必要がなくなります。

Props を使用する際の問題:"Prop Drilling"

props を介してデータを親コンポーネントから直接子コンポーネントに渡すことは良い方法です。 しかし、深くネストされたコンポーネントツリーを介してデータを渡す必要がある場合や、複数のコンポーネントが同じデータを共有する必要がある場合、props を渡すことは煩雑になります。 一般的なデータ共有の解決策は、データを共通の祖先に持ち上げ、子コンポーネントがそれを props として受け取るようにすることです。 しかし、これにより props が複数のコンポーネントを介して渡される必要がある場合があります。 この状況は "Prop Drilling" と呼ばれます。

以下の例を考えてみましょう。これは props を介してテーマを渡しています:

use yew::{html, Component, Context, Html, Properties, function_component};

#[derive(Clone, PartialEq)]
pub struct Theme {
foreground: String,
background: String,
}

#[derive(PartialEq, Properties)]
pub struct NavbarProps {
theme: Theme,
}

#[function_component]
fn Navbar(props: &NavbarProps) -> Html {
html! {
<div>
<Title theme={props.theme.clone()}>
{ "App title" }
</Title>
<NavButton theme={props.theme.clone()}>
{ "Somewhere" }
</NavButton>
</div>
}
}

#[derive(PartialEq, Properties)]
pub struct ThemeProps {
theme: Theme,
children: Html,
}

#[function_component]
fn Title(_props: &ThemeProps) -> Html {
html! {
// impl
}
}

#[function_component]
fn NavButton(_props: &ThemeProps) -> Html {
html! {
// impl
}
}

/// アプリのルート
#[function_component]
fn App() -> Html {
let theme = Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
};

html! {
<Navbar {theme} />
}
}

私たちはテーマ設定を Navbar に渡して、それが TitleNavButton に到達するようにしています。 もし TitleNavButton のようなテーマにアクセスする必要があるコンポーネントが、prop を介さずに直接テーマにアクセスできるとしたら、もっと良いでしょう。 コンテキストはこの問題を解決し、親コンポーネントがデータ(この場合はテーマ)をその子コンポーネントに渡すことを可能にします。

コンテキストの使用

ステップ 1:コンテキストの提供

コンテキストを消費するには、コンテキストプロバイダーが必要です。ContextProvider<T> は、T がコンテキスト構造体として使用されるプロバイダーです。 TClonePartialEq を実装する必要があります。ContextProvider は、その子コンポーネントがコンテキストを持つコンポーネントです。 コンテキストが変更されると、子コンポーネントは再レンダリングされます。データを渡すための構造体が定義されます。ContextProvider は次のように使用できます:

use yew::prelude::*;
/// アプリのテーマ
#[derive(Clone, Debug, PartialEq)]
struct Theme {
foreground: String,
background: String,
}

/// メインコンポーネント
#[function_component]
pub fn App() -> Html {
let ctx = use_state(|| Theme {
foreground: "#000000".to_owned(),
background: "#eeeeee".to_owned(),
});

html! {
// `ctx` は `Rc<UseStateHandle<Theme>>` 型であり、`Theme` が必要です
// したがって、デリファレンスします。
<ContextProvider<Theme> context={(*ctx).clone()}>
// ここにあるすべての子コンポーネントとその子コンポーネントは、このコンテキストにアクセスします。
<Toolbar />
</ContextProvider<Theme>>
}
}

/// ツールバー
/// このコンポーネントはコンテキストにアクセスできます。
#[function_component]
pub fn Toolbar() -> Html {
html! {
<div>
<ThemedButton />
</div>
}
}

/// `Toolbar` 内に配置されたボタン
/// このコンポーネントは、コンポーネントツリー内の `ThemeContextProvider` の子コンポーネントであるため、
/// コンテキストにアクセスできます。
#[function_component]
pub fn ThemedButton() -> Html {
let theme = use_context::<Theme>().expect("no ctx found");

html! {
<button style={format!("background: {}; color: {};", theme.background, theme.foreground)}>
{ "Click me!" }
</button>
}
}

ステップ 2:コンテキストの使用

関数コンポーネント

use_context フックは、関数コンポーネント内でコンテキストを使用するために使用されます。 詳細については、use_context ドキュメント を参照してください。

構造体コンポーネント

構造体コンポーネント内でコンテキストを使用するには、2つの方法があります:

使用シナリオ

通常、ツリーの異なる部分のリモートコンポーネントでデータを使用する必要がある場合、コンテキストが役立ちます。 以下はいくつかの例です:

  • テーマ:アプリケーションのトップにコンテキストを配置し、アプリケーションのテーマを保持し、視覚的な外観を調整するために使用できます(上記の例を参照)。
  • 現在のユーザーアカウント:多くの場合、コンポーネントは現在ログインしているユーザーを知る必要があります。コンテキストを使用して、現在のユーザーオブジェクトをコンポーネントに提供できます。

コンテキストを使用する前の考慮事項

コンテキストは非常に使いやすいですが、それが誤用/過度に使用される可能性もあります。 複数のレベル深いコンポーネントに props を共有するためにコンテキストを使用できるからといって、必ずしもそうすべきではありません。

例えば、コンポーネントを抽出して、そのコンポーネントを別のコンポーネントの子コンポーネントとして渡すことができます。 例えば、Layout コンポーネントが articles を prop として受け取り、それを ArticleList コンポーネントに渡す場合、 Layout コンポーネントをリファクタリングして、子コンポーネントを props として受け取り、<Layout> <ArticleList {articles} /> </Layout> と表示するようにするべきです。

子コンポーネントのコンテキスト値を変更する

Rust の所有権ルールにより、コンテキストには子コンポーネントが呼び出せる &mut self メソッドを持つことができません。 コンテキストの値を変更するには、リデューサーと組み合わせて使用する必要があります。これは、use_reducer フックを使用して行うことができます。

コンテキストの例 は、可変コンテキストの使用を示しています。

さらなる読み物