鸿蒙next开发:ArkUI自定义组件-@Reusable装饰器:组件复用_鸿蒙arkui 组件复用demo
往期鸿蒙全套实战文章必看:(附带鸿蒙全栈学习资料)
-
鸿蒙开发核心知识点,看这篇文章就够了
-
最新版!鸿蒙HarmonyOS Next应用开发实战学习路线
-
鸿蒙HarmonyOS NEXT开发技术最全学习路线指南
-
鸿蒙应用开发实战项目,看这一篇文章就够了(部分项目附源码)
@Reusable装饰器:组件复用
@Reusable装饰器装饰任意自定义组件时,表示该自定义组件可以复用。
说明
从API version 10开始,对@Reusable进行支持,支持在ArkTS中使用。
概述
- @Reusable适用自定义组件,与@Component结合使用,标记为@Reusable的自定义组件从组件树上被移除时,组件和其对应的JSView对象都会被放入复用缓存中,后续创建新自定义组件节点时,会复用缓存区中的节点,节约组件重新创建的时间。
说明
关于组件复用的原理与使用、组件复用优化方法、适用场景。
限制条件
- @Reusable装饰器仅用于自定义组件。
import { ComponentContent } from \"@kit.ArkUI\";// @Builder加上@Reusable编译报错,不适用于builder// @Reusable@Builderfunction buildCreativeLoadingDialog(closedClick: () => void) { Crash()}@Componentexport struct Crash { build() { Column() { Text(\"Crash\") .fontSize(12) .lineHeight(18) .fontColor(Color.Blue) .margin({ left: 6 }) }.width(\'100%\') .height(\'100%\') .justifyContent(FlexAlign.Center) }}@Entry@Componentstruct Index { @State message: string = \'Hello World\'; private uicontext = this.getUIContext(); build() { RelativeContainer() { Text(this.message) .id(\'Index\') .fontSize(50) .fontWeight(FontWeight.Bold) .alignRules({ center: { anchor: \'__container__\', align: VerticalAlign.Center }, middle: { anchor: \'__container__\', align: HorizontalAlign.Center } }) .onClick(() => { let contentNode = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog), () => { }); this.uicontext.getPromptAction().openCustomDialog(contentNode); }) } .height(\'100%\') .width(\'100%\') }}
- ComponentContent不支持传入@Reusable装饰器装饰的自定义组件。
import { ComponentContent } from \"@kit.ArkUI\";@Builderfunction buildCreativeLoadingDialog(closedClick: () => void) { Crash()}// 如果注释掉就可以正常弹出弹窗,如果加上@Reusable就直接crash@Reusable@Componentexport struct Crash { build() { Column() { Text(\"Crash\") .fontSize(12) .lineHeight(18) .fontColor(Color.Blue) .margin({ left: 6 }) }.width(\'100%\') .height(\'100%\') .justifyContent(FlexAlign.Center) }}@Entry@Componentstruct Index { @State message: string = \'Hello World\'; private uicontext = this.getUIContext(); build() { RelativeContainer() { Text(this.message) .id(\'Index\') .fontSize(50) .fontWeight(FontWeight.Bold) .alignRules({ center: { anchor: \'__container__\', align: VerticalAlign.Center }, middle: { anchor: \'__container__\', align: HorizontalAlign.Center } }) .onClick(() => { // ComponentContent底层是buildNode,buildNode不支持传入@Reusable注解的自定义组件 let contentNode = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog), () => { }); this.uicontext.getPromptAction().openCustomDialog(contentNode); }) } .height(\'100%\') .width(\'100%\') }}
- @Reusable装饰器不支持嵌套使用,存在增加内存和不方便维护的问题;
说明
不支持嵌套使用,嵌套使用会增加缓存池,降低复用效率,增加内存。
嵌套使用形成独立的复用缓存池,生命周期传递存在问题,资源和变量无法共享,不便维护,容易引发问题。
示例中PlayButton形成的复用缓存池,并不能在PlayButton02的复用缓存池使用,但PlayButton02自己形成复用缓存相互可以使用;
在PlayButton隐藏时已经触发PlayButton02的aboutToRecycle,但是在PlayButton02单独显示时却无法执行aboutToReuse,组件复用的生命周期方法存在无法成对调用问题;
综上,不建议嵌套使用。
@Entry@Componentstruct Index { @State isPlaying: boolean = false; @State isPlaying02: boolean = true; @State isPlaying01: boolean = false; build() { Column() { if (this.isPlaying02) { // 初始态是显示的按钮 Text(\"Default shown childbutton\") .fontSize(14) PlayButton02({ isPlaying02: $isPlaying02 }) } Text(`------------------------`) // 初始态是隐藏的按钮 if (this.isPlaying01) { Text(\"Default hidden childbutton\") .fontSize(14) PlayButton02({ isPlaying02: $isPlaying01 }) } Text(`------------------------`) // 父子嵌套 if (this.isPlaying) { Text(\"parent child 嵌套\") .fontSize(14) PlayButton({ buttonPlaying: $isPlaying }) } Text(`------------------------`) // 父子嵌套控制 Text(`Parent=child==is ${this.isPlaying ? \'\' : \'not\'} playing`).fontSize(14) Button(\'Parent=child===controll=\' + this.isPlaying) .margin(14) .onClick(() => { this.isPlaying = !this.isPlaying; }) Text(`------------------------`) // 默认隐藏按钮控制 Text(`Hiddenchild==is ${this.isPlaying01 ? \'\' : \'not\'} playing`).fontSize(14) Button(\'Button===hiddenchild==control==\' + this.isPlaying01) .margin(14) .onClick(() => { this.isPlaying01 = !this.isPlaying01; }) Text(`------------------------`) // 默认显示按钮控制 Text(`shownchid==is ${this.isPlaying02 ? \'\' : \'not\'} playing`).fontSize(14) Button(\'Button===shownchid==control==:\' + this.isPlaying02) .margin(15) .onClick(() => { this.isPlaying02 = !this.isPlaying02; }) } }}// 复用1@Reusable@Componentstruct PlayButton { @Link buttonPlaying: boolean; build() { Column() { // 复用 PlayButton02({ isPlaying02: $buttonPlaying }) Button(this.buttonPlaying ? \'parent_pause\' : \'parent_play\') .margin(12) .onClick(() => { this.buttonPlaying = !this.buttonPlaying; }) } }}// 复用2 不建议嵌套使用@Reusable@Componentstruct PlayButton02 { @Link isPlaying02: boolean; aboutToRecycle(): void { console.log(\"=====aboutToRecycle====PlayButton02====\"); } aboutToReuse(params: ESObject): void { console.log(\"=====aboutToReuse====PlayButton02====\"); } build() { Column() { Button(\'===commonbutton=====\') .margin(12) } }}
使用场景
-
列表滚动:当应用需要展示大量数据的列表,并且用户进行滚动操作时,频繁创建和销毁列表项的视图可能导致卡顿和性能问题。在这种情况下,使用列表组件的组件复用机制可以重用已经创建的列表项视图,提高滚动的流畅度。
-
动态布局更新:应用界面频繁更新时,重复创建和删除视图可能导致频繁的布局计算,影响帧率。使用组件复用可以避免不必要的视图创建和布局计算,提高性能。
-
频繁创建和销毁数据项的视图时,使用组件复用可以重用已创建的视图,只更新数据内容,减少视图的创建和销毁,提高性能。
使用场景举例
动态布局更新
- 示例代码将Child自定义组件标记为复用组件,通过Button点击更新Child,触发复用。
- @Reusable:自定义组件被@Reusable装饰器修饰,即表示其具备组件复用的能力;
- aboutToReuse:当一个可复用的自定义组件从复用缓存中重新加入到节点树时,触发aboutToReuse生命周期回调,并将组件的构造参数传递给aboutToReuse。
// xxx.etsexport class Message { value: string | undefined; constructor(value: string) { this.value = value; }}@Entry@Componentstruct Index { @State switch: boolean = true; build() { Column() { Button(\'Hello\') .fontSize(30) .fontWeight(FontWeight.Bold) .onClick(() => { this.switch = !this.switch; }) if (this.switch) { // 如果只有一个复用的组件,可以不用设置reuseId Child({ message: new Message(\'Child\') }) .reuseId(\'Child\') } } .height(\"100%\") .width(\'100%\') }}@Reusable@Componentstruct Child { @State message: Message = new Message(\'AboutToReuse\'); aboutToReuse(params: Record) { console.info(\"Recycle ====Child==\"); this.message = params.message as Message; } build() { Column() { Text(this.message.value) .fontSize(30) } .borderWidth(1) .height(100) }}
列表滚动配合LazyForEach使用
- 示例代码将CardView自定义组件标记为复用组件,List上下滑动,触发复用。
- 被@State修饰的变量item才能更新,未被@State修饰的变量不会更新。
class MyDataSource implements IDataSource { private dataArray: string[] = []; private listener: DataChangeListener | undefined; public totalCount(): number { return this.dataArray.length; } public getData(index: number): string { return this.dataArray[index]; } public pushData(data: string): void { this.dataArray.push(data); } public reloadListener(): void { this.listener?.onDataReloaded(); } public registerDataChangeListener(listener: DataChangeListener): void { this.listener = listener; } public unregisterDataChangeListener(listener: DataChangeListener): void { this.listener = undefined; }}@Entry@Componentstruct ReuseDemo { private data: MyDataSource = new MyDataSource(); aboutToAppear() { for (let i = 1; i { ListItem() { CardView({ item: item }) } }, (item: string) => item) } } }}// 复用组件@Reusable@Componentexport struct CardView { @State item: string = \'\'; aboutToReuse(params: Record): void { this.item = params.item as string; } build() { Column() { Text(this.item) .fontSize(30) } .borderWidth(1) .height(100) }}
if使用场景
- 示例代码将OneMoment自定义组件标记为复用组件,List上下滑动,触发复用。
- 可以使用reuseId为复用组件分配复用组,相同reuseId的组件会在同一个复用组中复用。如果只有一个复用的组件,可以不设置reuseId。
- 通过reuseId标识需要复用的组件,省去重复执行if逻辑中的删除和重建操作,提高组件复用的效率和性能。
@Entry@Componentstruct Index { private dataSource = new MyDataSource(); aboutToAppear(): void { for (let i = 0; i < 20; i++) { let title = i + 1 + \"test_if\"; this.dataSource.pushData(new FriendMoment(i.toString(), title, \'app.media.app_icon\')); } for (let i = 0; i { ListItem() { // 使用reuseId进行组件复用的控制 OneMoment({ moment: moment }) .reuseId((moment.image !== \'\') ? \'withImage\' : \'noImage\') } }, (moment: FriendMoment) => moment.id) } .cachedCount(0) } }}class FriendMoment { id: string = \'\'; text: string = \'\'; title: string = \'\'; image: string = \'\'; answers: Array = []; constructor(id: string, title: string, image: string) { this.text = id; this.title = title; this.image = image; }}@Reusable@Componentexport struct OneMoment { @Prop moment: FriendMoment; // 复用id相同的组件才能触发复用 aboutToReuse(params: ESObject): void { console.log(\"=====aboutToReuse====OneMoment==复用了==\" + this.moment.text); } build() { Column() { Text(this.moment.text) // if分支判断 if (this.moment.image !== \'\') { Flex({ wrap: FlexWrap.Wrap }) { Image($r(this.moment.image)).height(50).width(50) Image($r(this.moment.image)).height(50).width(50) Image($r(this.moment.image)).height(50).width(50) Image($r(this.moment.image)).height(50).width(50) } } } }}class BasicDataSource implements IDataSource { private listeners: DataChangeListener[] = []; private originDataArray: T[] = []; public totalCount(): number { return 0; } public getData(index: number): T { return this.originDataArray[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) = 0) { this.listeners.splice(pos, 1); } } notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); }}export class MyDataSource extends BasicDataSource { private dataArray: T[] = []; public totalCount(): number { return this.dataArray.length; } public getData(index: number): T { return this.dataArray[index]; } public pushData(data: T): void { this.dataArray.push(data); this.notifyDataAdd(this.dataArray.length - 1); }}
Foreach使用场景
- 使用Foreach创建可复用的自定义组件时,由于Foreach渲染控制语法的全展开属性,复用组件无法复用。示例中点击update,数据刷新成功,但滑动列表时,ListItemView无法复用。
- 单击clear,再次单击update,ListItemView复用成功,因为一帧内重复创建多个已被销毁的自定义组件。
// xxx.etsclass MyDataSource implements IDataSource { private dataArray: string[] = []; public totalCount(): number { return this.dataArray.length; } public getData(index: number): string { return this.dataArray[index]; } public pushData(data: string): void { this.dataArray.push(data); } public registerDataChangeListener(listener: DataChangeListener): void { } public unregisterDataChangeListener(listener: DataChangeListener): void { }}@Entry@Componentstruct Index { private data: MyDataSource = new MyDataSource(); private data02: MyDataSource = new MyDataSource(); @State isShow: boolean = true; @State dataSource: ListItemObject[] = []; aboutToAppear() { for (let i = 0; i < 100; i++) { this.data.pushData(i.toString()); } for (let i = 30; i { for (let i = 1; i { for (let i = 1; i { ListItem() { ListItemView({ obj: item }) } }, (item: ListItemObject) => { return item.uuid.toString(); }) }.cachedCount(0) .width(\'100%\') .height(\'100%\') } }}@Reusable@Componentstruct ListItemView { @ObjectLink obj: ListItemObject; @State item: string = \'\'; aboutToAppear(): void { // 点击 update,首次进入,上下滑动,由于Foreach折叠展开属性,无法复用 console.log(\"=====aboutToAppear=====ListItemView==创建了==\" + this.item); } aboutToReuse(params: ESObject) { this.item = params.item; // 点击 clear,再次update,复用成功 // 符合一帧内重复创建多个已被销毁的自定义组件 console.log(\"=====aboutToReuse====ListItemView==复用了==\" + this.item); } build() { Column({ space: 10 }) { Text(`${this.obj.id}.标题`) .fontSize(16) .fontColor(\'#000000\') .padding({ top: 20, bottom: 20, }) if (this.obj.isExpand) { Text(\'\') .fontSize(14) .fontColor(\'#999999\') } } .width(\'100%\') .borderRadius(10) .backgroundColor(Color.White) .padding(15) .onClick(() => { this.obj.isExpand = !this.obj.isExpand; }) }}@Observedclass ListItemObject { uuid: string = \"\"; id: number = 0; isExpand: boolean = false;}
Grid使用场景
- 示例中使用@Reusable装饰器修饰GridItem中的自定义组件ReusableChildComponent,即表示其具备组件复用的能力。
- 使用aboutToReuse可以在Grid滑动时从复用缓存中加入到组件树之前触发,从而更新组件的状态变量以展示正确的内容。
- 需要注意的是无需在aboutToReuse中对@Link、@StorageLink、@ObjectLink、@Consume等自动更新值的状态变量进行更新,可能触发不必要的组件刷新。
// MyDataSource类实现IDataSource接口class MyDataSource implements IDataSource { private dataArray: number[] = []; public pushData(data: number): void { this.dataArray.push(data); } // 数据源的数据总量 public totalCount(): number { return this.dataArray.length; } // 返回指定索引位置的数据 public getData(index: number): number { return this.dataArray[index]; } registerDataChangeListener(listener: DataChangeListener): void { } unregisterDataChangeListener(listener: DataChangeListener): void { }}@Entry@Componentstruct MyComponent { // 数据源 private data: MyDataSource = new MyDataSource(); aboutToAppear() { for (let i = 1; i { GridItem() { // 使用可复用自定义组件 ReusableChildComponent({ item: item }) } }, (item: string) => item) } .cachedCount(2) // 设置GridItem的缓存数量 .columnsTemplate(\'1fr 1fr 1fr\') .columnsGap(10) .rowsGap(10) .margin(10) .height(500) .backgroundColor(0xFAEEE0) } }}@Reusable@Componentstruct ReusableChildComponent { @State item: number = 0; // aboutToReuse从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容 // aboutToReuse参数类型已不支持any,这里使用Record指定明确的数据类型。Record用于构造一个对象类型,其属性键为Keys,属性值为Type aboutToReuse(params: Record) { this.item = params.item; } build() { Column() { // 请开发者自行在src/main/resources/base/media路径下添加app.media.app_icon图片,否则运行时会因资源缺失而报错 Image($r(\'app.media.app_icon\')) .objectFit(ImageFit.Fill) .layoutWeight(1) Text(`图片${this.item}`) .fontSize(16) .textAlign(TextAlign.Center) } .width(\'100%\') .height(120) .backgroundColor(0xF9CF93) }}
WaterFlow使用场景
- WaterFlow滑动场景存在FlowItem及其子组件的频繁创建和销毁,可以将FlowItem中的组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力。
class WaterFlowDataSource implements IDataSource { private dataArray: number[] = []; private listeners: DataChangeListener[] = []; constructor() { for (let i = 0; i { listener.onDataAdd(index); }); } // 获取数据总数 public totalCount(): number { return this.dataArray.length; } // 注册改变数据的控制器 registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) = 0) { this.listeners.splice(pos, 1); } } // 在数据尾部增加一个元素 public addLastItem(): void { this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length); this.notifyDataAdd(this.dataArray.length - 1); }}@Reusable@Componentstruct ReusableFlowItem { @State item: number = 0; // 从复用缓存中加入到组件树之前调用,可在此处更新组件的状态变量以展示正确的内容 aboutToReuse(params: ESObject) { this.item = params.item; console.log(\"=====aboutToReuse====FlowItem==复用了==\" + this.item); } aboutToRecycle(): void { console.log(\"=====aboutToRecycle====FlowItem==回收了==\" + this.item); } build() { // 请开发者自行在src/main/resources/base/media路径下添加app.media.app_icon图片,否则运行时会因资源缺失而报错 Column() { Text(\"N\" + this.item).fontSize(24).height(\'26\').margin(10) Image($r(\'app.media.app_icon\')) .objectFit(ImageFit.Cover) .width(50) .height(50) } }}@Entry@Componentstruct Index { @State minSize: number = 50; @State maxSize: number = 80; @State fontSize: number = 24; @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F]; scroller: Scroller = new Scroller(); dataSource: WaterFlowDataSource = new WaterFlowDataSource(); private itemWidthArray: number[] = []; private itemHeightArray: number[] = []; // 计算flow item宽/高 getSize() { let ret = Math.floor(Math.random() * this.maxSize); return (ret > this.minSize ? ret : this.minSize); } // 保存flow item宽/高 getItemSizeArray() { for (let i = 0; i { // 点击后回到顶部 this.scroller.scrollEdge(Edge.Top); }) WaterFlow({ scroller: this.scroller }) { LazyForEach(this.dataSource, (item: number) => { FlowItem() { ReusableFlowItem({ item: item }) }.onAppear(() => { if (item + 20 == this.dataSource.totalCount()) { for (let i = 0; i < 50; i++) { this.dataSource.addLastItem(); } } }) }) } } } } @Builder itemFoot() { Column() { Text(`Footer`) .fontSize(10) .backgroundColor(Color.Red) .width(50) .height(50) .align(Alignment.Center) .margin({ top: 2 }) } }}
Swiper使用场景
- Swiper滑动场景,条目中存在子组件的频繁创建和销毁,可以将条目中的子组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力。
@Entry@Componentstruct Index { private dataSource = new MyDataSource(); aboutToAppear(): void { for (let i = 0; i { QuestionSwiperItem({ itemData: item }) }, (item: Question) => item.id) } } .width(\'100%\') .margin({ top: 5 }) }}class Question { id: string = \'\'; title: ResourceStr = \'\'; image: ResourceStr = \'\'; answers: Array = []; constructor(id: string, title: ResourceStr, image: ResourceStr, answers: Array) { this.id = id; this.title = title; this.image = image; this.answers = answers; }}@Reusable@Componentstruct QuestionSwiperItem { @State itemData: Question | null = null; aboutToReuse(params: Record): void { this.itemData = params.itemData as Question; console.info(\"===test===aboutToReuse====QuestionSwiperItem==\"); } build() { Column() { Text(this.itemData?.title) .fontSize(18) .fontColor($r(\'sys.color.ohos_id_color_primary\')) .alignSelf(ItemAlign.Start) .margin({ top: 10, bottom: 16 }) Image(this.itemData?.image) .width(\'100%\') .borderRadius(12) .objectFit(ImageFit.Contain) .margin({ bottom: 16 }) .height(80) .width(80) Column({ space: 16 }) { ForEach(this.itemData?.answers, (item: Resource) => { Text(item) .fontSize(16) .fontColor($r(\'sys.color.ohos_id_color_primary\')) }, (item: ResourceStr) => JSON.stringify(item)) } .width(\'100%\') .alignItems(HorizontalAlign.Start) } .width(\'100%\') .padding({ left: 16, right: 16 }) }}class BasicDataSource implements IDataSource { private listeners: DataChangeListener[] = []; private originDataArray: T[] = []; public totalCount(): number { return 0; } public getData(index: number): T { return this.originDataArray[index]; } registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) = 0) { this.listeners.splice(pos, 1); } } notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); }}export class MyDataSource extends BasicDataSource { private dataArray: T[] = []; public totalCount(): number { return this.dataArray.length; } public getData(index: number): T { return this.dataArray[index]; } public pushData(data: T): void { this.dataArray.push(data); this.notifyDataAdd(this.dataArray.length - 1); }}
ListItemGroup使用场景
- 可以视作特殊List滑动场景,将ListItem需要销毁重建的子组件封装成自定义组件,并使用@Reusable装饰器修饰,使其具备组件复用能力。
@Entry@Componentstruct ListItemGroupAndReusable { data: DataSrc2 = new DataSrc2(); @Builder itemHead(text: string) { Text(text) .fontSize(20) .backgroundColor(0xAABBCC) .width(\'100%\') .padding(10) } aboutToAppear() { for (let i = 0; i < 10000; i++) { let data_1 = new DataSrc1(); for (let j = 0; j { ListItemGroup({ header: this.itemHead(index.toString()) }) { LazyForEach(item, (ii: string, index: number) => { ListItem() { Inner({ str: ii }) } }) } .width(\'100%\') .height(\'60vp\') }) } } .width(\'100%\') .height(\'100%\') }}@Reusable@Componentstruct Inner { @State str: string = \'\'; aboutToReuse(param: ESObject) { this.str = param.str; } build() { Text(this.str) }}class DataSrc1 implements IDataSource { listeners: DataChangeListener[] = []; Data: string[] = []; public totalCount(): number { return this.Data.length; } public getData(index: number): string { return this.Data[index]; } // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) = 0) { this.listeners.splice(pos, 1); } } // 通知LazyForEach组件需要重载所有子组件 notifyDataReload(): void { this.listeners.forEach(listener => { listener.onDataReloaded(); }); } // 通知LazyForEach组件需要在index对应索引处添加子组件 notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); } // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 notifyDataChange(index: number): void { this.listeners.forEach(listener => { listener.onDataChange(index); }); } // 通知LazyForEach组件需要在index对应索引处删除该子组件 notifyDataDelete(index: number): void { this.listeners.forEach(listener => { listener.onDataDelete(index); }); } // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 notifyDataMove(from: number, to: number): void { this.listeners.forEach(listener => { listener.onDataMove(from, to); }); }}class DataSrc2 implements IDataSource { listeners: DataChangeListener[] = []; Data: DataSrc1[] = []; public totalCount(): number { return this.Data.length; } public getData(index: number): DataSrc1 { return this.Data[index]; } // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) = 0) { this.listeners.splice(pos, 1); } } // 通知LazyForEach组件需要重载所有子组件 notifyDataReload(): void { this.listeners.forEach(listener => { listener.onDataReloaded(); }); } // 通知LazyForEach组件需要在index对应索引处添加子组件 notifyDataAdd(index: number): void { this.listeners.forEach(listener => { listener.onDataAdd(index); }); } // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 notifyDataChange(index: number): void { this.listeners.forEach(listener => { listener.onDataChange(index); }); } // 通知LazyForEach组件需要在index对应索引处删除该子组件 notifyDataDelete(index: number): void { this.listeners.forEach(listener => { listener.onDataDelete(index); }); } // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 notifyDataMove(from: number, to: number): void { this.listeners.forEach(listener => { listener.onDataMove(from, to); }); }}
多种条目类型使用场景
标准型
- 复用组件的布局相同。
- 示例参见列表滚动中描述。
有限变化型
- 复用组件之间有不同,但是类型数量较少。
- 示例:为复用组件显式设置两个reuseId,或使用两个自定义组件进行复用。
class MyDataSource implements IDataSource { private dataArray: string[] = []; private listener: DataChangeListener | undefined; public totalCount(): number { return this.dataArray.length; } public getData(index: number): string { return this.dataArray[index]; } public pushData(data: string): void { this.dataArray.push(data); } public reloadListener(): void { this.listener?.onDataReloaded(); } public registerDataChangeListener(listener: DataChangeListener): void { this.listener = listener; } public unregisterDataChangeListener(listener: DataChangeListener): void { this.listener = undefined; }}@Entry@Componentstruct Index { private data: MyDataSource = new MyDataSource(); aboutToAppear() { for (let i = 0; i { ListItem() { ReusableComponent({ item: item }) .reuseId(item % 2 === 0 ? \'ReusableComponentOne\' : \'ReusableComponentTwo\') } .backgroundColor(Color.Orange) .width(\'100%\') }, (item: number) => item.toString()) } .cachedCount(2) } }}@Reusable@Componentstruct ReusableComponent { @State item: number = 0; aboutToReuse(params: ESObject) { this.item = params.item; } build() { Column() { if (this.item % 2 === 0) { Text(`Item ${this.item} ReusableComponentOne`) .fontSize(20) .margin({ left: 10 }) } else { Text(`Item ${this.item} ReusableComponentTwo`) .fontSize(20) .margin({ left: 10 }) } }.margin({ left: 10, right: 10 }) }}
组合型
- 复用组件之间有不同,但情况较多,且拥有共同的子组件。
- 将三种复用组件转换为Builder函数后,它们的内部共同子组件将处于同一个父组件MyComponent下。
- 使用组件复用时,子组件的缓存池会在父组件上共享,从而节省组件创建时的资源消耗。
class MyDataSource implements IDataSource { private dataArray: string[] = []; private listener: DataChangeListener | undefined; public totalCount(): number { return this.dataArray.length; } public getData(index: number): string { return this.dataArray[index]; } public pushData(data: string): void { this.dataArray.push(data); } public reloadListener(): void { this.listener?.onDataReloaded(); } public registerDataChangeListener(listener: DataChangeListener): void { this.listener = listener; } public unregisterDataChangeListener(listener: DataChangeListener): void { this.listener = undefined; }}@Entry@Componentstruct MyComponent { private data: MyDataSource = new MyDataSource(); aboutToAppear() { for (let i = 0; i { ListItem() { if (index % 3 === 0) { this.itemBuilderOne(item) } else if (index % 5 === 0) { this.itemBuilderTwo(item) } else { this.itemBuilderThree(item) } } .backgroundColor(\'#cccccc\') .width(\'100%\') .onAppear(() => { console.log(`ListItem ${index} onAppear`); }) }, (item: number) => item.toString()) } .width(\'100%\') .height(\'100%\') .cachedCount(0) }}@Reusable@Componentstruct ChildComponentA { @State item: string = \'\'; aboutToReuse(params: ESObject) { console.log(`ChildComponentA ${params.item} Reuse ${this.item}`); this.item = params.item; } aboutToRecycle(): void { console.log(`ChildComponentA ${this.item} Recycle`); } build() { Column() { Text(`Item ${this.item} Child Component A`) .fontSize(20) .margin({ left: 10 }) .fontColor(Color.Blue) Grid() { ForEach((new Array(20)).fill(\'\'), (item: string, index: number) => { GridItem() { // 请开发者自行在src/main/resources/base/media路径下添加app.media.startIcon图片,否则运行时会因资源缺失而报错 Image($r(\'app.media.startIcon\')) .height(20) } }) } .columnsTemplate(\'1fr 1fr 1fr 1fr 1fr\') .rowsTemplate(\'1fr 1fr 1fr 1fr\') .columnsGap(10) .width(\'90%\') .height(160) } .margin({ left: 10, right: 10 }) .backgroundColor(0xFAEEE0) }}@Reusable@Componentstruct ChildComponentB { @State item: string = \'\'; aboutToReuse(params: ESObject) { this.item = params.item; } build() { Row() { Text(`Item ${this.item} Child Component B`) .fontSize(20) .margin({ left: 10 }) .fontColor(Color.Red) }.margin({ left: 10, right: 10 }) }}@Reusable@Componentstruct ChildComponentC { @State item: string = \'\'; aboutToReuse(params: ESObject) { this.item = params.item; } build() { Row() { Text(`Item ${this.item} Child Component C`) .fontSize(20) .margin({ left: 10 }) .fontColor(Color.Green) }.margin({ left: 10, right: 10 }) }}@Reusable@Componentstruct ChildComponentD { @State item: string = \'\'; aboutToReuse(params: ESObject) { this.item = params.item; } build() { Row() { Text(`Item ${this.item} Child Component D`) .fontSize(20) .margin({ left: 10 }) .fontColor(Color.Orange) }.margin({ left: 10, right: 10 }) }}