一、引言

在鸿蒙(HarmonyOS)应用开发中,ArkTS 框架提供了一套强大的响应式编程模型。其中,@State 装饰器是实现组件内部状态管理、驱动 UI 自动刷新的核心工具。本文将深入浅出地讲解 @State 的使用方法、监听机制以及最佳实践,帮助你快速掌握这一关键特性。

二、什么是 @State

@State 是 HarmonyOS ArkTS 框架中用于管理组件内部状态的核心装饰器,它实现了数据驱动 UI 的响应式编程。被 @State 修饰的变量变化时,依赖该变量的 UI 组件会自动重新渲染,保持数据与界面的同步。

其底层基于依赖收集变更通知机制:组件渲染时,框架会收集 @State 变量的 UI 依赖关系;当变量被修改时,框架会通知所有依赖方执行最小化更新,从而高效地刷新界面。

三、监听基本数据类型

对于 booleanstringnumber 等基础类型,直接赋值即可被框架观察到并触发 UI 刷新。

@Entry
@Component
struct BasicTypeDemo {
  @State count: number = 0;
  @State name: string = 'Hello';

  build() {
    Column({space: 20}) {
      Text(`Count: ${this.count}`).fontSize(30)
      Text(`Name: ${this.name}`).fontSize(30)
      Button('Increment')
        .onClick(() => {
          this.count++;   // 可观察,触发UI刷新
        })
      Button('Change Name')
        .onClick(() => {
          this.name = 'ArkTS';   // 可观察,触发UI刷新
        })
    }
  }
}

运行效果如下:

在这里插入图片描述

四、监听对象类型

@State 可以观察到对象自身的赋值以及属性赋值的变化,但需要注意:必须通过赋值操作修改对象或其属性,直接修改嵌套子属性(第二层及以下)@State 无法监测。

interface IUser {
  name: string
  age: number
}

@Entry
@Component
struct ObjectDemo {
  @State user:IUser = { name: 'Alice', age: 25 };

  build() {
    Column({space: 20}) {
      Text(`Name: ${this.user.name}`).fontSize(20)
      Text(`Age: ${this.user.age}`).fontSize(20)
      Button('Update Age')
        .onClick(() => {
          // ✅ 属性赋值,可观察
          this.user.age++
          this.user.name = "Beer"
        })
      Button('Replace Object')
        .onClick(() => {
          // ✅ 整体赋值,可观察
          this.user = { name: 'Bob', age: 30 };
        })
    }
  }
}

五、监听嵌套对象

示例:嵌套对象结构

class Pet {
  name: string = '阿旺';
  age:number = 2;
}

class Person {
  name: string = 'Jerry';
  age: number = 18;
  pet: Pet = new Pet();
}

@Entry
@Component
struct NestedDemo {
  @State person: Person = new Person();

  build() {
    Column({space: 20}) {
      Text(`name: ${this.person.name}`).fontSize(20)
      Text(`age: ${this.person.age}`).fontSize(20)
      Text(`pet name: ${this.person.pet.name}`).fontSize(20)
      Text(`pet age: ${this.person.pet.age}`).fontSize(20)
      Button('改变人的年龄')
        .onClick(() => {
          // ✅ 第一层变化,@State可观察
          this.person.age++
        })
      Button('改变宠物的年龄')
        .onClick(() => {
          // ❌ 不可观察
          this.person.pet.age++
        })
    }
  }
}

上述代码中,当点击第二个按钮 改变宠物的年龄 时,对 this.person.pet.age 重新进行了赋值,但是页面并没有发生变化。

这是因为@State 只支持一层观察,无法递归监听嵌套对象深层属性的变化。对于嵌套对象(二层及以下属性的变化),必须配合 @Observed@ObjectLink 装饰器。

5.1 @Observed

@Observed 是一个类装饰器,用于标记一个类为可观察的。当类被 @Observed 装饰后,其实例的属性变化可以被框架追踪到,即使这些属性是嵌套的深层属性。通常与 @ObjectLink 配合使用,解决 @State 无法监听嵌套对象深层变化的限制。

5.2 @ObjectLink

@ObjectLink 是一个变量装饰器,用于在子组件中接收父组件传递过来的 @Observed 装饰类的实例。它会在子组件中建立对父组件数据的引用,当被引用的属性发生变化时,子组件会自动刷新。

当数据结构包含多层嵌套(如对象中包含对象、数组等),且需要监听深层属性的变化时,使用 @Observed + @ObjectLink 组合是最佳实践。父组件用 @State 持有顶层对象,子组件用 @ObjectLink 接收嵌套对象,从而实现深度响应式监听。

下面使用 @Observed + @ObjectLink,对上面的嵌套对象结构进行改造:

// 使用@Observed装饰被嵌套的类
@Observed
class Pet {
  name: string = '阿旺';
  age:number = 2;
}

class Person {
  name: string = 'Jerry';
  age: number = 18;
  pet: Pet = new Pet();
}

@Entry
@Component
struct NestedDemo {
  @State person: Person = new Person();

  build() {
    Column({space: 20}) {
      Text(`person name: ${this.person.name}`).fontSize(20)
      Text(`person age: ${this.person.age}`).fontSize(20)
      PetComponent({ pet: this.person.pet })
      Button('改变人的年龄')
        .onClick(() => {
          this.person.age++
        })
      Button('改变宠物的年龄')
        .onClick(() => {
          // 现在可以观察到嵌套属性的变化
          this.person.pet.age++
        })
    }
  }
}

@Component
struct PetComponent {
  @ObjectLink pet: Pet;

  build() {
    Column({space: 20}) {
      Text(`component pet name: ${this.pet.name}`).fontSize(20)
      Text(`component pet age: ${this.pet.age}`).fontSize(20)
    }
  }
}

5.4 @Track

上述代码虽然实现了对深层次数据变化的监听,但依然存在问题。为了验证这个问题,我们对代码进行如下改造:

// ...
@Entry
@Component
struct NestedDemo {
  //...

  isRender(tag: string): number {
    console.log(`Text ${tag} is rendered`);
    return 20;
  }

  build() {
    Column({space: 20}) {
      Text(`person name: ${this.person.name}`).fontSize(this.isRender("person name"))
      Text(`person age: ${this.person.age}`).fontSize(this.isRender("person age"))
      PetComponent({ pet: this.person.pet })
      Button('改变人的年龄')
        .onClick(() => {
          this.person.age++
        })
      Button('改变宠物的年龄')
        .onClick(() => {
          this.person.pet.age++
        })
    }
  }
}
//...

添加了 isRender(tag: string) 方法,打开 Log 窗口,点击 改变人的年龄 按钮,可以看到:

在这里插入图片描述

问题分析:咱们只修改了人的年龄,但人名那个文本也被重新渲染了,这明显不合适,造成了过度渲染。这种过度渲染会带来以下问题:

  1. 性能浪费:不必要的UI更新消耗CPU和GPU资源
  2. 响应延迟:大量不必要的渲染操作会降低应用响应速度
  3. 电池消耗:频繁的UI更新会增加设备功耗

要想解决这个问题,可以使用 @Track 装饰器。

@Track 装饰器是鸿蒙ArkTS框架中用于精细化状态管理的工具,它能够精确追踪对象内部特定属性的变化,仅在标记的属性发生变更时触发UI更新。这解决了传统状态管理(如@State)中“过度渲染”的问题——传统方式下,即使对象只有一个属性变化,也会导致整个组件重新渲染,造成性能浪费。

在类定义中,使用 @Track 装饰需要被观察的属性:

//...
class Person {
+  @Track name: string = 'Jerry';
+  @Track age: number = 18;
+  @Track pet: Pet = new Pet();
}

@Entry
@Component
struct NestedDemo {//...}
//...

上面咱们对 nameagepet 三个属性都使用 @Track 进行了修饰,可能你会疑惑咱们只是修改的 age 属性的值,其他两个为啥也要用 @Track 修饰呢?

这是是因为,未使用 @Track 标记的属性不能在 UI 组件中使用,否则会报错。namepet 属性,咱们都用在UI组件中使用了,为了防止报错,所以都用 @Track 进行了修饰。

注意:可以在非 UI 组件中使用非 @Track 装饰的属性,如事件回调函数中、生命周期函数中等。

5.5 与 @Observed 的区别

  • @Observed 是类装饰器,作用于整个类,使类的所有属性都可被观察。
  • @Track 是属性装饰器,作用于类中的具体属性,只标记需要被观察的字段。
  • 当只需要监听嵌套对象中的某几个属性时,使用 @Track 更加轻量和精确,避免不必要的性能开销。

六、总结对比

数据类型 监听方式 可观察的修改 注意事项
基本类型(number/string/boolean) @State 直接赋值 最基础的响应式用法,性能最佳
对象(第一层属性) @State 整体赋值、属性赋值 嵌套属性(第二层及以下)不可观察
嵌套对象(第二层及以下) @Observed + @ObjectLink 深层属性变化可观察 类需用 @Observed 装饰,子组件用 @ObjectLink 接收
嵌套对象(精确控制) @Track 仅标记的属性变化可观察 避免过度渲染,提升性能;未标记的属性不能在 UI 中使用

七、关键要点

  • @State 变量必须初始化,且通过 this.xxx = value 修改,支持基本类型和对象及对象第一层属性的变化监听。
  • 嵌套对象的深层变化需要 @Observed / @ObjectLink 配合实现深度观察,父组件用 @State 持有顶层对象,子组件用 @ObjectLink 接收嵌套对象。
  • @Track 装饰器用于精确控制属性观察,仅标记的属性变化才触发 UI 更新,可有效避免过度渲染问题;但未标记 @Track 的属性不能在 UI 中使用,否则会导致 JSCrash。
Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐