` fragment, and the "never" span is dead
+ // code. Verifies bare `return val;` semantics in a for-body preamble.
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for _ in 0..1 {
+ return html!{
{"returned"}
};
+
{"never"}
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::
().await, "returned");
+}
+
+#[wasm_bindgen_test]
+async fn for_return_in_unbraced_match_arm() {
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for i in 0..10 {
+ match i {
+ 3 => return html!{
{format!("stopped at {i}")}
},
+ _ =>
{i},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "stopped at 3");
+}
+
+// `break `, `continue `, and `return `
+// in an unbraced match arm are compile errors (see html-match-fail.rs). The
+// documented workaround is to wrap the arm body in braces and use `html!(...)`
+// to produce the value. The tests below verify the workaround compiles and
+// renders correctly.
+#[wasm_bindgen_test]
+async fn for_return_html_workaround_with_braced_arm() {
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for i in 0..10 {
+ match i {
+ 3 => { return html!(
{format!("stopped at {i}")}
) }
+ _ =>
{i},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "stopped at 3");
+}
+
+#[wasm_bindgen_test]
+async fn for_break_workaround_with_braced_arm() {
+ // Breaking the loop doesn't need a value; the workaround for users who
+ // tried `break ` is just a braced `{ break }` arm.
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for i in 0..10 {
+ match i {
+ 3 => { break }
+ _ => {i},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "012"
+ );
+}
diff --git a/packages/yew/tests/html_while.rs b/packages/yew/tests/html_while.rs
new file mode 100644
index 00000000000..b2408e58e1a
--- /dev/null
+++ b/packages/yew/tests/html_while.rs
@@ -0,0 +1,364 @@
+#![cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
+
+mod common;
+
+use common::obtain_result;
+use wasm_bindgen_test::*;
+use yew::prelude::*;
+use yew::scheduler;
+
+wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser);
+
+async fn render_and_read>() -> String {
+ yew::Renderer::::with_root(gloo::utils::document().get_element_by_id("output").unwrap())
+ .render();
+ scheduler::flush().await;
+ obtain_result()
+}
+
+#[wasm_bindgen_test]
+async fn while_iterates_until_false() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 5 {
+ let current = { let c = i; i += 1; c };
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "01234"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_break_exits_early() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 100 {
+ let current = { let c = i; i += 1; c };
+ if current > 5 {
+ break
+ }
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "012345"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_continue_skips_matching() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 6 {
+ let current = { let c = i; i += 1; c };
+ if current % 2 == 0 {
+ continue
+ }
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "135"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_let_iterates_option_some() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..4).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ {v}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "0123"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_let_break_and_continue_together() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..100).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ if v >= 8 {
+ break
+ }
+ if v % 3 == 0 {
+ continue
+ }
+ {v}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "12457"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_break_with_trailing_semi() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 100 {
+ let current = { let c = i; i += 1; c };
+ if current > 2 {
+ break;
+ }
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "012"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_continue_with_trailing_semi() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 6 {
+ let current = { let c = i; i += 1; c };
+ if current % 2 == 0 {
+ continue;
+ }
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "135"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_let_match_arm_break() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..10).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ match v {
+ 3 => break,
+ _ => {v},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "012"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_let_match_arm_continue() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..6).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ match v % 2 {
+ 0 => continue,
+ _ => {v},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "135"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_expr_stmt_preamble_increments() {
+ #[component]
+ fn App() -> Html {
+ let mut i: i32 = 0;
+ html! {
+
+ while i < 4 {
+ let current = i;
+ i += 1;
+ {current}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "0123"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn while_labeled_break_no_semi() {
+ #[component]
+ fn App() -> Html {
+ let mut rows_completed = 0;
+ 'outer: for _row in 0..3 {
+ let mut i: i32 = 0;
+ let _ = html! {
+ while i < 10 {
+ let current = i;
+ i += 1;
+ if current >= 1 {
+ break 'outer
+ }
+ {current}
+ }
+ };
+ rows_completed += 1;
+ }
+ html! {
+ { format!("rows_completed={rows_completed}") }
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "rows_completed=0");
+}
+
+#[wasm_bindgen_test]
+async fn while_labeled_continue_no_semi() {
+ #[component]
+ fn App() -> Html {
+ let mut rows_skipped = 0;
+ 'outer: for row in 0..3 {
+ let mut i: i32 = 0;
+ let _ = html! {
+ while i < 2 {
+ let current = i;
+ i += 1;
+ if row == 1 {
+ continue 'outer
+ }
+ {current}
+ }
+ };
+ if row == 1 {
+ rows_skipped += 1;
+ }
+ }
+ html! {
+ { format!("rows_skipped={rows_skipped}") }
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "rows_skipped=0");
+}
+
+#[wasm_bindgen_test]
+async fn while_return_exits_component() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..1).into_iter();
+ html! {
+
+ while let Some(_) = it.next() {
+ return html!{
{"returned"}
};
+
{"never"}
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "returned");
+}
+
+#[wasm_bindgen_test]
+async fn while_return_in_unbraced_match_arm() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..10).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ match v {
+ 3 => return html!{
{format!("stopped at {v}")}
},
+ _ =>
{v},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "stopped at 3");
+}
+
+// Counterpart to `for_return_html_workaround_with_braced_arm`: verifies the
+// braced-arm workaround works under `while let` too.
+#[wasm_bindgen_test]
+async fn while_return_html_workaround_with_braced_arm() {
+ #[component]
+ fn App() -> Html {
+ let mut it = (0..10).into_iter();
+ html! {
+
+ while let Some(v) = it.next() {
+ match v {
+ 3 => { return html!(
{format!("stopped at {v}")}
) }
+ _ =>
{v},
+ }
+ }
+
+ }
+ }
+
+ assert_eq!(render_and_read::().await, "stopped at 3");
+}
diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx
index abb2ce30da6..7111aa90a9e 100644
--- a/website/docs/concepts/html/lists.mdx
+++ b/website/docs/concepts/html/lists.mdx
@@ -7,13 +7,13 @@ import TabItem from '@theme/TabItem'
## Iterators
-There are 3 ways to build HTML from iterators:
+There are 4 ways to build HTML from iterators:
-The main approach is to use for loops, the same for loops that already exist in Rust, but with 2 key differences:
-1. Unlike standard for loops which can't return anything, for loops in `html!` are converted to a list of nodes;
-2. Diverging expressions, i.e. `break`, `continue` are not allowed in the body of for loops in `html!`.
+The main approach is to use for loops, the same for loops that already exist in Rust, with one
+key difference: unlike standard for loops which can't return anything, for loops in `html!` are
+converted to a list of nodes.
```rust
use yew::prelude::*;
@@ -25,7 +25,10 @@ html! {
};
```
-`for` loop bodies also support `let` bindings before the html children:
+`for` loop bodies accept Rust statements before the html children. Any
+terminated statement works: `let` bindings, expression statements ending in
+`;`, item definitions (`fn`, `struct`, `use`, ...), and macro invocations with
+`;`.
```rust , ignore
use yew::prelude::*;
@@ -37,6 +40,155 @@ html! {
{label}
}
};
+```
+
+`break` and `continue` work as in ordinary Rust loops and affect the emitted node list.
+`continue` skips the current iteration without producing a node, `break` stops iteration early:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ if i % 2 == 0 {
+ continue
+ }
+ if i > 7 {
+ break
+ }
+ {i}
+ }
+};
+```
+
+`break` and `continue` are also available directly as match arms, whether
+braced or unbraced:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ match i {
+ 0 => continue,
+ 8.. => break,
+ _ => {i},
+ }
+ }
+};
+```
+
+Loop labels are supported. `break 'label` and `continue 'label` target a
+labeled loop defined in the surrounding Rust code, letting you exit an
+enclosing loop from inside the macro:
+
+```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;
+ }
+ {item.name}
+ }
+ });
+}
+```
+
+:::note Qualified paths
+
+A bare qualified-path expression like `::method(...)` collides with the
+`` element open tag and is rejected. Two ways out:
+
+- Add a trailing `;` and it becomes an expression statement in the preamble:
+ `::method(...);`. The return value is discarded.
+- Wrap it in `{...}` to use the return value as a node:
+ `{ ::method(...) }`.
+
+:::
+
+:::note Imperative loops in the preamble
+
+Block-like Rust expressions (`for`, `while`, `loop`, `{...}`) auto-terminate as
+statements in Rust grammar - a trailing `;` is not folded into the statement.
+A bare imperative loop in a preamble:
+
+```rust , ignore
+html! {
+ for item in items.iter() {
+ let mut by_han = BTreeMap::new();
+ for src in &item.sources { // imperative side-effect loop
+ by_han.entry(src.han_nom.clone()).or_default().push(src);
+ }
+ {render(by_han)}
+ }
+}
+```
+
+is parsed as html-`for` (emitting children) instead of as a Rust statement,
+and its body's `()` value cannot be converted to a `VNode`. Bind it with
+`let _ = ...;` so the parser sees a `Stmt::Local`:
+
+```rust , ignore
+html! {
+ for item in items.iter() {
+ let mut by_han = BTreeMap::new();
+ let _ = for src in &item.sources {
+ by_han.entry(src.han_nom.clone()).or_default().push(src);
+ };
+ {render(by_han)}
+ }
+}
+```
+
+The same applies to imperative `while`, `loop`, and bare-block `{...}`
+statements. `match` and `if` are not subject to this collision because their
+html-control-flow forms naturally accept the same body shapes as their
+imperative use.
+
+:::
+
+
+
+`while` and `while let` loops work the same way as `for`, producing a list of nodes from their
+body. All the statement forms accepted by `for` bodies (let bindings, expression statements,
+items, macros) are also accepted here, along with `if` blocks and `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
+ }
+ {item}
+ }
+};
+```
+
+A plain `while` with a condition works too:
+
+```rust
+use yew::prelude::*;
+
+let mut counter: i32 = 0;
+
+html! {
+ while counter < 5 {
+ let current = counter;
+ counter += 1;
+ {current}
+ }
+};
```
diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
index fdfc25dc5f6..fae271b25c4 100644
--- a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
+++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
@@ -7,13 +7,11 @@ import TabItem from '@theme/TabItem'
## イテレータ
-イテレータから HTML を構築する方法は 3 つあります:
+イテレータから HTML を構築する方法は 4 つあります:
-主なアプローチは for ループを使用することです。これは Rust に既に存在する for ループと同じですが、2 つの重要な違いがあります:
-1. 通常の for ループは何も返せませんが、`html!` 内の for ループはノードのリストに変換されます。
-2. `break`、`continue` などの発散式は `html!` 内の for ループの本体では許可されていません。
+主なアプローチは for ループを使用することです。これは Rust に既に存在する for ループと同じですが、1 つの重要な違いがあります:通常の for ループは何も返せませんが、`html!` 内の for ループはノードのリストに変換されます。
```rust
use yew::prelude::*;
@@ -25,7 +23,7 @@ html! {
};
```
-`for` ループ本体では、html 子要素の前に `let` バインディングも使用できます:
+`for` ループ本体では、html 子要素の前に Rust の文を置くことができます。終端子を持つ文であれば何でも動作します:`let` バインディング、`;` で終わる式文、アイテム定義(`fn`、`struct`、`use` など)、`;` 付きのマクロ呼び出しです。
```rust , ignore
use yew::prelude::*;
@@ -37,6 +35,106 @@ html! {
{label}
}
};
+```
+
+`break` と `continue` は通常の Rust のループと同様に動作し、出力されるノードリストに影響します。
+`continue` は現在の反復でノードを生成せずにスキップし、`break` は反復を早期に終了します:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ if i % 2 == 0 {
+ continue
+ }
+ if i > 7 {
+ break
+ }
+ {i}
+ }
+};
+```
+
+`break` と `continue` は、波括弧付きでも波括弧なしでも、match アームの本体としてそのまま使えます:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ match i {
+ 0 => continue,
+ 8.. => break,
+ _ => {i},
+ }
+ }
+};
+```
+
+ループラベルもサポートされています。`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;
+ }
+ {item.name}
+ }
+ });
+}
+```
+
+:::note 限定パス
+
+`::method(...)` のような裸の限定パス式は `` という要素開始タグと衝突し、受け付けられません。回避方法は 2 つあります:
+
+- 末尾に `;` を付けてプリアンブルの式文にします:`::method(...);`。戻り値は破棄されます。
+- `{...}` で囲んで戻り値をノードとして使います:`{ ::method(...) }`。
+
+:::
+
+
+
+`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
+ }
+ {item}
+ }
+};
+```
+
+条件付きの通常の `while` も使用できます:
+
+```rust
+use yew::prelude::*;
+
+let mut counter: i32 = 0;
+
+html! {
+ while counter < 5 {
+ let current = counter;
+ counter += 1;
+ {current}
+ }
+};
```
diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
index 7cd4ec9459b..35a17177329 100644
--- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
+++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
@@ -7,13 +7,11 @@ import TabItem from '@theme/TabItem'
## 迭代器
-从迭代器构建 HTML 有 3 种方法:
+从迭代器构建 HTML 有 4 种方法:
-主要方法是使用 for 循环,与 Rust 中已有的 for 循环相同,但有 2 个关键区别:
-1. 与标准 for 循环不能返回任何内容不同,`html!` 中的 for 循环会被转换为节点列表;
-2. 发散表达式,即 `break`、`continue` 在 `html!` 中的 for 循环体内是不允许的。
+主要方法是使用 for 循环,与 Rust 中已有的 for 循环相同,但有一个关键区别:与标准 for 循环不能返回任何内容不同,`html!` 中的 for 循环会被转换为节点列表。
```rust
use yew::prelude::*;
@@ -25,7 +23,7 @@ html! {
};
```
-`for` 循环体也支持在 html 子节点前使用 `let` 绑定:
+`for` 循环体支持在 html 子节点前书写 Rust 语句。任何带终止符的语句都可以:`let` 绑定、以 `;` 结尾的表达式语句、项定义(`fn`、`struct`、`use` 等)以及带 `;` 的宏调用。
```rust , ignore
use yew::prelude::*;
@@ -37,6 +35,106 @@ html! {
{label}
}
};
+```
+
+`break` 和 `continue` 的作用与普通 Rust 循环相同,会影响生成的节点列表。
+`continue` 会跳过当前迭代而不产生节点,`break` 会提前结束迭代:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ if i % 2 == 0 {
+ continue
+ }
+ if i > 7 {
+ break
+ }
+ {i}
+ }
+};
+```
+
+`break` 和 `continue` 也可以直接作为 match 分支使用,无论带花括号还是不带:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ match i {
+ 0 => continue,
+ 8.. => break,
+ _ => {i},
+ }
+ }
+};
+```
+
+循环标签也受支持。`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;
+ }
+ {item.name}
+ }
+ });
+}
+```
+
+:::note 限定路径
+
+像 `::method(...)` 这样的裸限定路径表达式会与 `` 元素起始标签冲突,因此会被拒绝。有两种解决方法:
+
+- 在末尾加上 `;`,使其成为前置的表达式语句:`::method(...);`。返回值会被丢弃。
+- 用 `{...}` 包起来,将返回值作为节点使用:`{ ::method(...) }`。
+
+:::
+
+
+
+`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
+ }
+ {item}
+ }
+};
+```
+
+带条件的普通 `while` 也可以使用:
+
+```rust
+use yew::prelude::*;
+
+let mut counter: i32 = 0;
+
+html! {
+ while counter < 5 {
+ let current = counter;
+ counter += 1;
+ {current}
+ }
+};
```
diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
index b2114e57bbc..8bde1886279 100644
--- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
+++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/lists.mdx
@@ -7,13 +7,11 @@ import TabItem from '@theme/TabItem'
## 迭代器
-從迭代器建立 HTML 有 3 種方法:
+從迭代器建立 HTML 有 4 種方法:
-主要方法是使用 for 迴圈,與 Rust 中已有的 for 迴圈相同,但有 2 個關鍵區別:
-1. 與標準 for 迴圈不能傳回任何內容不同,`html!` 中的 for 迴圈會被轉換為節點清單;
-2. 發散運算式,即 `break`、`continue` 在 `html!` 中的 for 迴圈主體內是不允許的。
+主要方法是使用 for 迴圈,與 Rust 中已有的 for 迴圈相同,但有一個關鍵區別:與標準 for 迴圈不能傳回任何內容不同,`html!` 中的 for 迴圈會被轉換為節點清單。
```rust
use yew::prelude::*;
@@ -25,7 +23,7 @@ html! {
};
```
-`for` 迴圈主體也支援在 html 子節點前使用 `let` 綁定:
+`for` 迴圈主體支援在 html 子節點前書寫 Rust 陳述式。任何帶終止符號的陳述式都可以:`let` 綁定、以 `;` 結尾的運算式陳述式、項目定義(`fn`、`struct`、`use` 等)以及帶 `;` 的巨集呼叫。
```rust , ignore
use yew::prelude::*;
@@ -37,6 +35,106 @@ html! {
{label}
}
};
+```
+
+`break` 和 `continue` 的作用與一般 Rust 迴圈相同,會影響生成的節點清單。
+`continue` 會跳過目前迭代而不產生節點,`break` 會提前終止迭代:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ if i % 2 == 0 {
+ continue
+ }
+ if i > 7 {
+ break
+ }
+ {i}
+ }
+};
+```
+
+`break` 和 `continue` 也可以直接作為 match 分支使用,無論帶大括號或不帶:
+
+```rust
+use yew::prelude::*;
+
+html! {
+ for i in 0..10 {
+ match i {
+ 0 => continue,
+ 8.. => break,
+ _ => {i},
+ }
+ }
+};
+```
+
+迴圈標籤也受支援。`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;
+ }
+ {item.name}
+ }
+ });
+}
+```
+
+:::note 限定路徑
+
+像 `::method(...)` 這樣的裸限定路徑運算式會與 `` 元素起始標籤衝突,因此會被拒絕。有兩種解決方式:
+
+- 在結尾加上 `;`,讓它成為前置的運算式陳述式:`::method(...);`。傳回值會被丟棄。
+- 用 `{...}` 包起來,將傳回值當作節點使用:`{ ::method(...) }`。
+
+:::
+
+
+
+`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
+ }
+ {item}
+ }
+};
+```
+
+帶條件的一般 `while` 也可以使用:
+
+```rust
+use yew::prelude::*;
+
+let mut counter: i32 = 0;
+
+html! {
+ while counter < 5 {
+ let current = counter;
+ counter += 1;
+ {current}
+ }
+};
```