从最基础的 @State 开始

@State 是组件自己的状态,只有声明它的组件能读取和修改。它是所有状态管理的起点。

@Entry
@Component
struct Counter {
  @State count: number = 0;  // 组件私有状态

  build() {
    Column({ space: 16 }) {
      Text(`计数:${this.count}`)
      Button('+1')
        .onClick(() => {
          this.count++;  // 修改状态,UI 自动刷新
        })
    }
  }
}

@State 的三个特点:

  1. 只能在声明它的组件内修改
  2. 修改后引用它的 UI 自动刷新
  3. 支持的类型有 number、string、boolean、enum,以及对象、数组(配合 @Observed 等)

@State 的复杂类型

@State 修饰的是对象或数组,直接替换或修改其属性都能触发刷新:

@Entry
@Component
struct StateObjectDemo {
  // 对象类型的状态
  @State user: User = new User('张三', 25);

  build() {
    Column({ space: 16 }) {
      Text(`姓名:${this.user.name},年龄:${this.user.age}`)

      Button('改年龄')
        .onClick(() => {
          this.user.age++;  // 修改对象属性,能触发刷新
        })

      Button('替换对象')
        .onClick(() => {
          this.user = new User('李四', 30);  // 整体替换,也能触发
        })
    }
  }
}

class User {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

注意:普通 class 的 @State 修改属性能刷新,但嵌套对象(对象里的对象)的深层属性修改,需要 @Observed 配合,否则不刷新。这是常见困惑点,稍后展开。

@Prop:父传子,单向

@Prop 让父组件把值传给子组件,子组件修改不影响父组件:

// 父组件
@Entry
@Component
struct Parent {
  @State parentValue: number = 10;

  build() {
    Column({ space: 16 }) {
      Text(`父组件的值:${this.parentValue}`)
      // 把 parentValue 传给子组件
      Child({ childValue: this.parentValue })
    }
  }
}

// 子组件
@Component
struct Child {
  @Prop childValue: number;  // 接收父组件传入的值(单向)

  build() {
    Column({ space: 8 }) {
      Text(`子组件收到的值:${this.childValue}`)
      Button('子组件 +1')
        .onClick(() => {
          this.childValue++;  // 只改子组件的副本,不影响父组件
        })
    }
  }
}

@Prop值传递的副本,子组件改的是自己那份,父组件不知道。适合"父组件的数据子组件只读展示,子组件可以基于它做本地调整"的场景。

@Link:父子双向同步

@Link 让父子组件共享同一份数据,改任一边都同步到另一边:

// 父组件
@Entry
@Component
struct Parent {
  @State parentValue: number = 10;

  build() {
    Column({ space: 16 }) {
      Text(`父组件的值:${this.parentValue}`)
      // 注意:传参时用 $ 前缀,表示传递引用
      Child({ childValue: $parentValue })
    }
  }
}

// 子组件
@Component
struct Child {
  @Link childValue: number;  // 双向绑定的引用

  build() {
    Column({ space: 8 }) {
      Text(`子组件看到的值:${this.childValue}`)
      Button('子组件 +1')
        .onClick(() => {
          this.childValue++;  // 父子同步修改
        })
    }
  }
}

关键在父组件传参时加 $ 前缀:Child({ childValue: $parentValue })。这样父子指向同一份数据,双向同步。

@Prop vs @Link 的对比

@Prop:单向,子组件拿到副本[10] ──复制──>[10]
  子改自己的 [11],父还是 [10]

@Link:双向,子组件拿到引用[$parentValue] <────>[$childValue]
  指向同一份数据,任一边改,两边都是 [11]

用一张表总结选择依据:

场景用哪个
子组件只展示父组件数据,不改@Prop
子组件要修改父组件的数据@Link
简单值(数字、字符串)子组件可本地修改@Prop
表单类,子组件的输入要同步回父@Link

反过来讲,如果子组件需要改父组件的数据,却用了 @Prop,那是个 bug——子组件改了父组件不会更新。

@Watch:监听状态变化

@Watch 给状态加观察器,状态变化时执行回调:

@Entry
@Component
struct WatchDemo {
  @State @Watch('onCountChange') count: number = 0;

  build() {
    Column({ space: 16 }) {
      Text(`计数:${this.count}`)
      Button('+1')
        .onClick(() => this.count++)
    }
  }

  // 状态变化时的回调
  onCountChange(propertyName: string, newValue: number, oldValue: number) {
    console.info(`count 从 ${oldValue} 变成 ${newValue}`);
    if (newValue >= 10) {
      console.info('达到 10 了,触发提示');
    }
  }
}

@Watch('onCountChange') 里的字符串是回调方法名。常见用途:

  • 输入校验(值变化时验证合法性)
  • 联动逻辑(一个状态变化触发另一个状态更新)
  • 埋点(值变化时记录日志)

深坑:嵌套对象的修改不刷新

前面提过,普通 @State 的对象,修改嵌套对象属性不会触发刷新:

class Address {
  city: string;
  constructor(city: string) {
    this.city = city;
  }
}

class Person {
  name: string;
  address: Address;  // 嵌套对象
  constructor(name: string, address: Address) {
    this.name = name;
    this.address = address;
  }
}

@Entry
@Component
struct NestedDemo {
  @State person: Person = new Person('张三', new Address('北京'));

  build() {
    Column({ space: 16 }) {
      Text(`${this.person.name}${this.person.address.city}`)
      Button('改城市')
        .onClick(() => {
          this.person.address.city = '上海';  // 修改嵌套对象,不刷新!
        })
    }
  }
}

上面按钮点了 Text 不会更新,因为 address 是普通对象,框架监听不到它内部属性的变化。

解决办法是用 @Observed + @ObjectLink

@Observed  // 标记类,可被观察
class Address {
  city: string;
  constructor(city: string) {
    this.city = city;
  }
}

@Observed
class Person {
  name: string;
  address: Address;
  constructor(name: string, address: Address) {
    this.name = name;
    this.address = address;
  }
}

@Entry
@Component
struct NestedDemo {
  @State person: Person = new Person('张三', new Address('北京'));

  build() {
    Column({ space: 16 }) {
      Text(`${this.person.name}${this.person.address.city}`)
      Button('改城市')
        .onClick(() => {
          this.person.address.city = '上海';  // 现在能刷新了
        })
    }
  }
}

@Observed 让类变成"可观察的",@ObjectLink 用在子组件接收 @Observed 对象时。这个组合负责处理嵌套对象的深层属性修改。

小小建议哦

嵌套对象改属性不刷新是状态管理里最隐蔽的问题之一。两个处理方式:

  1. @Observed/@ObjectLink:正解,从根上让嵌套对象可观察
  2. 整体替换this.person = new Person(this.person.name, new Address('上海')),绕开深层修改

短期可以用方式 2 快速解决,长期和团队规范应该用方式 1,否则代码里到处是别扭的整体替换。

Logo

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

更多推荐