一、功能概述
鸿蒙Next的自定义组件冻结功能专为优化复杂UI页面性能而设计,在包含多页面栈、长列表或宫格布局等场景中作用显著。当状态变量绑定多个UI组件时,其变化可能引发大量UI组件刷新,导致界面卡顿和响应延迟。此功能通过设置freezeWhenInactive属性激活组件冻结机制,仅更新处于激活状态的自定义组件,缩小更新范围,提高复杂UI场景下的刷新效率。当inactive状态的组件重新变为active时,状态管理框架会执行必要刷新操作以确保UI正确展示,实现按需刷新,如多页面栈、长列表或宫格布局中仅刷新当前可见组件,延迟不可见组件的刷新。
适用场景
- 页面路由:当前栈顶页面为active,非栈顶不可见页面为inactive。
- TabContent:当前显示的TabContent中的自定义组件处于active状态,其余为inactive(首次渲染时,Tab只创建当前显示的TabContent,切换全部TabContent后才全部创建)。
- LazyForEach:仅当前显示的LazyForEach中的自定义组件为active,缓存节点的组件为inactive。
- Navigation:当前显示的NavDestination中的自定义组件为active,其他未显示的NavDestination组件为inactive。
注意事项
组件active / inactive不等同于可见性,如堆叠布局(Stack)下被遮罩的组件虽不可见,但不视为inactive状态,不在组件冻结适用范围内。从API version 11开始支持该功能。
二、功能示例
(一)页面路由
页面A跳转到页面B
- 点击页面A中的“first page storageLink + 1”按钮,
storageLink
状态变量改变,@Watch
中注册的first
方法被调用。 - 通过
router.pushUrl({url: 'pages/second'})
跳转到页面B后,页面A隐藏,状态变为inactive。此时点击页面B中的“this.storageLink2 += 2”按钮,仅回调页面B中@Watch
注册的second
方法,因为页面A的状态变量已被冻结。 - 点击“back”返回页面A,页面A状态由inactive变为active,重新刷新之前被冻结的状态变量,
@Watch
中注册的first
方法再次被调用。
- 点击页面A中的“first page storageLink + 1”按钮,
相关代码示例
// 页面A import { router } from '@kit.ArkUI'; @Entry @Component({ freezeWhenInactive: true }) struct FirstTest { @StorageLink('PropA') @Watch("first") storageLink: number = 47; first() { console.info("first page " + `${this.storageLink}`) } build() { Column() { Text(`From fist Page ${this.storageLink}`).fontSize(50) Button('first page storageLink + 1').fontSize(30) .onClick(() => { this.storageLink += 1 }) Button('go to next page').fontSize(30) .onClick(() => { router.pushUrl({ url: 'pages/second' }) }) } } } // 页面B import { router } from '@kit.ArkUI'; @Entry @Component({ freezeWhenInactive: true }) struct SecondTest { @StorageLink('PropA') @Watch("second") storageLink2: number = 1; second() { console.info("second page: " + `${this.storageLink2}`) } build() { Column() { Text(`second Page ${this.storageLink2}`).fontSize(50) Button('Change Divider.strokeWidth') .onClick(() => { router.back() }) Button('second page storageLink2 + 2').fontSize(30) .onClick(() => { this.storageLink2 += 2 }) } } }
(二)TabContent
操作与响应
- 点击“change message”更改
message
的值,当前正在显示的TabContent组件中的@Watch
注册的onMessageUpdated
方法被触发。 - 点击切换到另外的TabContent,该TabContent状态由inactive变为active,对应的
@Watch
注册的onMessageUpdated
方法被触发。 - 再次点击“change message”更改
message
的值,仅当前显示的TabContent子组件中的@Watch
注册的onMessageUpdated
方法被触发。
- 点击“change message”更改
相关代码示例
@Entry @Component struct TabContentTest { @State @Watch("onMessageUpdated") message: number = 0; private data: number[] = [0, 1] onMessageUpdated() { console.info(`TabContent message callback func ${this.message}`) } build() { Row() { Column() { Button('change message').onClick(() => { this.message++ }) Tabs() { ForEach(this.data, (item: number ) => { TabContent() { FreezeChild({ message: this.message, index: item }) }.tabBar(`tab${item}`) }, (item: number) => item.toString()) } } .width('100%') } .height('100%') } } @Component({ freezeWhenInactive: true }) struct FreezeChild { @Link @Watch("onMessageUpdated") message: number private index: number = 0 onMessageUpdated() { console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) } build() { Text("message" + `${this.message}, index: ${this.index}`) .fontSize(50) .fontWeight(FontWeight.Bold) } }
(三)LazyForEach
操作与响应
- 点击“change message”更改
message
的值,当前正在显示的ListItem中的子组件@Watch
注册的onMessageUpdated
方法被触发,缓存节点@Watch
注册的方法不会被触发(若不使用组件冻结,当前显示的ListItem和cachecount
缓存节点@Watch
注册的onMessageUpdated
方法都会触发watch回调)。 - List区域外的ListItem滑动到List区域内,状态由inactive变为active,对应的
@Watch
注册的onMessageUpdated
方法被触发。 - 再次点击“change message”更改
message
的值,仅有当前显示的ListItem中的子组件@Watch
注册的onMessageUpdated
方法被触发。
- 点击“change message”更改
相关代码示例
// 数据源相关类 class BasicDataSource implements IDataSource { // 省略部分代码 } class MyDataSource extends BasicDataSource { // 省略部分代码 } @Entry @Component struct LforEachTest { private data: MyDataSource = new MyDataSource(); @State @Watch("onMessageUpdated") message: number = 0; onMessageUpdated() { console.info(`LazyforEach message callback func ${this.message}`) } aboutToAppear() { for (let i = 0; i <= 20; i++) { this.data.pushData(`Hello ${i}`) } } build() { Column() { Button('change message').onClick(() => { this.message++ }) List({ space: 3 }) { LazyForEach(this.data, (item: string ) => { ListItem() { FreezeChild({ message: this.message, index: item }) } }, (item: string) => item) }.cachedCount(5).height(500) } } } @Component({ freezeWhenInactive: true }) struct FreezeChild { @Link @Watch("onMessageUpdated") message: number; private index: string = ""; aboutToAppear() { console.info(`FreezeChild aboutToAppear index: ${this.index}`) } onMessageUpdated() { console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) } build() { Text("message" + `${this.message}, index: ${this.index}`) .width('90%') .height(160) .backgroundColor(0xAFEEEE) .textAlign(TextAlign.Center) .fontSize(30) .fontWeight(FontWeight.Bold) } }
(四)Navigation
操作与响应
- 点击“change message”更改
message
的值,当前正在显示的MyNavigationTestStack组件中的@Watch
注册的info
方法被触发。 - 点击“Next Page”切换到PageOne,创建pageOneStack节点;再次点击“change message”,仅pageOneStack中的NavigationContentMsgStack子组件中的
@Watch
注册的info
方法被触发。 - 依此类推,在页面切换过程中,只有当前显示页面的相关组件会响应状态变量变化。点击“Back Page”返回时,对应页面的组件重新响应状态变化。
- 点击“change message”更改
相关代码示例
@Entry @Component struct MyNavigationTestStack { @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); @State @Watch("info") message: number = 0; @State logNumber: number = 0; info() { console.info(`freeze-test MyNavigation message callback ${this.message}`); } @Builder PageMap(name: string) { if (name === 'pageOne') { pageOneStack({ message: this.message, logNumber: this.logNumber }) } else if (name === 'pageTwo') { pageTwoStack({ message: this.message, logNumber: this.logNumber }) } else if (name === 'pageThree') { pageThreeStack({ message: this.message, logNumber: this.logNumber }) } } build() { Column() { Button('change message') .onClick(() => { this.message++; }) Navigation(this.pageInfo) { Column() { Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { this.pageInfo.pushPath({ name: 'pageOne' }); }) } }.title('NavIndex') .navDestination(this.PageMap) .mode(NavigationMode.Stack) } } } @Component struct pageOneStack { // 省略部分代码 build() { NavDestination() { Column() { NavigationContentMsgStack({ message: this.message, index: this.index, logNumber: this.logNumber }) Text("cur stack size:" + `${this.pageInfo.size()}`) .fontSize(30) .fontWeight(FontWeight.Bold) Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { this.pageInfo.pushPathByName('pageTwo', null); }) Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { this.pageInfo.pop(); }) }.width('100%').height('100%') }.title('pageOne') .onBackPressed(() => { this.pageInfo.pop(); return true; }) } } // pageTwoStack和pageThreeStack结构类似,省略部分代码 @Component({ freezeWhenInactive: true }) struct NavigationContentMsgStack { @Link @Watch("info") message: number; @Link index: number; @Link logNumber: number; info() { console.info(`freeze-test NavigationContent message callback ${this.message}`); console.info(`freeze-test ---- called by content ${this.index}`); this.logNumber++; } build() { Column() { Text("msg:" + `${this.message}`) .fontSize(30) .fontWeight(FontWeight.Bold) Text("log number:" + `${this.logNumber}`) .fontSize(30) .fontWeight(FontWeight.Bold) } } }
鸿蒙Next的自定义组件冻结功能通过合理的场景适配和简单的属性设置,有效提升了复杂UI页面的性能表现,开发者可根据实际项目需求灵活运用该功能来优化应用的用户体验。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。