ArkTS 进阶之道(13):ForEach 循环渲染边界——为啥 build 里不能写 for 循环 ArkTS 进阶之道13ForEach 循环渲染边界——为啥 build 里不能写 for 循环本文是「ArkTS 进阶之道」系列第 13 篇「ArkUI 渎染哲学」阶段收尾。上两篇讲渲染树绑定build() 重渲边界篇 60State 参只刷依赖 UI if 条件渲染边界篇 61if 贬饰器绑渲染树分支。本文讲循环渲染边界build 里 for 循环报错 vs ForEach 循环渲染装饰器合法——根因在 ForEach 编译期绑渲染树循环项不是运行时 for 语句迭代。能力系列篇 18 讲过循环渲染怎么用本文讲为哈 for 循环报错 ForEach 合法——根因在渲染树循环项绑定不是语句迭代。一、开篇build 里 for 不是循环是装饰器绑渲染树循环项不是迭代执行你写 TypeScript/React 时循环渲染是「魔法」JSX 里 for 循环不能直接写要用 map 映射数组// React JSX 里 for 循环不能直接写 function Component() { const list [一, 二, 三] return ( View {/* for (let i 0; i list.length; i) { Text{list[i]}/Text } ← JSX 里 for 不能直接写报错 */} {list.map(item Text{item/Text)} ← 要用 map 映射数组魔法 /View ) } // JSX 里 for 循环不能直接写要用 map 魔法绕你写鸿蒙 ArkTS 时build 里ForEach 贬饰器直接写——不用 map 绕// ArkTS build 里 ForEach 贬饰器直接写 Entry Component struct Index { State list: string[] [一, 二, 三] build() { Column() { ForEach(this.list, (item: string) { ← ForEach 贬饰器直接写合法不是 for 循环 Text(item) ← 每个元素绑一个 Text 渲染项 }, (item: string) item) ← keyGenerator 绑元素键值 } } } // build 里 ForEach 贬饰器直接写合法不用 map 绕不是 for 循环魔法 vs 贬饰器的区别React JSX 把循环渲染当「魔法」for 循环不能写map 绕ArkTS 把 ForEach 当「循环渲染装饰器」build 里 ForEach 直接写编译期绑渲染树循环项。根因不是魔法是渲染树循环项绑定——build 里 for 不是运行时迭代执行是编译期绑渲染树循环项的装饰器。二、根因build 里 ForEach 贬饰器的渲染树循环项绑定机制鸿蒙 ArkUI 的 build 里 ForEach 是渲染树循环项绑定——编译期把数组每个元素绑成渲染树循环项运行时按数组变重渲循环项不是运行时 for 语句迭代来自三重绑定机制。机制 1ForEach 编译期绑渲染树循环项——不是运行时 for 迭代ForEach 贬饰器编译期绑渲染树循环项——把数组每个元素编成渲染树的循环项不是运行时迭代的 for 语句Entry Component struct Index { State list: string[] [一, 二, 三] build() { Column() { ForEach(this.list, (item: string) { ← ForEach 贬饰器编译期绑循环项 Text(item) ← 每个元素绑一个 Text 循环项 }, (item: string) item) ← keyGenerator 绑元素键值一/二/三) } } } // 编译期ForEach 绑渲染树循环项1Text一 循环项2Text二 循环项3Text三) // 运行时按 list 数组渲染循环项不是运行时 for 循环迭代编译期绑循环项ForEach 贬饰器编译期把list数组每个元素绑成渲染树循环项——‘一’ 绑循环项1Text’一’‘二’ 绑循环项2Text’二’‘三’ 绑循环项3Text’三’。运行时按数组渲循环项——不是运行时 for 循环迭代执行是编译期绑的渲染树循环项按数组渲。根因不是迭代是渲染树循环项绑定。机制 2数组变自动重渲循环项——不是运行时 for 语句迭代ForEach 贬饰器数组变自动重渲循环项——数组增删触发重渲循环项不是运行时 for 语句迭代Entry Component struct Index { State list: string[] [一, 二, 三] build() { Column() { ForEach(this.list, (item: string) { Text(item) ← 每个元素绑一个 Text 循环项 }, (item: string) item) Button(加元素) .onClick(() { this.list.push(新) }) ← 数组变 ForEach 自动重渲循环项 } } } // 数组加元素ForEach 自动加循环项Text新不用 for 循环重新迭代 // 数组删元素ForEach 自动删循环项不用 for 循环重新迭代数组变自动重渲ForEach 贬饰器list数组变触发自动重渲循环项——push 加元素自动加循环项Text’新’splice 删元素自动删循环项。数组变不用 for 循环重新迭代ForEach 贬饰器绑的循环项按数组变自动重渲。根因不是 for 语句迭代是渲染树循环项按数组变自动重渲。机制 3keyGenerator 绑元素键值——循环项追踪增删改ForEach 贬饰器keyGenerator 绑元素键值——给每个元素绑唯一键值追踪循环项增删改Entry Component struct Index { State list: string[] [一, 二, 三] build() { Column() { ForEach(this.list, (item: string) { Text(item) }, ← itemGenerator 绑循环项内容 (item: string) item ← keyGenerator 绑元素键值一/二/三) ) } } } // keyGenerator 绑键值一 → key一二 → key二三 → key三 // 数组变按 key 追踪加 新 → 加 key新 循环项删 一 → 删 key一 循环项keyGenerator 绑键值追踪ForEach 第三参 keyGenerator 给每个元素绑唯一键值——数组变按 key 追踪循环项增删改。加元素按 key 加循环项删元素按 key 删循环项改元素按 key 改循环项。不用 for 循环遍历比对keyGenerator 绑的键值让 ForEach 按数组变精准追踪循环项。根因不是 for 遍历是 keyGenerator 绑键值追踪。机制 4for 语句报错边界——build 不是迭代执行流是渲染树build 里 for语句报错——build 是渲染树不是迭代执行流for 语句是运行时迭代跟渲染树绑定冲突Entry Component struct Index { State list: string[] [一, 二, 三] build() { Column() { // ❌ for 循环报错build 不是迭代执行流 // for (let i 0; i this.list.length; i) { Text(this.list[i]) } ← for 语句报错 // ✅ ForEach 贬饰器合法build 是渲染树ForEach 绑循环项 ForEach(this.list, (item: string) { Text(item) ← ForEach 贬饰器绑渲染树循环项合法 }, (item: string) item) } } } // for 语句报错根因build 是渲染树不是迭代执行流for 语句运行时迭代跟渲染树绑定冲突 // ForEach 贬饰器合法根因ForEach 编译期绑渲染树循环项运行时按数组变重渲不迭代for 语句报错边界build 里 for 语句报错根因是 build 是渲染树不是迭代执行流——for 语句是运行时迭代执行跟渲染树编译期绑定冲突报错。ForEach 贬饰器合法根因是 ForEach 编译期绑渲染树循环项运行时按数组变重渲不冲突。根因不是语法区别是渲染树循环项绑定 vs for 语句迭代执行的边界冲突。三、真机配图for 循环报错 vs ForEach 循环渲染装饰器对比证据初始态list一二三、ForEach 渲染 3 元素均循环渲染边界初始值点调两按钮后加元素 ForEach 自动加循环项、删首元素 ForEach 自动删循环项均循环渲染边界对比证据齐对比证据点加元素按钮后 ForEach 自动加循环项渲染多了新元素点删首元素按钮后 ForEach 自动删循环项删了首元素剩余重渲。ForEach 不是 for 循环是装饰器绑渲染树循环项——ForEach 贬饰器编译期绑渲染树循环项数组变自动重渲不用 for 语句迭代。for 语句报错根因是 build 是渲染树不是迭代执行流for 语句运行时迭代跟渲染树绑定冲突。四、真解法ForEach 循环渲染的三个场景场景 1数组循环渲染90% 场景首选数组变自动重渲Entry Component struct Index { State list: string[] [一, 二, 三] // ForEach 渲染源 build() { Column() { ForEach(this.list, (item: string) { Text(item) ← 每个元素绑一个 Text 循环项 }, (item: string) item) Button(加) .onClick(() { this.list.push(新) }) ← 数组变 ForEach 自动重渲 } } }为哈能跑ForEach 数组循环渲染——每个元素绑一个循环项数组变自动重渲。首选这个90% 的场景数组循环渲染用 ForEach 就够。要写「数组循环渲染每个元素」时用这个——不用 for/map 绕ForEach 贬饰器直接绑循环项按数组变自动重渲。场景 2对象数组循环渲染itemGenerator 渲染对象字段interface IUser { id: number name: string } Entry Component struct Index { State users: IUser[] [ { id: 1, name: 张三 } as IUser, { id: 2, name: 李四 } as IUser ] build() { Column() { ForEach(this.users, (user: IUser) { Text(id${user.id}, name${user.name}) ← itemGenerator 渲染对象字段 }, (user: IUser) ${user.id}) ← keyGenerator 绑 id 键值 Button(加用户) .onClick(() { this.users.push({ id: 3, name: 王五 } as IUser) }) } } }为哈能跑ForEach 对象数组循环渲染——itemGenerator 渲染对象字段id/namekeyGenerator 绑 id 键值追踪。要写「对象数组循环渲染字段」时用这个——itemGenerator 渲对象字段keyGenerator 绑 id 精准追踪增删改不用 for 循环遍历对象。场景 3ForEach if 嵌套循环渲染循环项里嵌条件渲染Entry Component struct Index { State list: string[] [一, 二, 三] State showAll: boolean true build() { Column() { ForEach(this.list, (item: string) { if (this.showAll) { ← 循环项里嵌 if 条件渲染 Text(全显${item}) } else { Text(简显${item[0]}) ← 显示首字符 } }, (item: string) item) Button(切显) .onClick(() { this.showAll !this.showAll }) } } }为哈能跑ForEach if 嵌套循环渲染——循环项里嵌 if 条件渲染循环条件组合。要写「循环项里有条件分支」时用这个——ForEach 绑循环项if 贬饰器嵌在循环项里绑条件分支循环条件组合渲染不用 map三元嵌套。五、一句话哲学build 里 for 不是循环是装饰器绑渲染树循环项不是迭代执行。ArkUI 的 build 里 ForEach 贬饰器编译期绑渲染树循环项每个元素绑一个循环项 keyGenerator 绑键值追踪增删改运行时按数组变自动重渲不用 for 语句迭代。根因不是语法区别是渲染树循环项绑定 vs for 语句迭代执行的边界——ForEach 贬饰器编译期绑循环项合法 数组变自动重渲不是 for 迭代 keyGenerator 绑键值追踪不用 for 遍历 for 语句报错边界build 是渲染树不是迭代执行流。对比 React JSX for 循环不能写要 map 绕ArkTS ForEach 贬饰器直接绑循环项。渲染哲学阶段收官串讲build() 重渲边界篇 60State 参只刷依赖 UI 不刷全 build→ if 条件渲染边界篇 61if 贬饰器绑渲染树分支不是 if 语句→ ForEach 循环渲染边界篇 62ForEach 贬饰器绑渲染树循环项不是 for 循环——三篇讲清 ArkUI 渎染哲学渲染树绑定build 编译期记依赖/if 绑分支/ForEach 绑循环项根因都是编译期绑渲染树不是运行时语句执行。为啥 if/for 语句报错build 是渲染树不是语句执行流——if/for 语句是运行时跳转/迭代执行跟渲染树编译期绑定冲突报错。if/ForEach 贬饰器是编译期绑渲染树分支/循环项运行时按状态/数组变重选重渲不冲突。根因不是语法限制是渲染树绑定 vs 语句执行的边界冲突。下一阶段预告ArkUI 组件设计篇 63 收尾——从渲染哲学渲染树绑定到组件设计Builder/BuilderParam/Styles/Extend 复用边界换角度讲 ArkUI 组件复用哲学系列收官。能力系列回链能力系列篇本文进阶点篇 18 循环渲染用法ForEach 循环渲染边界根因绑渲染树循环项篇 17 条件渲染用法上一篇if 条件渲染边界根因篇 19 Builder 用法下篇预告组件复用边界根因真机 demo 完整代码// 篇 62 demofor 循环报错 vs ForEach 循环渲染装饰器对比 // 对比build 里 for 循环报错 vs ForEach(...){} 循环渲染合法 Entry Component struct Index { State list: string[] [一, 二, 三] // ✅ ForEach 渲染源 State log: string (未操作) build() { Column({ space: 12 }) { Text(篇 62 配图for 循环报错 vs ForEach 循环渲染装饰器对比) .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 }) Text(build 里 for 循环报错 vs ForEach(...){} 循环渲染合法对比证据) .fontSize(12).fontColor(#888).margin({ bottom: 16 }) Column({ space: 6 }) { Text(list 镜度 ${this.list.length}).fontSize(15).fontWeight(FontWeight.Bold) Text(日志${this.log}).fontSize(12).fontColor(#333).margin({ top: 4 }) } .width(92%).padding(12).backgroundColor(#f5f5f5).borderRadius(8) // ✅ ForEach 循环渲染装饰器合法不是 for 循环 Text(ForEach 渲染 list 元素).fontSize(13).fontColor(#2563eb) ForEach(this.list, (item: string) { Text(✅ ForEach 渲染${item}).fontSize(14).fontColor(#2563eb) }, (item: string) item) // ❌ build 里 for 循环报错对比证据注释掉避编译炸 // for (let i 0; i this.list.length; i) { Text(this.list[i]) } ← for 语句报错 Button(加元素ForEach 自动刷循环渲染) .width(92%).height(44).fontSize(14) .onClick(() { this.list.push(新${this.list.length 1}) // ✅ 改 list ForEach 自动刷循环渲染 this.log 加元素list 镜度${this.list.length}ForEach 自动刷循环渲染不是 for 循环 }) Button(删首元素ForEach 自动重渲剩余) .width(92%).height(44).fontSize(14) .onClick(() { if (this.list.length 0) { this.list.splice(0, 1) // ✅ 删首元素 ForEach 自动重渲剩余 this.log 删首元素list 镜度${this.list.length}ForEach 自动重渲剩余 } }) } .width(100%).height(100%).alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住build 里 for 不是循环是装饰器绑渲染树循环项——ForEach 贬饰器编译期绑渲染树循环项每个元素绑一个循环项 keyGenerator 绑键值追踪增删改运行时按数组变自动重渲不用 for 语句迭代。根因不是语法区别是渲染树循环项绑定 vs for 语句迭代执行的边界——ForEach 贬饰器编译期绑循环项合法 数组变自动重渲不是 for 迭代 keyGenerator 绑键值追踪不用 for 遍历 for 语句报错边界build 是渲染树不是迭代执行流。数组循环渲染用 ForEach 绑循环项首选对象数组循环渲染用 itemGenerator 渲字段 keyGenerator 绑 idForEach if 嵌套用循环项里嵌条件渲染。ForEach 贬饰器绑渲染树循环项不是 for 循环是 ArkUI 渎染哲学核心收尾