---
title: '属性 (Properties)'
description: '父子组件通信'
---

import Tabs from '@theme/Tabs'
import TabItem from '@theme/TabItem'

:::note

属性 (Properties) 通常被简称为 "Props"。

:::

属性 (Properties) 本质上是 Yew 可以监视的组件参数。

一个类型必须先实现 `Properties` 特征才能被用作组件的属性。

## 响应性

Yew 在重新渲染时会在协调虚拟 DOM 时检查 props 是否已更改，以了解是否需要重新渲染嵌套组件。这样，Yew 可以被认为是一个非常响应式的框架，因为来自父组件的更改总是会向下传播，视图永远不会与来自 props/状态的数据不同步。

:::tip

如果您还没有完成 [教程](../../tutorial)，请尝试一下并自己测试这种响应性！

:::

## 派生宏

Yew 提供了一个派生宏来轻松地在结构体上实现 `Properties` 特征。

派生 `Properties` 的类型还必须实现 `PartialEq`，以便 Yew 可以进行数据比较。

```rust
use yew::Properties;

#[derive(Properties, PartialEq)]
pub struct Props {
    pub is_loading: bool,
}
```

## 在函数组件中使用

属性 `#[function_component]` 允许可选地在函数参数中接收 Props。要提供它们，它们通过 `html!` 宏中的属性分配。

<Tabs>
  <TabItem value="with-props" label="With Props">

```rust
use yew::{function_component, html, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct Props {
    pub is_loading: bool,
}

#[function_component]
fn HelloWorld(props: &Props) -> Html {
    html! { <>{"Am I loading? - "}{props.is_loading.clone()}</> }
}

// 然后提供属性
#[function_component]
fn App() -> Html {
    html! {<HelloWorld is_loading={true} />}
}

```

  </TabItem>
  <TabItem value="no-props" label="No Props">

```rust
use yew::{function_component, html, Html};





#[function_component]
fn HelloWorld() -> Html {
    html! { "Hello world" }
}

// 没有要提供的属性
#[function_component]
fn App() -> Html {
    html! {<HelloWorld />}
}

```

  </TabItem>
</Tabs>

## 派生宏字段属性

派生 `Properties` 时，默认情况下所有字段都是必需的。
以下属性允许您为属性提供默认值，当父组件没有设置它们时将使用这些默认值。

:::tip
属性在 Rustdoc 生成的文档中不可见。您的属性的文档字符串应该提及属性是否是可选的以及是否有特殊的默认值。
:::

<Tabs>
  <TabItem value="prop_or_default" label="#[prop_or_default]">

使用 `Default` 特征用字段类型的默认值初始化属性值。

```rust
use yew::{function_component, html, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct Props {
    // highlight-start
    #[prop_or_default]
    // highlight-end
    pub is_loading: bool,
}

#[function_component]
fn HelloWorld(props: &Props) -> Html {
    if props.is_loading.clone() {
        html! { "Loading" }
    } else {
        html! { "Hello world" }
    }
}

// 然后像这样使用默认值
#[function_component]
fn Case1() -> Html {
    html! {<HelloWorld />}
}
// 或者不覆盖默认值
#[function_component]
fn Case2() -> Html {
    html! {<HelloWorld is_loading={true} />}
}
```

  </TabItem>
  <TabItem value="prop_or_value" label="#[prop_or(value)]">

使用 `value` 来初始化属性值。`value` 可以是任何返回字段类型的表达式。
例如，要将布尔属性默认为 `true`，请使用属性 `#[prop_or(true)]`。表达式在构造属性时被评估，并且没有给出明确值时应用。

```rust
use yew::{function_component, html, Html, Properties};

#[derive(Properties, PartialEq)]
pub struct Props {
    // highlight-start
    #[prop_or("Bob".to_string())]
    // highlight-end
    pub name: String,
}

#[function_component]
fn HelloWorld(props: &Props) -> Html {
    html! {<>{"Hello world"}{props.name.clone()}</>}
}

// 然后像这样使用默认值
#[function_component]
fn Case1() -> Html {
    html! {<HelloWorld />}
}
// 或者不覆盖默认值
#[function_component]
fn Case2() -> Html {
    html! {<HelloWorld name={"Sam".to_string()} />}
}
```

  </TabItem>
  <TabItem value="prop_or_else_function" label="#[prop_or_else(function)]">

调用 `function` 来初始化属性值。`function` 应该有签名 `FnMut() -> T`，其中 `T` 是字段类型。当没有为该属性给出明确值时，该函数被调用。

```rust
use yew::{function_component, html, Html, Properties};

fn create_default_name() -> String {
    "Bob".to_string()
}

#[derive(Properties, PartialEq)]
pub struct Props {
    // highlight-start
    #[prop_or_else(create_default_name)]
    // highlight-end
    pub name: String,
}

#[function_component]
fn HelloWorld(props: &Props) -> Html {
    html! {<>{"Hello world"}{props.name.clone()}</>}
}

// 然后像这样使用默认值
#[function_component]
fn Case1() -> Html {
    html! {<HelloWorld />}
}
// 或者不覆盖默认值
#[function_component]
fn Case2() -> Html {
    html! {<HelloWorld name={"Sam".to_string()} />}
}
```

  </TabItem>
</Tabs>

## 使用 Properties 的内存/速度开销

内部属性是引用计数的。这意味着只有一个共享指针会沿着组件树向下传递给 props。这节省了我们不得不克隆整个 props 的成本，这可能很昂贵。

:::tip
使用 `AttrValue`，这是我们用于属性值的自定义类型，而不是将它们定义为 String 或其他类似类型。
:::

## Props 宏

`yew::props!` 宏允许您以与 `html!` 宏相同的方式构建属性。

宏使用与结构体表达式相同的语法，除了您不能使用属性或基本表达式（`Foo { ..base }`）。类型路径可以直接指向 props（`path::to::Props`）或指向组件的关联属性（`MyComp::Properties`）。

```rust
use yew::{function_component, html, Html, Properties, props, virtual_dom::AttrValue};

#[derive(Properties, PartialEq)]
pub struct Props {
    #[prop_or(AttrValue::from("Bob"))]
    pub name: AttrValue,
}

#[function_component]
fn HelloWorld(props: &Props) -> Html {
    html! {<>{"Hello world"}{props.name.clone()}</>}
}

#[function_component]
fn App() -> Html {
    // highlight-start
    let pre_made_props = props! {
        Props {} // 注意我们不需要指定 name 属性
    };
    // highlight-end
    html! {<HelloWorld ..pre_made_props />}
}
```

## 评估顺序

Props 按指定的顺序进行评估，如以下示例所示：

```rust
#[derive(yew::Properties, PartialEq)]
struct Props { first: usize, second: usize, last: usize }

fn main() {
    let mut g = 1..=3;
    let props = yew::props!(Props { first: g.next().unwrap(), second: g.next().unwrap(), last: g.next().unwrap() });

    assert_eq!(props.first, 1);
    assert_eq!(props.second, 2);
    assert_eq!(props.last, 3);
}
```

## 反模式

虽然几乎任何 Rust 类型都可以作为属性传递，但有一些应该避免的反模式。这些包括但不限于：

1. 使用 `String` 类型而不是 `AttrValue`。 <br />
   **为什么不好？** `String` 克隆成本很高。当属性值与钩子和回调一起使用时，通常需要克隆。`AttrValue` 是一个引用计数的字符串 (`Rc<str>`) 或一个 `&'static str`，因此非常便宜克隆。<br />
   **注意**：`AttrValue` 内部是来自 [implicit-clone](https://crates.io/crates/implicit-clone) 的 `IString`。查看该包以了解更多信息。
2. 使用内部可变性。 <br />
   **为什么不好？** 内部可变性（例如 `RefCell`、`Mutex` 等）应该 _通常_ 避免使用。它可能会导致重新渲染问题（Yew 不知道状态何时发生了变化），因此您可能需要手动强制重新渲染。就像所有事物一样，它有其用武之地。请谨慎使用。
3. 使用 `Vec<T>` 类型而不是 `IArray<T>`。 <br />
   **为什么不好？** `Vec<T>`，就像 `String` 一样，克隆成本也很高。`IArray<T>` 是一个引用计数的切片 (`Rc<[T]>`) 或一个 `&'static [T]`，因此非常便宜克隆。<br />
   **注意**：`IArray<T>` 可以从 [implicit-clone](https://crates.io/crates/implicit-clone) 导入。查看该包以了解更多信息。
4. 您发觉可能的新内容。您是否遇到了一个希望早点了解清楚的边缘情况？请随时创建一个问题或向本文档提供修复的 PR。

## yew-autoprops

[yew-autoprops](https://crates.io/crates/yew-autoprops) 是一个实验性包，允许您根据函数的参数动态创建 Props 结构体。如果属性结构体永远不会被重用，这可能会很有用。
