写 ArkTS 代码最头疼的就是状态同步问题:改了数据页面不刷新、父子组件数据双向同步乱套、复杂对象修改 UI 不更新。很多新手分不清这几个装饰器,随便乱用,最后出现各种玄学 bug。

重点:鸿蒙 7 状态装饰器和旧版思想一致,但要注意观测对象的变化监听规则。简单一句话:装饰器的本质,就是告诉框架 “这个变量需要被监听,一变就刷新 UI”。

一、速览四个核心装饰器定位

装饰器作用数据流向适用场景
@State组件内部私有状态组件内读写当前页面 / 组件自己用的状态,比如输入框文字、开关、弹窗显隐
@Prop从父组件单向传值到子组件父→子,子只能读不能直接改源数据父传子基础数据,子组件只展示,需要副本修改
@Link父子双向绑定父↔子双向同步子组件修改,父组件源数据跟着一起变,比如自定义弹窗输入框
@ObservedObject标记 class 类,让类里面属性可被观测配合 @ObjectLink 使用复杂对象、多条数据实体(订单、用户信息)

误区:不要把所有变量都加 @State。非 UI 相关临时变量不需要装饰器,过多监听会增加性能开销。

1. @State 组件内私有状态

@State 是最基础的状态装饰器,变量写在当前组件,组件内部读写,变量一旦修改,自动刷新当前组件 UI。

​
@Component
struct StateDemo {
  // 组件内部状态
  @State count: number = 0;

  build() {
    Column() {
      Text(`当前计数:${this.count}`)
        .fontSize(24)
      Button("+1")
        .onClick(() => {
          this.count += 1;
        })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

​

关键点:

  1. @State 变量属于当前组件,外部无法直接访问;
  2. 基础类型(string/number/boolean)直接监听;
  3. 如果是普通对象,直接修改对象内部属性不会触发刷新,需要用 @ObservedObject。

2. @Prop 父传子单向状态(副本)

父组件把数据传给子组件,子拿到的是一份拷贝。子修改 Prop 变量,不会影响父原始数据,单向数据流。

​
// 子组件
@Component
struct PropChild {
  // @Prop 接收父传过来的值
  @Prop childMsg: string;

  build() {
    Column() {
      Text(`子组件接收:${this.childMsg}`).fontSize(20)
      Button("修改子内副本")
        .onClick(() => {
          this.childMsg = "子组件修改的副本";
        })
    }
    .padding(16)
  }
}

// 父页面
@Entry
@Component
struct PropDemo {
  @State parentMsg: string = "父组件原始文字";

  build() {
    Column() {
      Text(`父组件:${this.parentMsg}`).fontSize(22)
      PropChild({ childMsg: this.parentMsg })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

​

关键点:

  1. @Prop:子拿到副本,子修改不会改变父;
  2. 如果父数据源更新,子的 Prop 会同步更新;
  3. 适合场景:子组件展示数据,允许在子内临时编辑,但不直接改动源头。

3. @Link 父子双向绑定

@Link 双向绑定,子组件修改变量,父组件原始 @State 变量同步跟着变。

注意:父组件传参的时候,必须带$符号,$变量名,代表引用这个状态。

​
// 子组件
@Component
struct LinkChild {
  // 双向绑定
  @Link linkText: string;

  build() {
    Column() {
      Text(`子组件双向值:${this.linkText}`).fontSize(20)
      TextInput({ text: this.linkText, placeholder: "修改会同步父组件" })
        .width('90%')
        .border({ width: 1, color: "#ddd" })
    }
    .padding(16)
  }
}

@Entry
@Component
struct LinkDemo {
  @State parentText: string = "双向绑定测试";

  build() {
    Column() {
      Text(`父组件:${this.parentText}`).fontSize(22)
      // 重点:$parentText,传递状态引用
      LinkChild({ linkText: $parentText })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

​

关键点:

  1. 父传参必须写$xxx;
  2. 子组件修改 @Link 变量,父源数据同步变化;
  3. 适合场景:弹窗输入框、自定义组件表单,子组件需要直接修改父数据。

4. @ObservedObject + @ObjectLink 复杂对象状态监听

@State 只能简单监听基础类型。如果是 class 对象,直接修改对象里面属性,UI 不会刷新。需要@ObservedObject标记类,@ObjectLink在子组件接收对象引用。

​
// 用@ObservedObject标记实体类
@ObservedObject
class UserInfo {
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 子组件
@Component
struct ObjectChild {
  @ObjectLink user: UserInfo;

  build() {
    Column() {
      Text(`姓名:${this.user.name} 年龄:${this.user.age}`).fontSize(20)
      Button("年龄+1")
        .onClick(() => {
          this.user.age += 1;
        })
    }
  }
}

@Entry
@Component
struct ObservedDemo {
  @State user: UserInfo = new UserInfo("张三", 18);

  build() {
    Column() {
      ObjectChild({ user: $user })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

​

关键点:

  1. @ObservedObject 写在 class 前面,开启类属性观测;
  2. 子组件用@ObjectLink接收对象引用;
  3. 修改 class 内部属性,UI 自动刷新;
  4. 适合:用户信息、订单、列表实体这类复杂数据模型。

五、状态管理高频踩坑汇总

坑 1:@Prop 子组件修改,以为会同步父数据

@Prop 是副本!子改 Prop 不会影响父。想要双向同步,改用 @Link。

坑 2:@Link 传参忘记写 $

父组件直接写linkText: parentText而不是$parentText,编译报错,双向绑定失效。

坑 3:普通对象不加 @ObservedObject,修改内部属性 UI 不刷新

​
// ❌错误写法
@State user = {name:"test", age:10}
// this.user.age++ 页面不会刷新

​

解决:实体类加上@ObservedObject,配合 @ObjectLink。

坑 4:滥用 @State,所有变量都加装饰器

普通临时变量、不需要渲染到 UI 的数据,不需要 @State,会增加框架监听开销。

坑 5:ForEach 渲染对象数组,对象没有 @ObservedObject

列表条目修改对象内部字段,列表 UI 不更新,是开发列表最常见 bug。

六、装饰器选型速查表

装饰器读写权限数据流典型场景
@State组件内可读可写组件内部页面开关、计数、基础状态
@Prop子组件只读副本,可修改副本,不影响父父→子单向展示类子组件,子临时编辑副本
@Link父子都可读写,双向同步双向绑定自定义输入组件、弹窗表单
@ObservedObject标记 class,搭配 @ObjectLink对象引用传递复杂实体对象、列表数据模型

结尾总结

  1. @State:组件内基础响应式变量;
  2. @Prop:单向传副本,子改不影响父;
  3. @Link:双向绑定,传参必须带$;
  4. 复杂 class 实体,使用@ObservedObject + @ObjectLink,才能监听对象内部属性变化;
  5. 优先单向数据流(@Prop),双向绑定(@Link)不要滥用,避免数据流转混乱难以排查。

实操指南

定义一个带 @ObservedObject 的商品类(name、price),父页面 @State 保存商品对象,传给子组件 @ObjectLink,子组件提供按钮修改价格,页面自动刷新价格。

Logo

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

更多推荐