+ for x in 0..3_u32 {
+ let mut acc: u32 = 0;
+ for i in 0..=x {
+ acc += i;
+ }
+ {acc}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "013"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn for_imperative_inner_while_runs_as_preamble() {
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for _x in 0..2_u32 {
+ let mut counter: u32 = 0;
+ while counter < 3 {
+ counter += 1;
+ }
+ {counter}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "33"
+ );
+}
+
+#[wasm_bindgen_test]
+async fn for_imperative_inner_loop_runs_as_preamble() {
+ #[component]
+ fn App() -> Html {
+ html! {
+
+ for x in 1..=3_u32 {
+ let mut n: u32 = 0;
+ loop {
+ n += 1;
+ if n >= x {
+ break;
+ }
+ }
+ {n}
+ }
+
+ }
+ }
+
+ assert_eq!(
+ render_and_read::().await,
+ "123"
+ );
+}
diff --git a/website/docs/concepts/html/conditional-rendering.mdx b/website/docs/concepts/html/conditional-rendering.mdx
index f1f935a6347..422a92374af 100644
--- a/website/docs/concepts/html/conditional-rendering.mdx
+++ b/website/docs/concepts/html/conditional-rendering.mdx
@@ -156,3 +156,46 @@ html! {
Arms with a single element can omit braces. Arms with multiple children or `let` bindings require braces.
`match` supports all standard Rust patterns including OR-patterns (`A | B`), destructuring, and `if` guards. Exhaustiveness is checked by the Rust compiler.
+
+## Loops inside `if`/`match` bodies
+
+`for`, `while`, and `loop` inside an `if`/`else if`/`else`/`match`-arm body
+follow the same rules they do at the top level of `html!`, with one caveat:
+if the loop body is fully Rust-parseable (no html elements anywhere inside),
+the macro takes the loop as a Rust statement instead of html-control-flow.
+
+The most common way to hit this is a loop whose only child is a bare
+`{expr}` block:
+
+```rust , ignore
+// Compiles. The for is at the top level, parsed as html-`for`.
+html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+}
+
+// Does NOT compile. The for is inside an `if`, parsed as a Rust statement;
+// rustc then complains that the body returns a value where `()` is expected.
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+Wrap the child in an html element to keep it as html-`for`:
+
+```rust , ignore
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+See [Lists](./lists.mdx) for the full set of workarounds.
diff --git a/website/docs/concepts/html/lists.mdx b/website/docs/concepts/html/lists.mdx
index 7111aa90a9e..8628c14c672 100644
--- a/website/docs/concepts/html/lists.mdx
+++ b/website/docs/concepts/html/lists.mdx
@@ -25,10 +25,10 @@ html! {
};
```
-`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
-`;`.
+`for` loop bodies accept Rust statements before the html children: `let`
+bindings, expression statements ending in `;`, item definitions (`fn`,
+`struct`, `use`, ...), macro invocations with `;`, and imperative `for`,
+`while`, or `loop` blocks for side effects.
```rust , ignore
use yew::prelude::*;
@@ -37,7 +37,12 @@ html! {
for item in items {
let label = format!("{}: {}", item.id, item.name);
let class = if item.active { "active" } else { "inactive" };
- {label}
+ let mut tags = String::new();
+ for tag in &item.tags {
+ tags.push_str(tag);
+ tags.push(' ');
+ }
+ {label}
}
};
```
@@ -112,15 +117,15 @@ A bare qualified-path expression like `::method(...)` collides with the
:::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:
+A nested `for`, `while`, or `loop` whose body has only Rust statements (no
+html elements anywhere inside) is detected as a Rust statement, not as
+html-control-flow. So this works as written:
```rust , ignore
html! {
for item in items.iter() {
let mut by_han = BTreeMap::new();
- for src in &item.sources { // imperative side-effect loop
+ for src in &item.sources {
by_han.entry(src.han_nom.clone()).or_default().push(src);
}
{render(by_han)}
@@ -128,26 +133,82 @@ html! {
}
```
-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`:
+If you mix html into the inner loop, the macro takes that loop as
+html-control-flow instead and emits its children per iteration:
```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)}
+ for tag in &item.tags {
+ {tag}
+ }
+ }
+}
+```
+
+:::
+
+:::caution `for` with a bare `{expr}` body inside `if`/`match`/`while`/another `for`
+
+A `for` whose only child is a bare `{expr}` block renders one node per
+iteration at the **top level** of an `html!`, but the same code nested
+inside an `if`/`match` arm/`while`/another `for` body is rejected with a
+`mismatched types: expected (), found T` error. The two positions use
+different parsing strategies, and the nested one tries to parse the loop
+as a Rust statement first. If the body is fully Rust-parseable (which a
+bare `{expr}` block is), it stops being html-control-flow.
+
+```rust , ignore
+// Works: the for is at the top level.
+html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+}
+
+// Does NOT compile: the for is nested inside an `if`.
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+To render `my_foo` 9 times conditionally, pick one of:
+
+```rust , ignore
+// 1. Wrap the child in an html element (recommended).
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+
+// 2. Push the for to the top level of a nested `html!`.
+html! {
+ if condition {
+ { html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ } }
+ }
+}
+
+// 3. Build the list with an iterator and `collect::()`.
+html! {
+ if condition {
+ { (0..9).map(|_| html!({my_foo})).collect::() }
}
}
```
-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.
+The same rule applies inside `match` arms, `while` bodies, and the body
+of an outer `for`.
:::
@@ -155,7 +216,8 @@ 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`.
+items, macros, and imperative `for`/`while`/`loop` blocks) are also accepted here, along with
+`if` blocks and `break`/`continue`.
```rust
use yew::prelude::*;
diff --git a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
index 0d5c3e726ad..90af1f56580 100644
--- a/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
+++ b/website/i18n/ja/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
@@ -156,3 +156,42 @@ html! {
単一要素のアームはブレースを省略できます。複数の子要素や `let` バインディングを持つアームにはブレースが必要です。
`match` は OR パターン(`A | B`)、分割代入、`if` ガードを含む、すべての標準的な Rust パターンをサポートします。網羅性は Rust コンパイラによってチェックされます。
+
+## `if` / `match` の本体内部のループ
+
+`if` / `else if` / `else` / `match` のアーム本体内部の `for`、`while`、`loop` は、`html!` のトップレベル位置と同じ規則に従いますが、一つだけ例外があります:ループ本体が完全に Rust として解析可能な場合(内部のどこにも html 要素がない場合)、マクロはそのループを html の制御フローではなく Rust の文として扱います。
+
+もっとも遭遇しやすいのは、ループ本体に裸の `{expr}` ブロックしか書かれていないケースです:
+
+```rust , ignore
+// コンパイル可能。for はトップレベルにあり、html-`for` として解析されます。
+html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+}
+
+// コンパイル不可。for は `if` の内部にあり、Rust の文として解析されます。
+// その後 rustc が、ループ本体の戻り型が `()` ではないと報告します。
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+子要素を html 要素で囲めば html-`for` の挙動が保たれます:
+
+```rust , ignore
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+回避策の全体像は[リスト](./lists.mdx)を参照してください。
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 fae271b25c4..09843bec909 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
@@ -97,6 +97,63 @@ let mut rendered = Vec::new();
- 末尾に `;` を付けてプリアンブルの式文にします:`::method(...);`。戻り値は破棄されます。
- `{...}` で囲んで戻り値をノードとして使います:`{ ::method(...) }`。
+:::
+
+:::caution `if` / `match` / `while` / 外側の `for` の内部で本体が裸の `{expr}` だけの `for`
+
+`for` ループの本体に裸の `{expr}` ブロックしか書かれていない場合、`html!` の**トップレベル**にあれば反復ごとに 1 ノードずつ描画されます。ところが同じコードを `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 {
+ {my_foo}
+ }
+ }
+}
+
+// 2. for をネストした `html!` のトップレベルへ移す。
+html! {
+ if condition {
+ { html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ } }
+ }
+}
+
+// 3. イテレータと `collect::()` でリストを組み立てる。
+html! {
+ if condition {
+ { (0..9).map(|_| html!({my_foo})).collect::() }
+ }
+}
+```
+
+同じ規則は `match` のアーム本体、`while` の本体、外側の `for` の本体にも当てはまります。
+
:::
diff --git a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
index 1dd65ef3665..f1b58782fb6 100644
--- a/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
+++ b/website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
@@ -156,3 +156,42 @@ html! {
单元素 arm 可以省略大括号。含多个子节点或 `let` 绑定的 arm 需要大括号。
`match` 支持所有标准 Rust 模式,包括 OR 模式(`A | B`)、解构和 `if` 守卫。穷尽性由 Rust 编译器检查。
+
+## `if` / `match` 体内部的循环
+
+`if` / `else if` / `else` / `match` 分支体内部的 `for`、`while`、`loop` 与 `html!` 顶层位置的规则相同,但有一个例外:如果循环体可以被 Rust 完整解析(内部任何位置都没有 html 元素),宏会把这个循环视为 Rust 语句而不是 html 控制流。
+
+最常见的触发情形是循环体里只有一个裸 `{expr}` 块:
+
+```rust , ignore
+// 可以编译。for 在最外层,被解析为 html-`for`。
+html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+}
+
+// 无法编译。for 在 `if` 内部,被解析为 Rust 语句;
+// 之后 rustc 会报告循环体返回的类型不是 `()`。
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+把子节点用 html 元素包起来即可保留 html-`for` 行为:
+
+```rust , ignore
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+完整的解决方案集合请参见[列表](./lists.mdx)。
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 35a17177329..8b6e5837a5e 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
@@ -97,6 +97,63 @@ let mut rendered = Vec::new();
- 在末尾加上 `;`,使其成为前置的表达式语句:`::method(...);`。返回值会被丢弃。
- 用 `{...}` 包起来,将返回值作为节点使用:`{ ::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 {
+ {my_foo}
+ }
+ }
+}
+
+// 2. 把 for 放进嵌套的 `html!` 顶层。
+html! {
+ if condition {
+ { html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ } }
+ }
+}
+
+// 3. 用迭代器配合 `collect::()` 构建列表。
+html! {
+ if condition {
+ { (0..9).map(|_| html!({my_foo})).collect::() }
+ }
+}
+```
+
+同样的规则也适用于 `match` 分支、`while` 循环体以及外层 `for` 的循环体。
+
:::
diff --git a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
index b758ffd7b09..8f52272bd63 100644
--- a/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
+++ b/website/i18n/zh-Hant/docusaurus-plugin-content-docs/current/concepts/html/conditional-rendering.mdx
@@ -156,3 +156,42 @@ html! {
單元素 arm 可以省略大括號。含多個子節點或 `let` 綁定的 arm 需要大括號。
`match` 支援所有標準 Rust 模式,包括 OR 模式(`A | B`)、解構和 `if` 守衛。窮舉性由 Rust 編譯器檢查。
+
+## `if` / `match` 主體內部的迴圈
+
+`if` / `else if` / `else` / `match` 分支主體內部的 `for`、`while`、`loop` 與 `html!` 最外層位置的規則相同,但有一個例外:如果迴圈主體可以被 Rust 完整剖析(內部任何位置都沒有 html 元素),巨集會把這個迴圈視為 Rust 陳述式而不是 html 控制流。
+
+最常見的觸發情形是迴圈主體裡只有一個裸 `{expr}` 區塊:
+
+```rust , ignore
+// 可以編譯。for 在最外層,被解析為 html-`for`。
+html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+}
+
+// 無法編譯。for 在 `if` 內部,被解析為 Rust 陳述式;
+// 之後 rustc 會回報迴圈主體傳回的型別不是 `()`。
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+把子節點用 html 元素包起來即可保留 html-`for` 行為:
+
+```rust , ignore
+html! {
+ if condition {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ }
+}
+```
+
+完整的解決方法集合請參見[清單](./lists.mdx)。
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 8bde1886279..c74cc8cc292 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
@@ -97,6 +97,63 @@ let mut rendered = Vec::new();
- 在結尾加上 `;`,讓它成為前置的運算式陳述式:`::method(...);`。傳回值會被丟棄。
- 用 `{...}` 包起來,將傳回值當作節點使用:`{ ::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 {
+ {my_foo}
+ }
+ }
+}
+
+// 2. 把 for 放進巢狀 `html!` 的最外層。
+html! {
+ if condition {
+ { html! {
+ for _ in 0..9 {
+ {my_foo}
+ }
+ } }
+ }
+}
+
+// 3. 使用迭代器搭配 `collect::()` 建構清單。
+html! {
+ if condition {
+ { (0..9).map(|_| html!({my_foo})).collect::() }
+ }
+}
+```
+
+同樣的規則也適用於 `match` 分支、`while` 迴圈主體以及外層 `for` 的迴圈主體。
+
:::