# nodePoolNative **Repository Path**: chenchen_1111/node-pool-native ## Basic Information - **Project Name**: nodePoolNative - **Description**: 原生Node三方库。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 4 - **Created**: 2024-08-12 - **Last Updated**: 2024-09-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 库的使用 ## 简介 本项目是基于此三方库的示例代码,主要讲解如何通过BuilderNode创建全局的自定义组件复用池,实现跨页面的组件复用。另外,此项目中也实现了使用常规复用的方式,从而方便测量比较两种实现上的性能差异。 ## 效果预览 ![预览图](screenshots/sample.gif) ## demo说明 1. 第一个tab“首页”中,是基于此三方库的实现,点击顶部tab或者滑动可以进行页面切换。 2. 第二个tab“社区”中,是使用常规复用的实现,点击顶部tab或者滑动可以进行页面切换。 3. 2个tab中所有页面均为7种类型卡片构成的瀑布流,我们做了7种类型的卡片复用。 ## 使用库的关键步骤以及关键代码 ### 常规复用 1. 使用List+Swiper实现Tabs页面切换 ``` ... List() { // Tab标题 ForEach(this.arrayTitle, (title: Title, index: number) => { ListItem() { TitleView({ title: title, clickListener: () => { if (title.isSelected) { return; } // 点击标题时,Swiper组件跳转到对应的页面 this.swiperController.changeIndex(index, true); // 设置标题为选中状态 this.arrayTitle[index].isSelected = true; this.arrayTitle[this.selectIndex].isSelected = false; this.selectIndex = index; } }) } }) } ... Swiper(this.swiperController) { LazyForEach(this.array, (item: string, index: number) => { WaterFlowView({ index: index }) }, (title: string) => title) } .loop(false) .onAnimationStart((_index: number, targetIndex: number) => { // Swiper滑动切换页面时,改变标题栏的选中状态 if (this.selectIndex !== targetIndex) { this.arrayTitle[targetIndex].isSelected = true; this.arrayTitle[this.selectIndex].isSelected = false; this.selectIndex = targetIndex; } }) .cachedCount(0) ... ``` 2.实现瀑布流的子组件 ``` @Component export struct WaterFlowView { @State waterFlowItemWidth: number = 0; index: number = 0; dataSource: WaterFlowDataSource = new WaterFlowDataSource(); build() { Column({ space: CommonConstants.SPACE_EIGHT }) { Column() { WaterFlow() { LazyForEach(this.dataSource, (item: ViewItem, index: number) => { FlowItem() { ItemUniversalView({ waterFlowItemWidth: this.waterFlowItemWidth, item: item, }) .reuseId(ItemTypeConstants.TYPE_UNIVERSAL); //卡片唯一标识,用于组件复用 } }, (index: string) => index); } } } } // 需要添加@Resuable装饰器,并实现aboutToResuse接口用于组件复用时刷新数据 @Reusable @Component export struct ItemUniversalView { @Link waterFlowItemWidth: number; @State item: ViewItem = new ViewItem(); // 通过aboutToResuse接口刷新复用后的数据 aboutToReuse(params: Record): void { this.item = params.item as ViewItem; } build() { Column() { Image(this.item.coverImage) .width(CommonConstants.FULL_PERCENT) .onClick(() => { if (!this.item.isLike) { this.item.isLike = true; } else { this.item.isLike = false; } }) DescUniversalView({ item: this.item, isLike: this.item.isLike }); }; } } ``` ### 使用自定义组件复用池 1. 使用List+Swiper实现Tabs页面切换 ``` List() { // Tab标题 ForEach(this.arrayTitle, (title: Title, index: number) => { ListItem() { TitleView({ title: title, clickListener: () => { if (title.isSelected) { return; } // 点击标题时,Swiper组件跳转到对应的页面 this.swiperController.changeIndex(index, true); // 设置标题为选中状态 this.arrayTitle[index].isSelected = true; this.arrayTitle[this.selectIndex].isSelected = false; this.selectIndex = index; } }) ... } }) } ... Swiper(this.swiperController) { LazyForEach(this.array, (item: string, index: number) => { // 瀑布流卡片做组件复用 WaterFlowNodeView({ index: index }) }, (title: string) => title) } .loop(false) .onAnimationStart((_index: number, targetIndex: number) => { // Swiper滑动切换页面时,改变标题栏的选中状态 if (this.selectIndex !== targetIndex) { this.arrayTitle[targetIndex].isSelected = true; this.arrayTitle[this.selectIndex].isSelected = false; this.selectIndex = targetIndex; } }) .cachedCount(0) ``` 2.引用组件复用库@hadss/nodepool,创建瀑布流子组件WrappedBuilder对象,使用NodeContainerProxy占位,并调用组件复用池,完成调用 ``` import { RegisterCallback, UpdaterCallback } from '@hadss/nodepool'; @Builder function FlowItemBuilder(data: ESObject) { FlowItemNode({ item: data.item, updater: data.updater, callback: data.callback }) } // 瀑布流子组件WrappedBuilder对象 let flowItemWrapper: WrappedBuilder = wrapBuilder(FlowItemBuilder); const REUSE_VIEW_TYPE_ITEM: string = 'reuse_type_'; // 自定义组件复用池Swiper页面 @Component export struct WaterFlowNodeView { index: number = 0; dataSource: WaterFlowDataSource = new WaterFlowDataSource(); // 节点工厂实例 private nodePoolFactory: CustomNodePoolFactory = new CustomNodePoolFactory(); private typeCfg: TypeReuseConfig = { type: REUSE_VIEW_TYPE_ITEM, expTime: 30 * 60 * 1000, // 老化时间 reuseCallback: this.reuseCallback, recycleCallback: this.recycleCallback } aboutToAppear(): void { this.nodePoolFactory.getCommonNodePool().setTypeReuseConfig(this.typeCfg); // 添加模拟数据 this.dataSource.addItems(recommendData()); } // 更新数据方法 fillNewData(item: ViewItem) { this.dataSource.addLastItem(item); } build() { Column({ space: CommonConstants.SPACE_EIGHT }) { Column() { WaterFlow() { LazyForEach(this.dataSource, (item: ViewItem, index: number) => { FlowItem() { NodeContainerProxy({ nodeItem: this.nodePoolFactory.getCommonNodePool().getNode(REUSE_VIEW_TYPE_ITEM, { item: item, updater: (item: ViewItem) => { this.fillNewData(item); }, callback: null }, flowItemWrapper) }) } .width($r('app.string.nodepool_percent_100')) .backgroundColor(Color.White) .clip(true) .borderRadius($r('app.float.common_border_radius_7')) }, (index: string) => index) } ... } .width(CommonConstants.FULL_PERCENT) .height(CommonConstants.FULL_PERCENT) } ... } } ``` 3.瀑布流卡片实现 ``` import { RegisterCallback, UpdaterCallback } from '@hadss/nodepool'; // 自定义组件复用池瀑布流子组件 @Component export struct FlowItemNode { @State item: ViewItem = new ViewItem(); @State itemHeight: number = 0; // 数据更新方法 updater: (item: ViewItem) => void = (item: ViewItem) => { } // 复用池组件数据更新回调 callback: RegisterCallback = (callback: UpdaterCallback) => { } // 更新数据 update(data: ESObject) { this.item = data.item; } aboutToAppear(): void { this.callback((data: ESObject) => { this.update(data); }) } build() { Column() { this.UniversalView(); } .borderRadius($r('app.integer.customreusablepool_flow_item_comp_border_radius')) } setIsLike() { if (!this.item.isLike) { this.item.isLike = true; } else { this.item.isLike = false; } } @Builder UniversalView() { Image(this.item.coverImage) .width(CommonConstants.FULL_PERCENT) .onClick(() => { this.setIsLike(); }) DescUniversalView({ item: this.item, isLike: this.item.isLike }); } } ``` ## 性能对比 以下是2种实现方式在100条数据下进行对比: 1.编译运行后,进入第二个底部tab“社区”,点击顶部tabs切换页面,抓取Trace,通过图1中选择区域可见,切换Tabs时,每个页面的首帧耗时(从DispatchTouchEvent标签开始,到sendCommands标签结束)都在62ms左右。 这是因为使用@Reusable的组件复用,是使用了父组件的复用池。FlowItem的父组件是WaterFlow,Tab切换时新页面的WaterFlow会被重新创建,这就导致前一个页面的复用池是无法使用的,只能重新创建所有的子组件。 2.编译运行后,进入第一个底部tab“首页”,点击顶部tabs切换页面,然后抓取Trace,通过图2中的选择区域可以看到,页面的首帧耗时大幅减少,只有15ms左右。这是因为后续页面被创建时,会先去复用池中查找可用的子组件直接使用,减少了创建子组件的时间。 3.编译运行后,进入第二个底部tab“社区”,点击顶部tabs切换3个页面,使用profiler工具分析内存的情况如图3所示。从图中看出,最高占用内存182MB,最后降到149MB。 4.编译运行后,进入第一个底部tab“首页”,点击顶部tabs切换3个页面,使用profiler工具分析内存的情况如图4所示。从图中看出,最终和最高占用内存一致,为164MB。 ### 创建耗时数据对比 | 页面 | 页面1 | 页面2 | 页面3 | |-----------|--------|--------|--------| | 创建耗时(优化前) | 65.2ms | 61.9ms | 60.9ms | | 创建耗时(优化后) | 16.1ms | 13.4ms | 13.4ms | ### 内存数据对比 | 内存 | 最高内存 | 最终内存 | |-----|-------|-------| | 优化前 | 182MB | 149MB | | 优化后 | 164MB | 164MB | 图1 常规复用Trace图 ![预览图](screenshots/before.PNG) 图2 自定义组件复用池Trace图 ![预览图](screenshots/after.PNG) 图3 常规复用内存情况 ![预览图](screenshots/allocation_before.png) 图4 自定义组件复用池内存情况 ![预览图](screenshots/allocation_after.png) ## 工程目录 ``` ├── ets | ├── constants | | ├── BreakpointConstants.ets | | ├── CommonConstants.ets | | ├── Constants.ets | | └── HomeConstants.ets | ├── data | | ├── MockData.ets | | ├── TitleBean.ets | | ├── TitleDataSource.ets | | ├── ViewItem.ets | | └── WaterFlowDataSource.ets | ├── entryability | | └── EntryAbility.ets | ├── entrybackupability | | └── EntryBackupAbility.ets | ├── pages | | ├── HomePage.ets | | ├── Index.ets | | └── MenuPage.ets | ├── utils | | ├── BreakpointSystem.ets | | ├── BreakpointType.ets | | └── Logger.ets | ├── view | | ├── FlowItemNode.ets | | ├── ItemDescView | | | ├── DescHotelView.ets | | | ├── DescRaidersView.ets | | | ├── DescRentView.ets | | | ├── DescSceneView.ets | | | ├── DescTravelImageView.ets | | | ├── DescTravelVideoView.ets | | | └── DescUniversalView.ets | | ├── ItemHotelView.ets | | ├── ItemRaidersView.ets | | ├── ItemRentView.ets | | ├── ItemSceneView.ets | | ├── ItemTravelImageView.ets | | ├── ItemTravelVideoView.ets | | ├── ItemUniversalView.ets | | ├── TitleView.ets | | ├── WaterFlowNodeView.ets | | └── WaterFlowView.ets | └── viewmodel | └── FooterTabViewModel.ets ├── module.json5 └── resources ``` ## 相关权限 无特别权限要求 ## 约束与限制 1. 本示例仅支持标准系统上运行,支持设备:华为手机。 2. HarmonyOS系统:HarmonyOS NEXT Developer Beta1及以上。 3. DevEco Studio版本:DevEco Studio NEXT Developer Beta1及以上。 4. HarmonyOS SDK版本:HarmonyOS NEXT Developer Beta1 SDK及以上。