コンテキスト
通常、データは親コンポーネントから子コンポーネントにpropsを介して渡されます。しかし、propsを渡すことは、途中の多くのコンポーネントを通して渡さなければならない場合や、アプリ内の多くのコンポーネントが同じ情報を必要とする場合、冗長で煩わしくなる可能性があります。コンテキストは、親コンポーネントがpropsで渡す必要なく、ツリー内のどのコンポーネントにも、どれだけ深くてもデータを渡せるようにすることで、この問題を解決します。
propsの問題:「Prop Drilling」
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
}
}
/// App root
#[function_component]
fn App() -> Html {
let theme = Theme {
foreground: "yellow".to_owned(),
background: "pink".to_owned(),
};
html! {
<Navbar {theme} />
}
}
Navbar
を通してテーマのpropを「ドリル」し、Title
とNavButton
に到達できるようにします。テーマへのアクセスを必要とするコンポーネントであるTitle
とNavButton
が、propsとして渡さなくてもテーマにアクセスできれば素晴らしいでしょう。コンテキストは、この問題を解決するために、親が子にデータ(この場合はテーマ)を渡せるようにします。
コンテキストの使用
ステップ1:コンテキストの提供
コンテキストを消費するには、コンテキストプロバイダが必要です。ContextProvider<T>
(T
はプロバイダとして使用されるコンテキスト構造体です)。T
はClone
とPartialEq
を実装する必要があります。ContextProvider
は、その子がコンテキストを利用できるコンポーネントです。コンテキストが変更されると、子コンポーネントは再レンダリングされます。渡すデータを定義するために構造体が使用されます。ContextProvider
は次のように使用できます。
use yew::prelude::*;
/// App theme
#[derive(Clone, Debug, PartialEq)]
struct Theme {
foreground: String,
background: String,
}
/// Main component
#[function_component]
pub fn App() -> Html {
let ctx = use_state(|| Theme {
foreground: "#000000".to_owned(),
background: "#eeeeee".to_owned(),
});
html! {
// `ctx` is type `Rc<UseStateHandle<Theme>>` while we need `Theme`
// so we deref it.
// It derefs to `&Theme`, hence the clone
<ContextProvider<Theme> context={(*ctx).clone()}>
// Every child here and their children will have access to this context.
<Toolbar />
</ContextProvider<Theme>>
}
}
/// The toolbar.
/// This component has access to the context
#[function_component]
pub fn Toolbar() -> Html {
html! {
<div>
<ThemedButton />
</div>
}
}
/// Button placed in `Toolbar`.
/// As this component is a child of `ThemeContextProvider` in the component tree, it also has access
/// to the context.
#[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
をpropsとして受け取り、それをArticleList
コンポーネントに渡すとします。Layout
コンポーネントをリファクタリングして、子をpropsとして受け取り、<Layout> <ArticleList {articles} /> </Layout>
と表示する必要があります。
子のコンテキスト値の変更
Rustの所有権のルールにより、コンテキストは子によって呼び出すことができる&mut self
を受け取るメソッドを持つことができません。コンテキストの値を変更するには、それをリデューサーと組み合わせる必要があります。これは、use_reducer
フックを使用することで行われます。
コンテキストの例は、コンテキストの助けを借りて変更可能なコンテキストを示しています。