コンテキスト (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
に渡して、それが Title
と NavButton
に到達するようにしています。
もし Title
と NavButton
のようなテーマにアクセスする必要があるコンポーネントが、prop を介さずに直接テーマにアクセスできるとしたら、もっと良いでしょう。
コンテキストはこの問題を解決し、親コンポーネントがデータ(この場合はテーマ)をその子コンポーネントに渡すことを可能にします。
コンテキストの使用
ステップ 1:コンテキストの提供
コンテキストを消費するには、コンテキストプロバイダーが必要です。ContextProvider<T>
は、T
がコンテキスト構造体として使用されるプロバイダーです。
T
は Clone
と PartialEq
を実装する必要があります。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
フックを使用して行うことができます。
コンテキストの例 は、可変コンテキストの使用を示しています。