ArkTs学习之ArkTS装饰器@Entry装饰器 @Component装饰器(一)
一、概述
先看一张图
什么是装饰器
装饰器: 用于装饰类、结构、方法以及变量,并赋予其特殊的含义。如上述示例中@Entry、@Component和@State都是装饰器,@Component表示自定义组件,@Entry表示该自定义组件为入口组件,@State表示组件中的状态变量,状态变量变化会触发UI刷新(可以参考VUE中的data数据理解)。
1、装饰器是一个函数,这个函数仅在代码加载阶段执行一次。本质就是编译时执行的函数
2、装饰器的语法是 @后跟一个函数或者一个执行后返回函数的表达式
3、这个函数要么不返回值,要么返回一个新对象取代所修饰的目标对象
4、装饰器有两个版本,一个是2014年通过的,一个是2022年通过的。ArkTS里使用的是2014年通过的
装饰器分类
我们介绍的也只是2014年通过的。如果想了解最新版的装饰器,请看 TypeScript 装饰器
装饰器简单代码示例
@ClassDecorator() // 类装饰器 class A { @PropertyDecorator() // 属性装饰器 name: string; @MethodDecorator() // 方法装饰器 fly( @ParameterDecorator() // 参数装饰器 meters: number ) { // code } @AccessorDecorator() // 存取器装饰器 get egg() { // code } set egg(e) { // code } }
⚠️注意:
构造方法没有方法装饰器,只有参数装饰器。类装饰器其实就是在装饰构造方法。
装饰器只能用于类,要么应用于类的整体,要么应用于类的内部成员,不能用于独立的函数
二、自定义组件
🔊:自定义组件必须使用struct定义,并且被Component装饰器修饰
在arkTs中,自定义组件分为两种:
- 根组件:就是被装饰器@Entry装饰的入口组件,这也是自定义组件(父组件)。
// 根组件(父组件) @Entry @Component struct FatherComponent {}
- 子组件:没有被@Entry装饰的自定义组件,只有@Component装饰器(子组件)。
// 子组件 @Component struct SonComponent {}
❗️注意:1. 子组件必须被父组件调用,才能在页面上展示出来,它自己无法展示。页面由一个父组件和无数个子组件、系统组件构成。
2. 如果在另外的文件中引用该自定义组件,需要使用export关键字导出,并在使用的页面import该自定义组件。
自定义组件的基本结构
-
struct:自定义组件基于struct实现,struct + 自定义组件名 + {...}的组合构成自定义组件,不能有继承关系。对于struct的实例化,可以省略new。
🍀说明:自定义组件名、类名、函数名不能和系统组件名相同。 -
@Component:@Component装饰器仅能装饰struct关键字声明的数据结构。struct被@Component装饰后具备组件化的能力,需要实现build方法描述UI,一个struct只能被一个@Component装饰。@Component可以接受一个可选的bool类型参数。
@Component struct MyComponent { build() { //描述UI } }
🍀说明:从API version 11开始,@Component可以接受一个可选的bool类型参数。
自定义组件处于非激活状态时,状态变量将不响应更新,即@Watch不会调用,状态变量关联的节点不会刷新。通过freezeWhenInactive属性来决定是否使用冻结功能,不传参数时默认不使用。支持的场景有:页面路由,TabContent,LazyForEach,Navigation。
@Component({freezeWhenInactive: true}) struct MyComponent { build() { //描述UI } }
-
build()函数:build()函数用于定义自定义组件的声明式UI描述,自定义组件必须定义build()函数。
@Component struct MyComponent { build() { //描述UI } }
-
@Entry:@Entry装饰的自定义组件将作为UI页面的入口。在单个UI页面中,最多可以使用@Entry装饰一个自定义组件。@Entry可以接受一个可选的LocalStorage的参数。
@Entry @Component struct MyComponent { build() { //描述UI } }
🍀说明:从API version 10开始,@Entry可以接受一个可选的LocalStorage的参数或者一个可选的EntryOptions参数。
@Entry({ routeName : 'myPage' }) @Component struct MyComponent { build() { //描述UI } }
-
@Reusable:@Reusable装饰的自定义组件具备可复用能力
🍀说明:从API version 10开始,该装饰器支持在ArkTS卡片中使用。
三、页面和自定义组件生命周期
在开始之前,我们先明确自定义组件和页面的关系:
-
自定义组件:@Component装饰的UI单元,可以组合多个系统组件实现UI的复用,可以调用组件的生命周期。
-
页面:即应用的UI页面。可以由一个或者多个自定义组件组成,@Entry装饰的自定义组件为页面的入口组件,即页面的根节点,一个页面有且仅能有一个@Entry。只有被@Entry装饰的组件才可以调用页面的生命周期。
⏰ 页面生命周期,即被@Entry装饰的组件生命周期,提供以下生命周期接口:
-
onPageShow:页面每次显示时触发一次,包括路由过程、应用进入前台等场景。
-
onPageHide:页面每次隐藏时触发一次,包括路由过程、应用进入后台等场景。
-
onBackPress:当用户点击返回按钮时触发。
⏰ 组件生命周期,即一般用@Component装饰的自定义组件的生命周期,提供以下生命周期接口:
-
aboutToAppear:组件即将出现时回调该接口,具体时机为在创建自定义组件的新实例后,在执行其build()函数之前执行。
-
aboutToDisappear:aboutToDisappear函数在自定义组件析构销毁之前执行。不允许在aboutToDisappear函数中改变状态变量,特别是@Link变量的修改可能会导致应用程序行为不稳定。
⏰ 生命周期流程图,下图展示的是被@Entry装饰的组件(页面)生命周期。
示例:
index.ets
// Index.ets import router from '@ohos.router'; @Entry @Component struct MyComponent { @State showChild: boolean = true; @State btnColor:string = "#FF007DFF" // 只有被@Entry装饰的组件才可以调用页面的生命周期 onPageShow() { console.info('Index onPageShow'); } // 只有被@Entry装饰的组件才可以调用页面的生命周期 onPageHide() { console.info('Index onPageHide'); } // 只有被@Entry装饰的组件才可以调用页面的生命周期 onBackPress() { console.info('Index onBackPress'); this.btnColor ="#FFEE0606" return true // 返回true表示页面自己处理返回逻辑,不进行页面路由;返回false表示使用默认的路由返回逻辑,不设置返回值按照false处理 } // 组件生命周期 aboutToAppear() { console.info('MyComponent aboutToAppear'); } // 组件生命周期 aboutToDisappear() { console.info('MyComponent aboutToDisappear'); } build() { Column() { // this.showChild为true,创建Child子组件,执行Child aboutToAppear if (this.showChild) { Child() } // this.showChild为false,删除Child子组件,执行Child aboutToDisappear Button('delete Child') .margin(20) .backgroundColor(this.btnColor) .onClick(() => { this.showChild = false; }) // push到page页面,执行onPageHide Button('push to next page') .onClick(() => { router.pushUrl({ url: 'pages/page' }); }) } } } @Component struct Child { @State title: string = 'Hello World'; // 组件生命周期 aboutToDisappear() { console.info('[lifeCycle] Child aboutToDisappear') } // 组件生命周期 aboutToAppear() { console.info('[lifeCycle] Child aboutToAppear') } build() { Text(this.title).fontSize(50).margin(20).onClick(() => { this.title = 'Hello ArkUI'; }) } }
page.ets
// page.ets @Entry @Component struct page { @State textColor: Color = Color.Black; @State num: number = 0 onPageShow() { this.num = 5 } onPageHide() { console.log("page onPageHide"); } onBackPress() { // 不设置返回值按照false处理 this.textColor = Color.Grey this.num = 0 } aboutToAppear() { this.textColor = Color.Blue } build() { Column() { Text(`num 的值为:${this.num}`) .fontSize(30) .fontWeight(FontWeight.Bold) .fontColor(this.textColor) .margin(20) .onClick(() => { this.num += 5 }) } .width('100%') } }
以上示例中,Index页面包含两个自定义组件,一个是被@Entry装饰的MyComponent,也是页面的入口组件,即页面的根节点;一个是Child,是MyComponent的子组件。只有@Entry装饰的节点才可以使页面级别的生命周期方法生效,因此在MyComponent中声明当前Index页面的页面生命周期函数(onPageShow / onPageHide / onBackPress)。MyComponent和其子组件Child分别声明了各自的组件级别生命周期函数(aboutToAppear / aboutToDisappear)。
-
应用冷启动的初始化流程为:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build执行完毕 --> MyComponent build执行完毕 --> Index onPageShow。
-
点击“delete Child”,if绑定的this.showChild变成false,删除Child组件,会执行Child aboutToDisappear方法。
-
点击“push to next page”,调用router.pushUrl接口,跳转到另外一个页面,当前Index页面隐藏,执行页面生命周期Index onPageHide。此处调用的是router.pushUrl接口,Index页面被隐藏,并没有销毁,所以只调用onPageHide。跳转到新页面后,执行初始化新页面的生命周期的流程。
-
如果调用的是router.replaceUrl,则当前Index页面被销毁,执行的生命周期流程将变为:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已经提到,组件的销毁是从组件树上直接摘下子树,所以先调用父组件的aboutToDisappear,再调用子组件的aboutToDisappear,然后执行初始化新页面的生命周期流程。
-
点击返回按钮,触发页面生命周期Index onBackPress,且触发返回一个页面后会导致当前Index页面被销毁。
-
最小化应用或者应用进入后台,触发Index onPageHide。当前Index页面没有被销毁,所以并不会执行组件的aboutToDisappear。应用回到前台,执行Index onPageShow。
-
退出应用,执行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
四、自定义组件的自定义布局
如果需要通过测算的方式布局自定义组件内子组件的位置,建议使用以下接口:
-
onMeasureSize:组件每次布局时触发,计算子组件的尺寸,其执行时间先于onPlaceChildren。
-
onPlaceChildren:组件每次布局时触发,设置子组件的起始位置。
示例:
// xxx.ets @Entry @Component struct Index { build() { Column() { CustomLayout({ builder: ColumnChildren }) } } } // 通过builder的方式传递多个组件,作为自定义组件的一级子组件(即不包含容器组件,如Column) @Builder function ColumnChildren() { ForEach([1, 2, 3], (index: number) => { // 暂不支持lazyForEach的写法 Text('S' + index) .fontSize(30) .width(100) .height(100) .borderWidth(2) .offset({ x: 10, y: 20 }) }) } @Component struct CustomLayout { /** 属性 */ @State startSize: number = 100; @BuilderParam builder: () => void = this.doNothingBuilder; private result: SizeResult = { width: 0, height: 0 }; /** 自定义构建函数 */ @Builder doNothingBuilder() { }; /** 自定义布局 */ // 第一步:计算各子组件的大小 onMeasureSize(selfLayoutInfo: GeometryInfo, children: Array<Measurable>, constraint: ConstraintSizeOptions) { let size = 100; children.forEach((child) => { let result: MeasureResult = child.measure({ minHeight: size, minWidth: size, maxWidth: size, maxHeight: size }) size += result.width / 2; }) this.result.width = 100; this.result.height = 400; return this.result; } // 第二步:放置各子组件的位置 onPlaceChildren(selfLayoutInfo: GeometryInfo, children: Array<Layoutable>, constraint: ConstraintSizeOptions) { let startPos = 300; children.forEach((child) => { let pos = startPos - child.measureResult.height; child.layout({ x: pos, y: pos }) }) } build() { this.builder() } }
以上示例中,Index页面包含一个实现了自定义布局的自定义组件,且对应自定义组件的子组件通过index页面内的builder方式传入。
而在自定义组件中,调用了onMeasureSize和onPlaceChildren设置子组件大小和放置位置。例如,在本示例中,在onMeasureSize中初始化组件大小size=100,后续的每一个子组件size会加上上一个子组件大小的一半,实现组件大小递增的效果。而在onPlaceChildren中,定义startPos=300,设置每一个子组件的位置为startPos减去子组件自身的高度,所有子组件右下角一致在顶点位置(300,300),实现一个从右下角开始展示组件的类Stack组件。
五、自定义组件冻结功能场景
页面路由
-
当页面A调用router.pushUrl接口跳转到页面B时,页面A为隐藏不可见状态,此时如果更新页面A中的状态变量,不会触发页面A刷新。
-
当应用退到后台运行时无法被冻结。
⏰ 页面A:
import router from '@ohos.router'; @Entry @Component({ freezeWhenInactive: true }) struct FirstTest { /** 状态变量 */ @StorageLink('PropA') @Watch("first") storageLink: number = 47; /** 自定义方法 */ private first() { console.info("first page " + `${this.storageLink}`) } /** 系统构建函数 */ build() { Column({space:10}) { Text(`From fist Page ${this.storageLink}`) .fontSize(30) Button('first page storageLink + 1') .fontSize(18) .onClick(() => { this.storageLink += 1 }) Button('go to next page') .fontSize(18) .onClick(() => { router.pushUrl({ url: 'pages/second' }) }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }
⏰ 页面B:
import router from '@ohos.router'; @Entry @Component({ freezeWhenInactive: true }) struct SecondTest { /** 状态变量 */ @StorageLink('PropA') @Watch("second") storageLink2: number = 1; /** 自定义函数 */ private second() { console.info("second page: " + `${this.storageLink2}`) } build() { Column({ space: 10 }) { Text(`second Page ${this.storageLink2}`).fontSize(30) Button('Change Divider.strokeWidth') .fontSize(18) .onClick(() => { router.back() }) Button('second page storageLink2 + 2') .fontSize(18) .onClick(() => { this.storageLink2 += 2 }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) } }
结果:
-
当页面A 设置 @Component({ freezeWhenInactive: false }) ,点击页面B 更改 ‘second page storeageLink2’ 按钮时
-
当页面A 设置 @Component({ freezeWhenInactive: true }) ,点击页面B 更改 ‘second page storeageLink2’ 按钮时
在上面的示例中:
1. 点击页面A中的Button “first page storageLink + 1”,storLink状态变量改变,@Watch中注册的方法first会被调用。
2. 通过router.pushUrl({url: 'pages/second'}),跳转到页面B,页面A隐藏,状态由active变为inactive。
3. 点击页面B中的Button “this.storageLink2 += 2”,只回调页面B@Watch中注册的方法second,因为页面A的状态变量此时已被冻结。
4. 点击“back”,页面B被销毁,页面A的状态由inactive变为active,重新刷新在inactive时被冻结的状态变量,页面A @Watch中注册的方法first被再次调用。
TabContent
-
对Tabs中当前不可见的TabContent进行冻结,不会触发组件的更新。
-
需要注意的是:在首次渲染的时候,Tab只会创建当前正在显示的TabContent,当切换全部的TabContent后,TabContent才会被全部创建。
⏰ 示例
@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') .margin({ top: 10 }) .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) } }
结果:
-
当设置自定义组件 FreezeChild 设置 @Component({ freezeWhenInactive: false }) 时
-
当设置自定义组件 FreezeChild 设置 @Component({ freezeWhenInactive: true }) 时
在上面的示例中:
1. 点击“change message”更改message的值,当前正在显示的TabContent组件中的@Watch中注册的方法onMessageUpdated被触发。
2. 点击“two”切换到另外的TabContent,TabContent状态由inactive变为active,对应的@Watch中注册的方法onMessageUpdated被触发。
3. 再次点击“change message”更改message的值,仅当前显示的TabContent子组件中的@Watch中注册的方法onMessageUpdated被触发。
LazyforEach
对LazyforEach中缓存的自定义组件进行冻结,不会触发组件的更新。
⏰ 示例
class BasicDataSource implements IDataSource { private listeners: DataChangeListener[] = []; private originDataArray: string[] = []; public totalCount(): number { return 0; } public getData(index: number): string { return this.originDataArray[index]; } // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 registerDataChangeListener(listener: DataChangeListener): void { if (this.listeners.indexOf(listener) < 0) { console.info('add listener'); this.listeners.push(listener); } } // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 unregisterDataChangeListener(listener: DataChangeListener): void { const pos = this.listeners.indexOf(listener); if (pos >= 0) { console.info('remove listener'); 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); }) } } class MyDataSource extends BasicDataSource { private dataArray: string[] = []; public totalCount(): number { return this.dataArray.length; } public getData(index: number): string { return this.dataArray[index]; } public addData(index: number, data: string): void { this.dataArray.splice(index, 0, data); this.notifyDataAdd(index); } public pushData(data: string): void { this.dataArray.push(data); this.notifyDataAdd(this.dataArray.length - 1); } } @Entry @Component struct LforEachTest { /** 状态变量 */ @State @Watch("onMessageUpdated") message: number = 0; /** 私有变量 */ private data: MyDataSource = new MyDataSource(); /** 自定义函数 */ 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 = ""; /** 自定义函数 */ onMessageUpdated() { console.info(`FreezeChild message callback func ${this.message}, index: ${this.index}`) } /** 生命周期函数 */ aboutToAppear() { console.info(`FreezeChild aboutToAppear 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) } }
结果:
在上面的示例中:
1.点击“change message”更改message的值,当前正在显示的ListItem中的子组件@Watch中注册的方法onMessageUpdated被触发。缓存节点@Watch中注册的方法不会被触发。(如果不加组件冻结,当前正在显示的ListItem和cachecount缓存节点@Watch中注册的方法onMessageUpdated都会触发watch回调。)
2.List区域外的ListItem滑动到List区域内,状态由inactive变为active,对应的@Watch中注册的方法onMessageUpdated被触发。
3.再次点击“change message”更改message的值,仅有当前显示的ListItem中的子组件@Watch中注册的方法onMessageUpdated被触发。
Navigation
对当前不可见的页面进行冻结,不会触发组件的更新,当返回该页面时,触发@Watch回调进行刷新。
⏰ 示例
@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' }); //将name指定的NavDestination页面信息入栈 }) } }.title('NavIndex') .navDestination(this.PageMap) .mode(NavigationMode.Stack) } } } @Component struct pageOneStack { /** 状态变量 */ @Consume('pageInfo') pageInfo: NavPathStack; @State index: number = 1; @Link message: number; @Link logNumber: number; /** 系统构建函数 */ 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; }) } } @Component struct pageTwoStack { /** 状态变量 */ @Consume('pageInfo') pageInfo: NavPathStack; @State index: number = 2; @Link message: number; @Link logNumber: number; /** 系统构建函数 */ 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('pageThree', null); }) Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { this.pageInfo.pop(); }) }.width('100%').height('100%') }.title('pageTwo') .onBackPressed(() => { this.pageInfo.pop(); return true; }) } } @Component struct pageThreeStack { /** 状态变量 */ @Consume('pageInfo') pageInfo: NavPathStack; @State index: number = 3; @Link message: number; @Link logNumber: number; /** 系统构建函数 */ 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('pageOne', null); }) Button('Back Page', { stateEffect: true, type: ButtonType.Capsule }) .width('80%') .height(40) .margin(20) .onClick(() => { this.pageInfo.pop(); }) }.width('100%').height('100%') }.title('pageThree') .onBackPressed(() => { this.pageInfo.pop(); return true; }) } } @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) } } }
结果:

在上面的示例中:
1. 点击“change message”更改message的值,当前正在显示的MyNavigationTestStack组件中的@Watch中注册的方法info被触发。
2. 点击“Next Page”切换到PageOne,创建pageOneStack节点。
3. 再次点击“change message”更改message的值,仅pageOneStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。
4. 再次点击“Next Page”切换到PageTwo,创建pageTwoStack节点。
5. 再次点击“change message”更改message的值,仅pageTwoStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。
6. 再次点击“Next Page”切换到PageThree,创建pageThreeStack节点。
7. 再次点击“change message”更改message的值,仅pageThreeStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。
8. 点击“Back Page”回到PageTwo,此时,仅pageTwoStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。
9. 再次点击“Back Page”回到PageOne,此时,仅pageOneStack中的NavigationContentMsgStack子组件中的@Watch中注册的方法info被触发。
10. 再次点击“Back Page”回到初始页,此时,无任何触发。