ArkTS 进阶之道14Builder 组件复用边界——为啥 build 不能当函数调本文是「ArkTS 进阶之道」系列第 14 篇开「ArkUI 组件设计」阶段系列收官。上一阶段讲渲染哲学篇 60-62渲染树绑定build/if/ForEach 绑渲染树节点/分支/循环项。本文讲组件复用Builder 荬饰器复用 UI 片段——根因在绑渲染树节点不是返值函数Builder 荬饰器绑渲染树节点复用 UIbuild 不能当函数调返 UI。能力系列篇 19 讲过 Builder 怎么用本文讲为哈 Builder 复用合法 build 当函数调报错——根因在渲染树节点绑定不是返值函数。一、开篇Builder 不是返值函数是绑渲染树节点的复用荬饰器你写 TypeScript/React 时UI 片段复用是「魔法」React 要定义子组件/函数组件返 JSX// React 函数组件返 JSX 复用 UI 片段 function MyTextBlock({ label, value }: { label: string; value: number }) { return ( View Text{label}/Text Text{value}/Text /View ) ← 返 JSX 值是返值函数 } function Component() { return ( View MyTextBlock label复用1 value{1} / ← 调函数组件返 JSX 嵌 MyTextBlock label复用2 value{2} / /View ) } // React 用函数组件返 JSX 复用是返值函数魔法你写鸿蒙 ArkTS 时Builder绑渲染树节点复用——不用返值函数// ArkTS Builder 绑渲染树节点复用 UI 片段 Entry Component struct Index { Builder MyTextBlock(label: string, value: number) { ← Builder 荬饰器绑渲染树节点 Column() { Text(label) Text(${value}) } } build() { Column() { this.MyTextBlock(复用1, 1) ← 调 Builder 绑渲染树节点1不返值 this.MyTextBlock(复用2, 2) ← 调 Builder 绑渲染树节点2不返值 } } } // Builder 绑渲染树节点复用 UI 片段不是返值函数不返 JSX魔法 vs 荬饰器的区别React 把 UI 复用当「魔法」函数组件返 JSX 值ArkTS 把 Builder 当「绑渲染树节点荬饰器」不返值绑渲染树节点复用 UI。根因不是魔法是渲染树节点绑定——Builder 调用不返 UI 值是绑渲染树节点的复用荬饰器。二、根因Builder 的渲染树节点绑定复用机制鸿蒙 ArkUI 的 Builder 是渲染树节点绑定复用——编译期把 Builder 方法绑成渲染树节点调用点嵌节点复用 UI不是返值函数来自三重绑定机制。机制 1Builder 编译期绑渲染树节点——不是返值函数Builder 荬饰器编译期绑渲染树节点——把 Builder 方法编成渲染树的可复用节点不是返 UI 值的函数Entry Component struct Index { Builder MyTextBlock(label: string, value: number) { ← Builder 荬饰器编译期绑渲染树节点 Column() { Text(label) Text(${value}) } } build() { Column() { this.MyTextBlock(复用1, 1) ← 调用点嵌渲染树节点1绑节点不返值 this.MyTextBlock(复用2, 2) ← 调用点嵌渲染树节点2绑节点不返值 } } } // 编译期Builder MyTextBlock 绑成渲染树可复用节点模板 // 调用点this.MyTextBlock(...) 嵌渲染树节点实例不返 UI 值编译期绑节点BuilderMyTextBlock荬饰器编译期把方法绑成渲染树的可复用节点模板——调用this.MyTextBlock(复用1, 1)在调用点嵌渲染树节点实例1参数 label‘复用1’/value1调用this.MyTextBlock(复用2, 2)嵌节点实例2。绑节点不返 UI 值不是返值函数。根因不是返值是渲染树节点绑定复用。机制 2调用点嵌节点复用 UI——多个调用点绑多个节点实例Builder 荬饰器调用点嵌节点复用 UI——多个调用点各嵌渲染树节点实例复用同一 Builder 模板Entry Component struct Index { State count: number 0 Builder MyTextBlock(label: string, value: number) { ← Builder 模板复用源 Column() { Text(label) Text(值 ${value}) } } build() { Column() { this.MyTextBlock(复用1, this.count) ← 调用点1 嵌节点实例1label复用1/valuecount this.MyTextBlock(复用2, this.count * 10) ← 调用点2 嵌节点实例2label复用2/valuecount*10 Button(改 count) .onClick(() { this.count }) ← count 参两个节点实例都刷绑节点追踪 } } } // 调用点1/调用点2 各嵌节点实例复用同一 Builder MyTextBlock 模板 // count 参两个节点实例都刷绑渲染树节点追踪依赖调用点嵌节点复用BuilderMyTextBlock调用点1嵌节点实例1label‘复用1’/valuecount调用点2嵌节点实例2label‘复用2’/valuecount*10——两个实例复用同一 Builder 模板参数不同显不同 UI。count 参两个节点实例都刷绑渲染树节点追踪依赖篇 60 重渲边界机制。根因不是返值拼接是调用点嵌节点复用。机制 3build 当函数调报错边界——返值表达式跟渲染树绑定冲突build 里Builder 不能当函数调返值——Builder 绑渲染树节点不返 UI 值当函数调返值表达式跟渲染树绑定冲突报错Entry Component struct Index { Builder MyTextBlock(label: string, value: number) { Column() { Text(label); Text(${value}) } } build() { Column() { // ❌ Builder 不能当函数调返值返值表达式跟渲染树绑定冲突 // const ui1 this.MyTextBlock(复用1, 1) ← 当函数调返值报错 // return this.MyTextBlock(复用1, 1) this.MyTextBlock(复用2, 2) ← 返值拼接报错 // ✅ Builder 调用点嵌节点不返值绑渲染树节点 this.MyTextBlock(复用1, 1) ← 调用点嵌节点合法不返值 } } } // Builder 当函数调返值报错根因Builder 绑渲染树节点不返 UI 值 // 返值表达式const ui / return / 拼接跟渲染树绑定冲突报错build 当函数调报错边界Builder 当函数调返值const ui / return / 拼接报错根因是 Builder 绑渲染树节点不返 UI 值——返值表达式要求返 UI 值拼接跟渲染树节点绑定冲突报错。Builder 调用点嵌节点不返值合法根因不是语法限制是渲染树节点绑定 vs 返值函数的边界冲突。三、真机配图Builder 组件复用边界——绑渲染树节点复用 UI 片段初始态Builder 复用1 显 count0、复用2 显 count*100、分割线渲染均组件复用边界初始值点调按钮后Builder 复用1/复用2 图刷了 count1/10、分割线渲染均组件复用边界对比证据齐对比证据点改 count 按钮后 Builder 复用1 显 count1、复用2 显 count*1010两个调用点嵌的节点实例都刷了绑渲染树节点追踪依赖。Builder 不是返值函数是绑渲染树节点的复用荬饰器——Builder 荬饰器编译期绑渲染树节点模板调用点嵌节点实例复用 UI 不返值。build 当函数调返值报错根因是 Builder 绑渲染树节点不返 UI 值返值表达式跟渲染树绑定冲突。四、真解法Builder 组件复用的三个场景场景 1Builder 带参数复用 UI 片段90% 场景首选多调用点复用Entry Component struct Index { State count: number 0 Builder MyTextBlock(label: string, value: number) { ← Builder 带参数复用模板 Column() { Text(label) Text(值 ${value}) } } build() { Column() { this.MyTextBlock(复用1, this.count) ← 调用点1 嵌节点实例1 this.MyTextBlock(复用2, this.count * 10) ← 调用点2 嵌节点实例2 Button(改 count) .onClick(() { this.count }) ← count 参两个节点实例都刷 } } }为哈能跑Builder 带参数复用 UI 片段——多个调用点嵌节点实例参数不同显不同 UI。首选这个90% 的场景 UI 片段复用用 Builder 带参数就够。要写「同一 UI 片段多调用点复用显不同参数」时用这个——不用子组件/函数组件Builder 荬饰器绑节点模板调用点嵌实例。场景 2Builder 无参数复用分割线/装饰 UI 片段复用Entry Component struct Index { Builder MyDivider() { ← Builder 无参数复用模板 Divider().color(#eee).strokeWidth(1).margin(4) } build() { Column() { Text(区块1) this.MyDivider() ← 调用点1 嵌分割线节点1 Text(区块2) this.MyDivider() ← 调用点2 嵌分割线节点2 Text(区块3) } } }为哈能跑Builder 无参数复用——分割线/装饰 UI 片段多调用点嵌节点复用。要写「装饰 UI 片段分割线/间距/装饰块多调用点复用」时用这个——无参数 Builder 绑节点模板调用点嵌实例复用装饰 UI 不重复写。场景 3Builder 条件渲染嵌套复用 UI 片段里嵌条件分支Entry Component struct Index { State show: boolean true State count: number 0 Builder MyStatusBlock(label: string, value: number) { Column() { Text(label) if (value 0) { ← Builder 里嵌 if 条件渲染 Text(正数${value}) } else { Text(非正) } } } build() { Column() { this.MyStatusBlock(状态1, this.count) ← 调用点1 嵌节点实例1含条件分支 this.MyStatusBlock(状态2, this.count - 5) ← 调用点2 嵌节点实例2含条件分支 Button(改 count) .onClick(() { this.count }) } } }为哈能跑Builder 条件渲染嵌套——Builder 里嵌 if 条件渲染分支复用 UI 片段含条件分支。要写「复用 UI 片段里有条件分支」时用这个——Builder 绑节点模板嵌 if 荬饰器绑分支调用点嵌实例含条件分支按参数选分支渲染。五、一句话哲学Builder 不是返值函数是绑渲染树节点的复用荬饰器。ArkUI 的 Builder 荬饰器编译期绑渲染树节点模板调用点嵌节点实例复用 UI 不返值。根因不是返值函数是渲染树节点绑定——Builder 编译期绑节点渲染树可复用节点模板 调用点嵌节点实例多个调用点复用同一模板 build 当函数调报错边界Builder 绑节点不返 UI 值返值表达式跟渲染树绑定冲突。对比 React 函数组件返 JSX 值ArkTS Builder 绑渲染树节点不返值。渲染哲学→组件设计过渡渲染哲学篇 60-62讲渲染树绑定build/if/ForEach 绑节点/分支/循环项——都是「渲染树结构绑定」。组件设计篇 63讲 Builder 绑渲染树节点复用——是「渲染树节点复用绑定」。Builder 补上渲染哲学没讲的复用不只绑渲染树结构还能绑可复用节点模板调用点嵌实例。从「结构绑定」到「节点复用」扩展根因都是编译期绑渲染树。系列收官预告本文是 ArkTS 进阶之道系列最后开的新阶段——「ArkUI 组件设计」阶段。下篇篇 64如续讲 BuilderParam/Styles/Extend 复用边界或系列就此打住。五阶段哲学体系类型哲学50-52→ 作用域哲学53-55→ 状态哲学56-59→ 渎染哲学60-62→ 组件设计63讲清 ArkTS/ArkUI 进阶哲学。能力系列回链能力系列篇本文进阶点篇 19 Builder 用法Builder 组件复用边界根因绑渲染树节点篇 18 循环渲染用法上一篇ForEach 循环渲染边界根因篇 13 State 基础用法状态哲学State 赋值就刷 UI 依赖追踪真机 demo 完整代码// 篇 63 demoBuilder 组件复用 vs build 当函数调报错对比 // 对比Builder 荬饰器复用合法 vs 普通方法返 UI 报错 Entry Component struct Index { State count: number 0 State log: string (未操作) // ✅ Builder 荬饰器复用 UI 片段绑渲染树节点 Builder MyTextBlock(label: string, value: number) { Column({ space: 4 }) { Text(label).fontSize(12).fontColor(#888) Text(值 ${value}).fontSize(14).fontWeight(FontWeight.Bold).fontColor(#2563eb) } .alignItems(HorizontalAlign.Center) } // ✅ Builder 带参数复用多个调用点绑多个渲染树节点 Builder MyDivider() { Divider().color(#eee).strokeWidth(1).margin(4) } build() { Column({ space: 12 }) { Text(篇 63 配图Builder 组件复用边界) .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 }) Text(Builder 复用 UI 片段 vs build 不能当函数调对比证据) .fontSize(12).fontColor(#888).margin({ bottom: 16 }) Column({ space: 6 }) { Text(count ${this.count}).fontSize(15).fontWeight(FontWeight.Bold) Text(日志${this.log}).fontSize(12).fontColor(#333).margin({ top: 4 }) } .width(92%).padding(12).backgroundColor(#f5f5f5).borderRadius(8) // ✅ Builder 调用复用 UI 片段绑渲染树节点 Text(Builder 复用调用).fontSize(13).fontColor(#2563eb) this.MyTextBlock(复用1, this.count) // ✅ Builder 调用绑渲染树节点1 this.MyDivider() // ✅ Builder 分割线复用 this.MyTextBlock(复用2, this.count * 10) // ✅ Builder 再调用绑渲染树节点2 // ❌ build 不能当普通函数调返 UI对比证据注释掉避编译炸 // this.MyTextBlock(复用1, this.count) this.MyTextBlock(复用2, this.count * 10) // 普通方法返 UI 报错build 是渲染树不是返值表达式 Button(改 countBuilder 复用点都刷) .width(92%).height(44).fontSize(14) .onClick(() { this.count // ✅ count 变 Builder 复用点都刷绑渲染树节点追踪 this.log count${this.count}Builder 复用点都刷绑渲染树节点追踪 }) } .width(100%).height(100%).alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住Builder 不是返值函数是绑渲染树节点的复用荬饰器——Builder 荬饰器编译期绑渲染树节点模板调用点嵌节点实例复用 UI 不返值。根因不是返值函数是渲染树节点绑定——Builder 编译期绑节点渲染树可复用节点模板 调用点嵌节点实例多个调用点复用同一模板 build 当函数调报错边界Builder 绑节点不返 UI 值返值表达式跟渲染树绑定冲突。Builder 带参数复用 UI 片段用多调用点嵌实例显不同参数首选Builder 无参数复用用装饰 UI 片段分割线/间距Builder 条件渲染嵌套用复用 UI 片段含条件分支。绑渲染树节点复用不返值是 ArkUI 组件设计哲学核心