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”回到初始页,此时,无任何触发。

posted on 2024-04-01 19:21  梁飞宇  阅读(3063)  评论(0)    收藏  举报