---
title: '列表'
---

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

## 迭代器

從迭代器建立 HTML 有 4 種方法：

<Tabs>
  <TabItem value="`for` loops" label="`for` 迴圈">
主要方法是使用 for 迴圈，與 Rust 中已有的 for 迴圈相同，但有一個關鍵區別：與標準 for 迴圈不能傳回任何內容不同，`html!` 中的 for 迴圈會被轉換為節點清單。

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

html! {
    for i in 0 .. 10 {
        <span>{i}</span>
    }
};
```

`for` 迴圈主體支援在 html 子節點前書寫 Rust 陳述式。任何帶終止符號的陳述式都可以：`let` 綁定、以 `;` 結尾的運算式陳述式、項目定義（`fn`、`struct`、`use` 等）以及帶 `;` 的巨集呼叫。

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

html! {
    for item in items {
        let label = format!("{}: {}", item.id, item.name);
        let class = if item.active { "active" } else { "inactive" };
        <div class={class}>{label}</div>
    }
};
```

`break` 和 `continue` 的作用與一般 Rust 迴圈相同，會影響生成的節點清單。
`continue` 會跳過目前迭代而不產生節點，`break` 會提前終止迭代：

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

html! {
    for i in 0..10 {
        if i % 2 == 0 {
            continue
        }
        if i > 7 {
            break
        }
        <span>{i}</span>
    }
};
```

`break` 和 `continue` 也可以直接作為 match 分支使用，無論帶大括號或不帶：

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

html! {
    for i in 0..10 {
        match i {
            0 => continue,
            8.. => break,
            _ => <span>{i}</span>,
        }
    }
};
```

迴圈標籤也受支援。`break 'label` 和 `continue 'label` 可以作用於巨集外部 Rust 程式碼中定義的帶標籤迴圈，讓你能從巨集內部跳出外層迴圈：

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

let mut rendered = Vec::new();
'outer: for section in sections {
    rendered.push(html! {
        for item in section.items {
            if should_stop(&item) {
                break 'outer;
            }
            <span>{item.name}</span>
        }
    });
}
```

:::note 限定路徑

像 `<Type>::method(...)` 這樣的裸限定路徑運算式會與 `<Type>` 元素起始標籤衝突，因此會被拒絕。有兩種解決方式：

- 在結尾加上 `;`，讓它成為前置的運算式陳述式：`<Type>::method(...);`。傳回值會被丟棄。
- 用 `{...}` 包起來，將傳回值當作節點使用：`{ <Type>::method(...) }`。

:::

:::caution 在 `if` / `match` / `while` / 外層 `for` 內部使用 `{expr}` 作為 `for` 迴圈主體

如果 `for` 迴圈主體只包含一個裸 `{expr}` 區塊，在 `html!` 的**最外層**位置可以正常運作，每次迭代會渲染一個節點；但同樣的程式碼嵌套在 `if` / `match` 分支 / `while` / 另一個 `for` 主體內時會被拒絕，並回報 `mismatched types: expected (), found T`。這兩個位置使用不同的剖析策略：嵌套位置會先嘗試將迴圈解析為 Rust 陳述式。如果迴圈主體能完全被 Rust 剖析（裸 `{expr}` 區塊就是這種情況），它就不再被視為 html 控制流。

```rust , ignore
// 可以編譯：for 位於最外層。
html! {
    for _ in 0..9 {
        {my_foo}
    }
}

// 無法編譯：for 嵌套在 `if` 內部。
html! {
    if condition {
        for _ in 0..9 {
            {my_foo}
        }
    }
}
```

要在條件成立時把 `my_foo` 渲染 9 次，請選擇以下任一方式：

```rust , ignore
// 1. 用 html 元素包裹子節點（建議做法）。
html! {
    if condition {
        for _ in 0..9 {
            <span>{my_foo}</span>
        }
    }
}

// 2. 把 for 放進巢狀 `html!` 的最外層。
html! {
    if condition {
        { html! {
            for _ in 0..9 {
                {my_foo}
            }
        } }
    }
}

// 3. 使用迭代器搭配 `collect::<Html>()` 建構清單。
html! {
    if condition {
        { (0..9).map(|_| html!({my_foo})).collect::<Html>() }
    }
}
```

同樣的規則也適用於 `match` 分支、`while` 迴圈主體以及外層 `for` 的迴圈主體。

:::

  </TabItem>
  <TabItem value="`while` loops" label="`while` 迴圈">
`while` 和 `while let` 迴圈的運作方式與 `for` 相同，也會從迴圈主體中生成節點清單。`for` 迴圈主體中支援的所有陳述式形式（let 綁定、運算式陳述式、項目、巨集）在這裡同樣可用，此外還有 `if` 區塊以及 `break` / `continue`。

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

let mut items = vec!["a", "b", "c", "skip", "d"].into_iter();

html! {
    while let Some(item) = items.next() {
        if item == "skip" {
            continue
        }
        if item.is_empty() {
            break
        }
        <span>{item}</span>
    }
};
```

帶條件的一般 `while` 也可以使用：

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

let mut counter: i32 = 0;

html! {
    while counter < 5 {
        let current = counter;
        counter += 1;
        <span>{current}</span>
    }
};
```

  </TabItem>
  <TabItem value="`for` block" label="`for` 區塊">
另一種方法是使用 `for` 關鍵字，這不是原生的 Rust 語法，而是由 HTML 巨集用於輸出顯示迭代器所需的程式碼。
當迭代器已經計算好，只需要將其傳遞給巨集時，這種方法比第一種更好。

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

let items = (1..=10).collect::<Vec<_>>();

html! {
    <ul class="item-list">
        { for items.iter() }
    </ul>
};
```

  </TabItem>
  <TabItem value="`collect` method" label="`collect` 方法">

最後一種方法是在迭代器的最終轉換上呼叫 `collect::<Html>()`，它傳回一個 Yew 可以顯示的清單。

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

let items = (1..=10).collect::<Vec<_>>();

html! {
    <ul class="item-list">
        { items.iter().collect::<Html>() }
    </ul>
};
```

  </TabItem>
</Tabs>

## 鍵 (Key) 列表

鍵 (Key) 列表是一個最佳化的列表，其中**所有**子元素都有鍵。
`key` 是 Yew 提供的一個特殊屬性，它為 HTML 元素或元件提供一個唯一標識符，用於 Yew 內部的最佳化。

:::caution
Key 只需要在每個清單中是唯一的，與 HTML `id` 的全域唯一性相反。它不應該依賴於列表的順序。
:::

始終建議為清單新增按鍵 (key)。

可以透過將唯一的 `String`、`str` 或整數傳遞給特殊的 `key` 屬性來新增鍵：

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

let names = vec!["Sam","Bob","Ray"]

html! {
    <div id="introductions">
        {
            names.into_iter().map(|name| {
                html!{<div key={name}>{ format!("Hello, I'am {}!",name) }</div>}
            }).collect::<Html>()
        }
    </div>
};

```

### 效能優化

我們有一個[帶有鍵 (keys) 的列表範例](https://github.com/yewstack/yew/tree/master/examples/keyed_list)可以讓你測試效能上的改進，這裡有一個簡單的測試流程：

1. 進入[線上示範](https://examples.yew.rs/keyed_list)
2. 新增 500 個元素
3. 停用鍵
4. 反轉列表
5. 看 "最後一次渲染花費了 Xms"（在撰寫本文時，大約為 60ms）
6. 啟用鍵
7. 再次反轉列表
8. 看 "最後一次渲染花費了 Xms"（在撰寫本文時，大約為 30ms）

截至撰寫本文時，對於 500 個組件，速度提高了 2 倍。

### 原理解釋

通常，當你迭代時，只需要在每個列表項目上添加一個鍵，資料的順序可能會改變。
在重新渲染清單時，它用於加速協調過程。

如果沒有鍵，假設你迭代 `["bob", "sam", "rob"]`，最終得到的 HTML 如下：

```html
<div id="bob">My name is Bob</div>
<div id="sam">My name is Sam</div>
<div id="rob">My name is rob</div>
```

然後在下一次渲染時，如果你的清單更改為 `["bob", "rob"]`，Yew 可以刪除 id="rob" 的元素，並將 id="sam" 更新為 id="rob"。

如果你為每個元素添加了一個鍵，初始HTML 將保持不變，但在使用修改後的列表`["bob", "rob"]` 進行渲染後，Yew 只會刪除第二個HTML 元素，而其他元素則保持不變，因為它可以使用鍵將它們關聯起來。

如果你遇到了一個從一個元件切換到另一個元件的 bug/"feature"，但兩者都有一個 div 作為最高渲染元素。
Yew 在這些情況下會重複使用已渲染的 HTML div 作為最佳化。
如果你需要該 div 被重新建立而不是被重複使用，那麼你可以添加不同的鍵，它們將不會被重複使用。

## 進一步閱讀

- [TodoMVC 範例](https://github.com/yewstack/yew/tree/master/examples/todomvc)
- [帶有按鍵 (keys) 的清單範例](https://github.com/yewstack/yew/tree/master/examples/keyed_list)
- [路由範例](https://github.com/yewstack/yew/tree/master/examples/router)
