HarmonyOS NEXT - 其他状态管理概述 @Watch装饰器:状态变量更改通知

除了前面章节提到的组件状态管理和应用状态管理,ArkTS还提供了@Watch、$$运算符和@Track来为开发者提供更多功能:

  • @Watch:用于监听状态变量的变化。

  • $$运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。

  • @Track:应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。

  • 自定义组件冻结:当自定义组件处于非激活状态时,状态变量将不响应更新。

@Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。

说明

从API version 9开始,该装饰器支持在ArkTS卡片中使用。

从API version 11开始,该装饰器支持在元服务中使用。

概述

@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。

装饰器说明

@Watch补充变量装饰器说明
装饰器参数必填。常量字符串,字符串需要有引号。是(string) => void自定义成员函数的方法的引用。
可装饰的自定义组件变量可监听所有装饰器装饰的状态变量。不允许监听常规变量。
装饰器的顺序建议@State@Prop@Link等装饰器在@Watch装饰器之前。

语法说明

类型说明
(changedPropertyName? : string) => void

该函数是自定义组件的成员函数,changedPropertyName是被watch的属性名。

在多个状态变量绑定同一个@Watch的回调方法的时候,可以通过changedPropertyName进行不同的逻辑处理

将属性名作为字符串输入参数,不返回任何内容。

观察变化和行为表现

  1. 当观察到状态变量的变化(包括双向绑定的AppStorageLocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发;

  2. @Watch方法在自定义组件的属性变更之后同步执行;

  3. 如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;

  4. 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法。

限制条件

  • 建议开发者避免无限循环。循环可能是因为在@Watch的回调方法里直接或者间接地修改了同一个状态变量引起的。为了避免循环的产生,建议不要在@Watch的回调方法里修改当前装饰的状态变量;

  • 开发者应关注性能,属性值更新函数会延迟组件的重新渲染(具体请见上面的行为表现),因此,回调函数应仅执行快速运算;

  • 不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题。

使用场景

@Watch和自定义组件更新

以下示例展示组件更新和@Watch的处理步骤。count在CountModifier中由@State装饰,在TotalView中由@Prop装饰。

@Component
struct TotalView {
  @Prop @Watch('onCountUpdated') count: number = 0;
  @State total: number = 0;
  // @Watch 回调
  onCountUpdated(propName: string): void {
    this.total += this.count;
  }

  build() {
    Text(`Total: ${this.total}`)
  }
}

@Entry
@Component
struct CountModifier {
  @State count: number = 0;

  build() {
    Column() {
      Button('add to basket')
        .onClick(() => {
          this.count++
        })
      TotalView({ count: this.count })
    }
  }
}

处理步骤:

  1. CountModifier自定义组件的Button.onClick点击事件自增count。

  2. 由于@State count变量更改,子组件TotalView中的@Prop被更新,其@Watch('onCountUpdated')方法被调用,更新了子组件TotalView 中的total变量。

  3. 子组件TotalView中的Text重新渲染。

@Watch与@Link组合使用

以下示例说明了如何在子组件中观察@Link变量。

class PurchaseItem {
  static NextId: number = 0;
  public id: number;
  public price: number;

  constructor(price: number) {
    this.id = PurchaseItem.NextId++;
    this.price = price;
  }
}

@Component
struct BasketViewer {
  @Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[];
  @State totalPurchase: number = 0;

  updateTotal(): number {
    let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0);
    // 超过100欧元可享受折扣
    if (total >= 100) {
      total = 0.9 * total;
    }
    return total;
  }
  // @Watch 回调
  onBasketUpdated(propName: string): void {
    this.totalPurchase = this.updateTotal();
  }

  build() {
    Column() {
      ForEach(this.shopBasket,
        (item: PurchaseItem) => {
          Text(`Price: ${item.price.toFixed(2)} €`)
        },
        (item: PurchaseItem) => item.id.toString()
      )
      Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
    }
  }
}

@Entry
@Component
struct BasketModifier {
  @State shopBasket: PurchaseItem[] = [];

  build() {
    Column() {
      Button('Add to basket')
        .onClick(() => {
          this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
        })
      BasketViewer({ shopBasket: $shopBasket })
    }
  }
}

处理步骤如下:

  1. BasketModifier组件的Button.onClick向BasketModifier shopBasket中添加条目;

  2. @Link装饰的BasketViewer shopBasket值发生变化;

  3. 状态管理框架调用@Watch函数BasketViewer onBasketUpdated 更新BasketViewer TotalPurchase的值;

  4. @Link shopBasket的改变,新增了数组项,ForEach组件会执行item Builder,渲染构建新的Item项;@State totalPurchase改变,对应的Text组件也重新渲染;重新渲染是异步发生的。

$$语法:内置组件双向同步 

$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。

内部状态具体指什么取决于组件。例如,TextInput组件的text参数。

说明

$$还用于@Builder装饰器的按引用传递参数,开发者需要注意两种用法的区别。

使用规则

使用示例

TextInput方法的text参数为例:

// xxx.ets
@Entry
@Component
struct TextInputExample {
  @State text: string = ''
  controller: TextInputController = new TextInputController()

  build() {
    Column({ space: 20 }) {
      Text(this.text)
      TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
        .placeholderColor(Color.Grey)
        .placeholderFont({ size: 14, weight: 400 })
        .caretColor(Color.Blue)
        .width(300)
    }.width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

 @Track装饰器:class对象属性级更新

@Track应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。

说明

从API version 11开始,该装饰器支持在ArkTS卡片中使用。

概述

@Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;而未被标记的属性不能在UI中使用。

装饰器说明

@Track变量装饰器说明
装饰器参数
可装饰的变量class对象的非静态成员属性。

观察变化和行为表现

当一个class对象是状态变量时,@Track装饰的属性发生变化,该属性关联的UI触发更新。

说明

当class对象中没有一个属性被标记@Track,行为与原先保持不变。@Track没有深度观测的功能。

使用@Track装饰器可以避免冗余刷新。

class LogTrack {
  @Track str1: string;
  @Track str2: string;

  constructor(str1: string) {
    this.str1 = str1;
    this.str2 = 'World';
  }
}

class LogNotTrack {
  str1: string;
  str2: string;

  constructor(str1: string) {
    this.str1 = str1;
    this.str2 = '世界';
  }
}

@Entry
@Component
struct AddLog {
  @State logTrack: LogTrack = new LogTrack('Hello');
  @State logNotTrack: LogNotTrack = new LogNotTrack('你好');

  isRender(index: number) {
    console.log(`Text ${index} is rendered`);
    return 50;
  }

  build() {
    Row() {
      Column() {
        Text(this.logTrack.str1) // UINode1
          .fontSize(this.isRender(1))
          .fontWeight(FontWeight.Bold)
        Text(this.logTrack.str2) // UINode2
          .fontSize(this.isRender(2))
          .fontWeight(FontWeight.Bold)
        Button('change logTrack.str1')
          .onClick(() => {
            this.logTrack.str1 = 'Bye';
          })
        Text(this.logNotTrack.str1) // UINode3
          .fontSize(this.isRender(3))
          .fontWeight(FontWeight.Bold)
        Text(this.logNotTrack.str2) // UINode4
          .fontSize(this.isRender(4))
          .fontWeight(FontWeight.Bold)
        Button('change logNotTrack.str1')
          .onClick(() => {
            this.logNotTrack.str1 = '再见';
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

在上面的示例中:

  1. 类LogTrack中的属性均被@Track装饰器装饰,点击按钮"change logTrack.str1",此时UINode1刷新,UINode2不刷新,只有一条日志输出,避免了冗余刷新。

    Text 1 is rendered
    

  2. 类logNotTrack中的属性均未被@Track装饰器装饰,点击按钮"change logNotTrack.str1",此时UINode3、UINode4均会刷新,有两条日志输出,存在冗余刷新。
     

    Text 3 is rendered
    Text 4 is rendered

限制条件

  • 不能在UI中使用非@Track装饰的属性,包括不能绑定在组件上、不能用于初始化子组件,错误的使用将导致JSCrash;可以在非UI中使用非@Track装饰的属性,如事件回调函数中、生命周期函数中等。

  • 建议开发者不要混用包含@Track的class对象和不包含@Track的class对象,如联合类型中、类继承中等。

使用场景

@Track和自定义组件更新

以下示例展示组件更新和@Track的处理步骤。对象log是@State装饰的状态变量,logInfo是@Track的成员属性,其余成员属性都是非@Track装饰的,而且也不准备在UI中更新它们的值。

class Log {
  @Track logInfo: string;
  owner: string;
  id: number;
  time: Date;
  location: string;
  reason: string;

  constructor(logInfo: string) {
    this.logInfo = logInfo;
    this.owner = 'OH';
    this.id = 0;
    this.time = new Date();
    this.location = 'CN';
    this.reason = 'NULL';
  }
}

@Entry
@Component
struct AddLog {
  @State log: Log = new Log('origin info.');

  build() {
    Row() {
      Column() {
        Text(this.log.logInfo)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            // The properties without @Track can be used in the event handler.
            console.log('owner: ' + this.log.owner +
              ' id: ' + this.log.id +
              ' time: ' + this.log.time +
              ' location: ' + this.log.location +
              ' reason: ' + this.log.reason);
            this.log.time = new Date();
            this.log.id++;

            this.log.logInfo += ' info.';
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

处理步骤:

  1. AddLog自定义组件的Text.onClick点击事件自增字符串' info.'。

  2. 由于@State log变量的@Track属性logInfo更改,Text重新渲染。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值