鸿蒙应用开发UI基础第二十六节:轻量级UI元素@Builder与@LocalBuilder区别示例演示
【学习目标】理解 Builder 设计初衷明确与 Component 核心差异掌握 Builder 两种定义方式、参数传递按值/按引用规则掌握 Builder 高级场景嵌套、this指向实战用法掌握 BuilderParam UI 插槽原理与使用方法实现组件 UI 内容灵活定制掌握 LocalBuilder 用法、参数传递与限制理解其固定this、维持组件父子关系的核心特性能够清晰对比 Builder 与 LocalBuilder在实际开发中正确选型。一、Builder 核心认知1.1 什么是 BuilderBuilder用于封装一段UI结构仅负责UI渲染无独立实例、无状态、无生命周期不会加入组件树编译阶段会直接内联展开是组件内/全局复用UI片段的最优轻量方案。1.2 为什么需要 Builder我们已经掌握Component封装可复用UI适用于独立功能、带业务逻辑、带生命周期的页面/组件。但开发中存在大量纯UI结构、无状态、无业务、无生命周期的复用片段例如 列表内固定骨架图页面内重复UI区块。这类片段不需要独立实例、状态、生命周期使用Component会造成额外实例开销粒度过细。因此 ArkTS 提供最轻量UI复用方案Builder 自定义构建函数专注细粒度UI复用几乎无额外性能消耗。1.3 Builder 与 Component 核心区别特性BuilderComponent本质UI构建函数独立自定义组件实例无实例有独立组件实例状态支持不能声明State支持State、Prop等状态生命周期无生命周期拥有完整生命周期调用方式直接调用作为组件使用适用场景纯UI结构复用功能组件/页面/业务模块刷新范围随所属组件整体刷新无独立刷新可独立响应式刷新调用位置只能在build()内部使用任意生命周期与场景均可使用1.4 Builder 两种定义形式组件内 Builder仅当前组件内使用可直接访问组件成员变量与状态跟随组件刷新而刷新全局 Builder整个工程通用不依赖任何组件实例不能访问this所有数据必须通过参数传递。二、项目工程本节工程BuilderDemo基于 API 20所有案例页面完整对应如下BuilderDemo/ ├── AppScope/ ├── entry/ │ ├── src/main/ets │ │ ├── entryability/EntryAbility.ets │ │ ├── pages/ │ │ │ ├── Index.ets # 入口页面跳转 │ │ │ ├── InnerBuilderPage.ets # 组件内Builder │ │ │ ├── GlobalBuilderPage.ets # 全局Builder │ │ │ ├── BuilderParamPage.ets # BuilderParam UI插槽 │ │ │ ├── AdvancedBuilderPage.ets # 高级嵌套、this指向、Binding │ │ │ └── LocalBuilderPage.ets # LocalBuilder 专属案例三、Index.ets 入口页面importrouterfromohos.router;// 定义页面配置接口interfacePageConfig{title:string;url:string;}Entry Component struct Index{pageList:PageConfig[][{title:1. 组件内 Builder,url:pages/InnerBuilderPage},{title:2. 全局 Builder,url:pages/GlobalBuilderPage},{title:3. BuilderParam 插槽,url:pages/BuilderParamPage},{title:4. this指向问题,url:pages/AdvancedBuilderPage},{title:5. LocalBuilder,url:pages/LocalBuilderPage}];// 自定义Builder按钮封装BuilderbuilderButton(title:string,onClick:()void){Button(title).width(280).onClick(onClick);}build(){Column({space:20}){Text(Builder LocalBuilder 实战).fontSize(26).fontWeight(FontWeight.Bold);// ForEach 循环渲染按钮ForEach(this.pageList,(item:PageConfig){this.builderButton(item.title,(){router.pushUrl({url:item.url});});});}.width(100%).height(100%).justifyContent(FlexAlign.Center);}}四、组件内 Builder 示例演示4.1 规则说明组件内 Builder 直接访问 this 状态变量时会跟随组件整体刷新不受传参规则限制自动刷新Builder 默认按值传递参数传递基础类型、对象变量、多参数时外部状态更新不会触发 Builder 刷新Builder 实现响应式刷新的标准方式使用对象字面量形式传入参数可建立响应式关联状态更新自动刷新多层 Builder 嵌套时每一层都必须使用对象字面量传参才能保证全链路刷新禁止在 Builder 内部修改传入的参数对象会导致运行异常从 API version 20 开始可通过 UIUtils.makeBinding()、Binding / MutableBinding 实现 Builder 内状态变量刷新使用 UIUtils.makeBinding() 包装读状态回调支持 Builder 内 UI 自动刷新额外传入写状态回调可将 Builder 内部的参数修改同步到外部组件。4.2 说明Builder被用来展示组件不会参与动态UI刷新。组件中值的变化是通过使用装饰器的特性监听到值的改变触发的UI刷新而不是通过Builder的能力触发的。4.3 示例代码示例 1无参 Builder 直接访问 this → 自动刷新classUserInfo{name:stringage:number0}Entry Component struct InnerBuilderPage{State message:string组件内 Builder 实战State count:number0State user:UserInfo{name:Tom,age:25}BuilderbuildTitle(){Text(this.message).fontSize(24).fontWeight(FontWeight.Bold).fontColor(#333333)}build(){Column({space:15}){this.buildTitle()Button(更新标题).onClick((){this.message组件内 Builder 实战-更新})}.width(100%)}}示例 2按值传递 → 不刷新BuilderbuildButton(label:string){Text(基础类型传参 → 不刷新label)}// 调用this.buildButton(count${this.count})示例 3直接传递对象变量按值传递 → 不刷新BuilderbuildUserCardByObj(params:UserInfo){Text(姓名${params.name}年龄${params.age})}// 调用this.buildUserCardByObj(this.user)示例 4多参数传递按值传递 → 不刷新BuilderbuildUserCardByValue(name:string,age:number){Text(姓名${name}年龄${age})}// 调用this.buildUserCardByValue(this.user.name,this.user.age)示例 5对象字面量 → 按引用传递 → 刷新BuilderbuildUserCardByRef(params:UserInfo){Text(姓名${params.name}年龄${params.age})}// 调用this.buildUserCardByRef({name:this.user.name,age:this.user.age})示例 6嵌套 Builder 子组件刷新对比Component struct ChildComponent1{Prop info:UserInfonewUserInfo();build(){Row(){Text(ChildComponent1不刷新${this.info.name}${this.info.age}).fontSize(20).fontWeight(FontWeight.Bold)}}}Component struct ChildComponent2{Prop childName:string;Prop childAge:number0;build(){Row(){Text(ChildComponent2刷新${this.childName}${this.childAge}).fontSize(20).fontWeight(FontWeight.Bold)}}}BuilderparentBuilder(params:UserInfo){Text(parentBuilder刷新${params.name}${params.age}).fontSize(20).fontWeight(FontWeight.Bold)ChildComponent1({info:params})// 不刷新ChildComponent2({childName:params.name,childAge:params.age})// 刷新}示例 7错误示范Builder 内部修改参数BuildererrorModifyParam(params:UserInfo){Button(修改).onClick((){params.name错误修改// 运行异常})}示例 8API20 新特性 → Binding 绑定传参可修改可刷新BuilderModifyParam(params:BindingUserInfo){Text(姓名${params.value.name})Button(内部修改).onClick((){params.value.name新名字})}// 调用this.ModifyParam(UIUtils.makeBinding(()this.user,(user){this.useruser}))运行结果五、全局 Builder 实战5.1 核心功能定义全局通用UI片段多页面共享无组件依赖不绑定this所有数据必须通过参数传递。5.2 规则必须加function关键字跨页面使用需要加export5.3 示例代码interfaceGlobalInfo{globalContent:string callback?:()void}BuilderexportfunctionglobalButton(globalInfo:GlobalInfo){Button(globalInfo.globalContent).width(180).height(46).backgroundColor(#34C85E).margin(5).onClick(globalInfo?.callback)}BuilderexportfunctionglobalCard(title:string,content:string){Column(){Text(title).fontSize(20).fontWeight(FontWeight.Bold)Text(content).fontSize(16).margin({top:10})}.width(100%).padding(20).backgroundColor(#fff).borderRadius(12).margin(10)}Entry Component struct GlobalBuilderPage{State content:string全局复用跨页面统一样式State globalInfo:GlobalInfo{globalContent:全局按钮1}build(){Column({space:20}){globalCard(全局 Builder 实战,this.content)// 只能通过引用传递 一一传递对应参数 否则无法刷新。globalButton({globalContent:this.globalInfo.globalContent,callback:(){this.globalInfo.globalContent全局按钮点击更新}})}.width(100%).padding(20)}}5.4 运行结果六、BuilderParam UI插槽6.1 核心功能BuilderParam用于接收外部传入的Builder函数实现UI插槽slot能力让组件结构固定、内容可灵活定制。6.2 说明BuilderParam 是组件属性装饰器专门用于接收 Builder 函数类型的参数。使用场景父组件向子组件传递一段UI结构子组件只负责布局不关心具体内容。6.3 示例代码Component struct CardComponent{BuilderParam header?:()voidBuilderParam content?:()voidbuild(){Column(){if(this.header){this.header()}Divider().margin(10).color(Color.Red).strokeWidth(2)if(this.content){this.content()}}.width(100%).backgroundColor(#fff).padding(20).borderRadius(12)}}Entry Component struct BuilderParamPage{BuildercustomHeader(){Text(自定义头部).fontSize(22).fontWeight(FontWeight.Bold)}BuildercustomContent(){Text(这是插槽内容灵活传入任意UI)Button(插槽内按钮).width(150).margin(10)}build(){Column({space:20}){CardComponent({header:this.customHeader,content:this.customContent})}.padding(20)}}6.4 运行结果七、关于this 指向重点我们在使用 Builder BuilderParam 跨组件传递时会出现一个非常关键的问题this 指向会变导致UI刷新不符合预期。7.1 this 指向核心规则Builder 直接作为引用传递时this 由「调用它的组件」决定。箭头函数没有自己的 this会继承外层作用域的 this。在BuilderParam场景直接传递customBuilderParam: this.componentBuilder→ 函数在 Child 里执行this 指向 Child。箭头函数包裹customBuilderParam: () { this.componentBuilder() }→ 继承父组件 thisthis 指向 Parent。7.2 示例代码/** * 问题Builder BuilderParam this 指向问题 * 页面运行结果Parent Child Parent * 核心规则 * 1. 直接传递函数引用 → this 由【调用者】决定 * 2. 箭头函数包裹 → this 继承【定义时的组件】 */Component struct ChildComponent{label:stringChild;// 默认占位Builder无实际UIBuildercustomBuilder(){}BuildercustomChangeThisBuilder(){}// 接收外部传入的Builder函数BuilderParamcustomBuilderParam:()voidthis.customBuilder;BuilderParamcustomChangeThisBuilderParam:()voidthis.customChangeThisBuilder;build(){Row({space:15}){// 执行外部传入的构建函数this.customBuilderParam()this.customChangeThisBuilderParam()}}}Entry Component struct AdvancedBuilderPage{label:stringParent;// 普通 Builderthis 由【运行时调用者】决定不固定BuildercomponentBuilder(){Text(${this.label}).fontSize(26).fontWeight(FontWeight.Bold)}build(){Column({space:20}){Text(运行结果).fontSize(22)Row({space:15}){// 1️⃣ 父组件自己调用 → this 指向Parent → 显示 Parentthis.componentBuilder()ChildComponent({// 2️⃣ 直接传递函数引用函数在子组件内执行 → this 指向Child → 显示 ChildcustomBuilderParam:this.componentBuilder,// 3️⃣ 箭头函数包裹继承父组件this → this 固定指向Parent → 显示 ParentcustomChangeThisBuilderParam:(){this.componentBuilder()}})}}.width(100%).padding(30)}}7.3 运行结果最终页面显示Parent Child Parent7.4 一句话总结this 是函数执行时的上下文对象它的指向不是在函数定义时确定的而是在函数调用时才最终确定。普通函数谁调用this 就指向谁默认规则也是最容易错乱的场景箭头函数没有自己的 this继承外层作用域的 this永远不会错乱优先推荐八、LocalBuilder 装饰器Builder 在跨组件传递时this 会被改变这会导致严重问题UI 刷新不符合预期为了彻底解决这个问题ArkTS提供了LocalBuilder。LocalBuilder可以固定this指向当前8.1 设计目的LocalBuilder 拥有和局部 Builder完全一样的UI构建能力但可以固定 this 指向保证组件父子关系不变保证状态管理父子关系不变从根源解决 Builder 的上下文错乱问题。8.2 核心特性只能在组件内部定义不支持全局this永远指向声明它的组件不会被任何方式改变跨组件传递时组件归属固定不会被修改调用方式与 Builder 完全一致this.xxx()。8.3 LocalBuilder与Builder对比示例Component struct Child{label:stringChild;BuilderParam customBuilderParam?:()void;build(){if(this.customBuilderParam){this.customBuilderParam()}else{Text(BuilderParam undefined)}}}Entry Component struct LocalBuilderPage{label:stringParent;// Builderthis 指向调用方 → ChildBuildercomponentBuilder(){Text(this.label)}// LocalBuilderthis 永远指向 ParentLocalBuildercomponentLocalBuilder(){Text(this.label)}build(){Column({space:20}){// 显示 ChildChild({customBuilderParam:this.componentBuilder})// 显示 ParentChild({customBuilderParam:this.componentLocalBuilder})}}}8.4 限制条件只能在组件内声明不允许全局不能与其他任何装饰器一起使用不能修饰静态方法函数内部不允许修改参数作为参数传递时推荐直接传函数this.func或箭头函数() { this.func() }this指向都不会变.禁止直接传执行结果会导致布局错乱。8.5 参数传递规则与 Builder 一致按值传递基础类型、多参数、直接对象 →不刷新按引用传递单个对象字面量 →可刷新按回调传递API20 使用 UIUtils.makeBinding →可刷新可修改注意子组件调用父组件的 LocalBuilder 并传参时子组件状态变化不会触发LocalBuilder 刷新推荐LocalBuilder 内直接访问父组件自身状态。九、代码仓库工程名称BuilderDemo仓库地址https://gitee.com/HarmonyOS-UI-Basics/harmony-os-ui-basics.git十、下节预告下一节我们将深入学习鸿蒙UI样式复用三大核心方案掌握 Styles 通用样式复用实现多组件基础样式统一掌握 Extend 组件专属样式扩展支持参数与私有属性掌握 stateStyles 多态状态样式实现按压/禁用/选中自动切换清晰区分三者适用场景写出简洁、可维护、高性能样式代码
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2429061.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!