跳至主要內容
版本: 0.21

脈絡

通常,資料會透過 props 從父組件傳遞至子組件。但是,如果你必須透過中間的許多組件傳遞資料,或應用程式中的眾多組件需要相同資訊,那麼這時候傳遞 props 會變得冗長而惱人。Context 可解決此問題,它允許父組件將資料提供給其下方的任何組件使用,無需透過 props 將其傳遞下去,無論深度如何。

props 的問題:「Prop Drilling」

傳遞props是將資料從父項直接傳遞給子項的絕佳方法。在深度嵌套元件樹中傳遞或多個元件共享同一個資料時,它們變得相當繁瑣。資料共享的常見解決方案是將資料提升到共同的祖先,並讓子項將資料視為props。然而,這可能會導致props必須經過多個元件,才能傳遞到需要它的元件。這種情況稱為「props 遞迴」。

請考慮以下使用 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「遞迴」主題props,這樣它才能傳遞到TitleNavButton。如果TitleNavButton,這些需要存取主題的元件,能夠在不以 props 形式傳遞給它們的狀況下存取主題,那會很不錯。contexts 能夠解決這個問題,方法是允許父項傳遞資料,在本例中是主題,給它的子項。

使用 Contexts

步驟 1:提供 context

需要一個 context 提供器才能使用 context。ContextProvider<T>,其中T是用作提供器的 context 結構。T必須實作ClonePartialEqContextProvider是其子項可以使用 context 的元件。當 context 變更時,這些子項會重新渲染。結構用於定義要傳遞的資料。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:使用 context

函式元件

use_context hook 用於在函式元件中使用 contexts。請參閱use_context 的文件以深入瞭解。

結構元件

我們有 2 個選項可以在結構元件中使用 contexts

使用案例

一般而言,如果資料遠在樹的不同部分的元件需要,那麼情境可能會對你有幫助。下方列出一些此類案例

  • 佈景主題:你可以將情境置於應用程式的上方,其中包含應用程式的佈景主題,並使用它來調整視覺外觀,如同上方範例所示。
  • 目前使用者帳戶:在許多案例中,元件需要知道目前登入的使用者。你可以使用情境來把目前的使用者物件提供給元件。

使用情境之前需要考慮的事項

情境非常容易使用。這讓它們很容易被誤用/過度使用。但僅僅因為你可以使用情境與深入多層級的元件共享 prop,並不表示你應該這麼做。

例如,你可能可以萃取出一個元件,並將該元件作為另一個元件的子項傳遞。例如,你可能有一個 Layout 元件,將 articles 作為一個 prop 來取得,然後傳遞給 ArticleList 元件。你應該重新編排 Layout 元件,以將子項作為 prop 取得,並顯示 <Layout> <ArticleList {articles} /> </Layout>

突變子項的情境值

由於 Rust 的所有權規則,情境無法有取得 &mut self 的方法,而子項可以呼叫該方法。若要突變情境的值,我們必須將它與一個還原器結合。這需要使用 use_reducer 勾子。

contexts 範例 示範了可變情境,且透過情境獲得協助

進一步閱讀