harmony 鸿蒙\@Computed装饰器:计算属性

  • 2025-06-16
  • 浏览 (3)

\@Computed装饰器:计算属性

\@Computed装饰器:计算属性,当被计算的属性变化时,只会计算一次。这解决了UI多次重用该属性导致的重复计算和性能问题。

状态变量的变化可以触发其关联\@Computed的重新计算。在阅读本文档前,建议提前阅读:\@ComponentV2\@ObservedV2和\@Trace\@Local

说明:

\@Computed装饰器从API version 12开始支持。

概述

\@Computed为方法装饰器,装饰getter方法。\@Computed会检测被计算的属性变化,当被计算的属性变化时,\@Computed只会被求解一次。 对于复杂的计算,\@Computed会有性能收益。

装饰器说明

\@Computed语法:

@Computed
get varName(): T {
    return value;
}
\@Computed方法装饰器 说明
支持类型 getter访问器。
从父组件初始化 禁止。
可初始化子组件 \@Param。
被执行的时机 \@ComponentV2被初始化时,计算属性会被触发计算。当被计算的值发生变化时,计算属性会重新计算。
是否允许赋值 @Computed装饰的属性是只读的,不允许赋值,详情见使用限制

使用限制

  • \@Computed为方法装饰器,仅能装饰getter方法。
  @Computed
  get fullName() { // 正确用法
    return this.firstName + ' ' + this.lastName;
  }
  @Computed val: number = 0; // 错误用法,编译时报错
  @Computed
  func() { // 错误用法,编译时报错
  }
  • 在\@Computed装饰的getter方法中,不能改变参与计算的属性。
  @Computed
  get fullName() {
    this.lastName += 'a'; // 错误,不能改变参与计算的属性
    return this.firstName + ' ' + this.lastName;
  }
  • \@Computed不能和双向绑定!!连用,\@Computed装饰的是getter访问器,不会被子组件同步,也不能被赋值。开发者自己实现的计算属性的setter不生效,且产生编译时报错。
  @ComponentV2
  struct Child {
    @Param double: number = 100;
    @Event $double: (val: number) => void;
  
    build() {
      Button('ChildChange')
        .onClick(() => {
          this.$double(200);
        })
    }
  }
  
  @Entry
  @ComponentV2
  struct Index {
    @Local count: number = 100;
  
    @Computed
    get double() {
      return this.count * 2;
    }
  
    // @Computed装饰的属性是只读的,开发者自己实现的setter不生效,且产生编译时报错
    set double(newValue : number) {
      this.count = newValue / 2;
    }
  
    build() {
      Scroll() {
        Column({ space: 3 }) {
          Text(`${this.count}`)
          // 错误写法,@Computed装饰的属性是只读的,无法与双向绑定连用。
          Child({ double: this.double!! })
        }
      }
    }
  }
  • \@Computed为状态管理V2提供的能力,只能在\@ComponentV2和\@ObservedV2中使用。
  • 多个\@Computed一起使用时,警惕循环求解,以防止计算过程中的死循环。
  @Local a : number = 1;
  @Computed
  get b() {
    return this.a + ' ' + this.c;  // 错误写法,存在循环b -> c -> b
  }
  @Computed
  get c() {
    return this.a + ' ' + this.b; // 错误写法,存在循环c -> b -> c
  }

使用场景

当被计算的属性变化时,\@Computed装饰的getter访问器只会被求解一次

  1. 在自定义组件中使用计算属性。

  2. 点击第一个Button改变lastName,触发\@Computed fullName重新计算。

  3. this.fullName被绑定在两个Text组件上,观察fullName日志,可以发现,计算只发生了一次。

  4. 对于前两个Text组件,this.lastName + ' '+ this.firstName这段逻辑被求解了两次。

  5. 如果UI中有多处需要使用this.lastName + ' '+ this.firstName这段计算逻辑,可以使用计算属性,减少计算次数。

  6. 点击第二个Button,age自增,UI无变化。因为age非状态变量,只有被观察到的变化才会触发\@Computed fullName重新计算。

@Entry
@ComponentV2
struct Index {
  @Local firstName: string = 'Li';
  @Local lastName: string = 'Hua';
  age: number = 20; // 无法触发Computed

  @Computed
  get fullName() {
    console.info("---------Computed----------");
    return this.firstName + ' ' + this.lastName + this.age;
  }

  build() {
    Column() {
      Text(this.lastName + ' ' + this.firstName)
      Text(this.lastName + ' ' + this.firstName)
      Divider()
      Text(this.fullName)
      Text(this.fullName)
      Button('changed lastName').onClick(() => {
        this.lastName += 'a';
      })

      Button('changed age').onClick(() => {
        this.age++;  // 无法触发Computed
      })
    }
  }
}

计算属性本身会带来性能开销,在实际应用开发中需要注意: - 对于简单的计算逻辑,可以不使用计算属性。 - 如果计算逻辑在视图中仅使用一次,则不使用计算属性,直接求解。

  1. 在\@ObservedV2装饰的类中使用计算属性。
  2. 点击Button改变lastName,触发\@Computed fullName重新计算,且只被计算一次。
@ObservedV2
class Name {
  @Trace firstName: string = 'Li';
  @Trace lastName: string = 'Hua';

  @Computed
  get fullName() {
    console.info('---------Computed----------');
    return this.firstName + ' ' + this.lastName;
  }
}

const name: Name = new Name();

@Entry
@ComponentV2
struct Index {
  name1: Name = name;

  build() {
    Column() {
      Text(this.name1.fullName)
      Text(this.name1.fullName)
      Button('changed lastName').onClick(() => {
        this.name1.lastName += 'a';
      })
    }
  }
}

\@Computed装饰的属性可以被\@Monitor监听变化

如何使用计算属性求解fahrenheit和kelvin。示例如下: - 点击“-”,celsius– -> fahrenheit -> kelvin –> kelvin变化时调用onKelvinMonitor。 - 点击“+”,celsius++ -> fahrenheit -> kelvin –> kelvin变化时调用onKelvinMonitor。

@Entry
@ComponentV2
struct MyView {
  @Local celsius: number = 20;

  @Computed
  get fahrenheit(): number {
    return this.celsius * 9 / 5 + 32; // C -> F
  }

  @Computed
  get kelvin(): number {
    return (this.fahrenheit - 32) * 5 / 9 + 273.15; // F -> K
  }

  @Monitor("kelvin")
  onKelvinMonitor(mon: IMonitor) {
    console.log("kelvin changed from " + mon.value()?.before + " to " + mon.value()?.now);
  }

  build() {
    Column({ space: 20 }) {
      Row({ space: 20 }) {
        Button('-')
          .onClick(() => {
            this.celsius--;
          })

        Text(`Celsius ${this.celsius.toFixed(1)}`).fontSize(50)

        Button('+')
          .onClick(() => {
            this.celsius++;
          })
      }

      Text(`Fahrenheit ${this.fahrenheit.toFixed(2)}`).fontSize(50)
      Text(`Kelvin ${this.kelvin.toFixed(2)}`).fontSize(50)
    }
    .width('100%')
  }
}

\@Computed装饰的属性可以初始化\@Param

下面的例子使用\@Computed初始化\@Param。 - 点击Button('-')Button('+')改变商品数量,quantity是被\@Trace装饰的,其改变时可以被观察到的。 - quantity的改变会触发totalqualifiesForDiscount重新计算,计算商品总价和是否可以享有优惠。 - totalqualifiesForDiscount的改变会触发子组件Child对应Text组件刷新。

@ObservedV2
class Article {
  @Trace quantity: number = 0;
  unitPrice: number = 0;

  constructor(quantity: number, unitPrice: number) {
    this.quantity = quantity;
    this.unitPrice = unitPrice;
  }
}

@Entry
@ComponentV2
struct Index {
  @Local shoppingBasket: Article[] = [new Article(1, 20), new Article(5, 2)];

  @Computed
  get total(): number {
    return this.shoppingBasket.reduce((acc: number, item: Article) => acc + (item.quantity * item.unitPrice), 0);
  }

  @Computed
  get qualifiesForDiscount(): boolean {
    return this.total >= 100;
  }

  build() {
    Column() {
      Text(`Shopping List: `).fontSize(30)
      ForEach(this.shoppingBasket, (item: Article) => {
        Row() {
          Text(`unitPrice: ${item.unitPrice}`)
          Button('-').onClick(() => {
            if (item.quantity > 0) {
              item.quantity--;
            }
          })
          Text(`quantity: ${item.quantity}`)
          Button('+').onClick(() => {
            item.quantity++;
          })
        }

        Divider()
      })
      Child({ total: this.total, qualifiesForDiscount: this.qualifiesForDiscount })
    }.alignItems(HorizontalAlign.Start)
  }
}

@ComponentV2
struct Child {
  @Param total: number = 0;
  @Param qualifiesForDiscount: boolean = false;

  build() {
    Row() {
      Text(`Total: ${this.total} `).fontSize(30)
      Text(`Discount: ${this.qualifiesForDiscount} `).fontSize(30)
    }
  }
}

你可能感兴趣的鸿蒙文章

harmony 鸿蒙\@AnimatableExtend装饰器:定义可动画属性

harmony 鸿蒙管理应用拥有的状态概述

harmony 鸿蒙AppStorage:应用全局的UI状态存储

harmony 鸿蒙基本语法概述

harmony 鸿蒙\@Builder装饰器:自定义构建函数

harmony 鸿蒙\@BuilderParam装饰器:引用\@Builder函数

harmony 鸿蒙创建自定义组件

harmony 鸿蒙自定义组件混用场景指导

harmony 鸿蒙自定义组件成员属性访问限定符使用限制

harmony 鸿蒙自定义组件冻结功能

0  赞