---
title: '屬性 (Properties)'
description: '父子元件通訊'
---

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

:::note

屬性 (Properties) 通常被簡寫為 "Props"。

:::

屬性 (Properties) 是元件的參數，Yew 可以監視這些參數。

在元件的屬性中使用一個類型之前，它必須實作 `Properties` trait。

## 響應性

在重新渲染時，Yew 在協調虛擬 DOM 時檢查屬性是否已更改，以了解是否需要重新渲染巢狀元件。這樣，Yew 可以被認為是一個非常具有響應性的框架，因為來自父組件的變更總是會向下傳播，視圖永遠不會與來自屬性/狀態的資料不同步。

:::tip

如果您尚未完成 [教學](../../tutorial)，請嘗試並自行測試這種回應性！

:::

## 派生宏

Yew 提供了一個衍生宏，可以輕鬆地在結構體上實作 `Properties` trait。

您衍生 `Properties` 的型別也必須實作 `PartialEq`，以便 Yew 可以進行資料比較。

```rust
use yew::Properties;

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

## 在函數元件中使用

屬性 `#[component]` 允許在函式參數中選擇性地接收 Props。要提供它們，可以透過 `html!` 巨集中的屬性進行賦值。

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

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

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

#[component]
fn HelloWorld(&Props { is_loading }: &Props) -> Html {
    html! { {"Am I loading? - "}{is_loading} }
}

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

```

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

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

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

// 沒有屬性需要提供
#[component]
fn App() -> Html {
    html! { <HelloWorld /> }
}

```

  </TabItem>
</Tabs>

## 派生巨集欄位屬性

在派生 `Properties` 時，預設情況下所有欄位都是必要的。

以下屬性可讓您為屬性提供預設值，當父元件沒有設定它們時將使用這些預設值。

:::tip
屬性在 Rustdoc 產生的文檔中是不可見的。您的屬性的文檔字串應該提到一個屬性是否是可選的，以及它是否有一個特殊的預設值。
:::

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

使用 `Default` trait 的欄位類型的預設值初始化屬性值。

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

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

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

// 這樣使用預設值
#[component]
fn Case1() -> Html {
    html! { <HelloWorld /> }
}
// 或不覆蓋預設值
#[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::prelude::*;

#[derive(Properties, PartialEq)]
pub struct Props {
    #[prop_or_default]
    pub is_loading: bool,
    // highlight-start
    #[prop_or(AttrValue::Static("Bob"))]
    // highlight-end
    pub name: AttrValue,
}

#[component]
fn Hello(&Props { is_loading, ref name }: &Props) -> Html {
    if is_loading {
        html! { "Loading" }
    } else {
        html! { {"Hello "}{name} }
    }
}

// 這樣使用預設值
#[component]
fn Case1() -> Html {
    html! { <Hello /> }
}
// 或不覆蓋預設值
#[component]
fn Case2() -> Html {
    html! { <Hello name="Sam" /> }
}
```

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

呼叫 `function` 來初始化屬性值。 `function` 應該有 `FnMut() -> T` 簽名，其中 `T` 是欄位類型。當沒有為該屬性給出明確的值時，將呼叫該函數。
這個函數在屬性被建構時被呼叫。

```rust
use yew::prelude::*;

fn create_default_name() -> AttrValue {
    AttrValue::Static("Bob")
}

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

#[component]
fn Hello(&Props { is_loading, ref name }: &Props) -> Html {
    if is_loading {
        html! { "Loading" }
    } else {
        html! { {"Hello "}{name} }
    }
}

// 使用預設值
#[component]
fn Case1() -> Html {
    html! { <Hello /> }
}
// 或不覆蓋預設值
#[component]
fn Case2() -> Html {
    html! { <Hello name="Sam" /> }
}
```

  </TabItem>
</Tabs>

## 使用 Properties 的效能開銷

內部屬性是以引用計數的智慧型指標傳遞的。這意味著只有一個共享指標被傳遞到元件樹中的屬性，這樣就能節約克隆整個屬性的高昂成本。

:::tip
`AttrValue` 是我們用於屬性值的自訂類型，這樣就不用將它們定義為 String 或其他類似克隆成本高昂的類型了。
:::

## Props 巨集

`yew::props!` 巨集允許您以與 `html!` 巨集相同的方式建立屬性。

這個巨集使用與結構表達式相同的語法，只是您不能使用屬性或基本表達式 (`Foo { ..base }`)。類型路徑可以直接指向屬性 (`path::to::Props`)，也可以指向元件的關聯屬性 (`MyComp::Properties`)。

```rust
use yew::prelude::*;

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

#[component]
fn Hello(&Props { is_loading, ref name }: &Props) -> Html {
    if is_loading {
        html! { "Loading" }
    } else {
        html! { {"Hello "}{name} }
    }
}

#[component]
fn App() -> Html {
    // highlight-start
    let pre_made_props = yew::props! {
        Props {} // 注意我們不需要指定 name 屬性
    };
    // highlight-end
    html! { <Hello ..pre_made_props /> }
}
```

## 自動產生屬性 (yew-autoprops)

為了簡化您的開發流程，您也可以使用巨集 `#[autoprops]`（來自 `yew-autoprops` 套件）自動產生 `Properties` 結構體。

```rust
use yew::prelude::*;
use yew_autoprops::autoprops;

// #[autoprops] 巨集必須出現在 #[component] 之前，順序很重要
#[autoprops]
#[component]
fn Greetings(
    #[prop_or_default]
    is_loading: bool,
    #[prop_or(AttrValue::Static("Hello"))]
    message: &AttrValue,
    #[prop_or(AttrValue::Static("World"))]
    name: &AttrValue,
) -> Html {
    if is_loading {
        html! { "Loading" }
    } else {
        html! { {message}{" "}{name} }
    }
}

// 結構體 "GreetingsProps" 將會自動產生。
//
// `is_loading` 將作為值傳遞給元件，而 `message` 和 `name` 將使用引用，因為定義中有一個前導的 `&`。
```

與獨立的屬性結構體相比，`yew-autoprops` 還有一個額外的優勢，即能夠暴露未使用的屬性。

```rust
use yew::prelude::*;
use yew_autoprops::autoprops;

#[autoprops]
#[component]
// highlight-next-line
pub fn Foo(bar0: AttrValue, bar1: AttrValue) -> Html {
    // `bar1` 將產生一個關於未使用變數的編譯器警告
    html! {<div>{bar0}</div>}
}

#[derive(PartialEq, Properties, Clone)]
pub struct BarProps {
    bar0: AttrValue,
    // 這個屬性未被使用，但編譯器無法偵測到
    bar1: AttrValue,
}

#[component]
pub fn Bar(props: &BarProps) -> Html {
    html! {<div>{&props.bar0}</div>}
}
```

## 評估順序

屬性依照指定的順序進行評估，如下例所示：

```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` 可以從 [implicit-clone](https://crates.io/crates/implicit-clone) 匯入。查看該包以了解更多資訊。
4. 您發覺可能的新內容。您是否遇到了一個希望早點了解清楚的邊緣情況？請隨時建立一個問題或向本文檔提供修復的 PR。
