跳至主要内容
版本:0.21

上下文

通常,数据通过道具从父组件传递到子组件。但是,如果你必须通过中间的许多组件传递道具,或者应用程序中的许多组件需要相同的信息,那么传递道具可能会变得冗长且烦人。上下文通过允许父组件向其下方的任何组件(无论多深)提供数据来解决此问题,而无需通过道具将其传递下去。

道具的问题:“道具钻探”

传递道具是直接从父级传递数据到子级的好方法。当通过深度嵌套的组件树向下传递或多个组件共享相同数据时,它们会变得繁琐。数据共享的一个常见解决方案是将数据提升到公共祖先并让子级将其作为道具。但是,这可能导致道具必须经过多个组件才能到达需要它的组件的情况。这种情况称为“道具钻探”。

考虑以下使用道具传递主题的示例

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“钻探”主题道具,以便它可以到达TitleNavButton。如果需要访问主题的组件TitleNavButton可以访问主题而无需将主题作为道具传递给它们,那就太好了。上下文通过允许父级将数据(本例中为主题)传递给其子级来解决此问题。

使用上下文

步骤 1:提供上下文

需要上下文提供程序来使用上下文。ContextProvider<T>,其中T用作提供程序的上下文结构。T必须实现ClonePartialEqContextProvider是其子级可以使用上下文的组件。当上下文更改时,将重新呈现子级。使用结构来定义要传递的数据。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 个选项

用例

通常,如果树的不同部分中的远端组件需要一些数据,上下文可能会对你有帮助。以下是一些此类情况的示例

  • 主题:你可以在应用程序顶部放置一个包含应用程序主题的上下文,并使用它来调整视觉外观,如上例所示。
  • 当前用户帐户:在许多情况下,组件需要知道当前登录的用户。你可以使用上下文向组件提供当前用户对象。

在使用上下文之前需要考虑的事项

上下文非常容易使用。这使得它们很容易被滥用/过度使用。仅仅因为你可以使用上下文将道具分享给多层组件,并不意味着你应该这样做。

例如,你可能能够提取一个组件并将该组件作为子组件传递给另一个组件。例如,你可能有一个 Layout 组件,它将 articles 作为道具并将其传递给 ArticleList 组件。你应该重构 Layout 组件以将子组件作为道具并显示 <Layout> <ArticleList {articles} /> </Layout>

更改子级的上下文值

由于 Rust 的所有权规则,上下文不能有可由子级调用的采用 &mut self 的方法。要更改上下文的 value,我们必须将其与 reducer 结合使用。这可以通过使用 use_reducer hook 来完成。

contexts 示例 在上下文的帮助下演示了可变上下文

延伸阅读