HarmonyOS NEXT——状态管理变化
| 注解调用 | 一句话总结 | 作用域 |
|---|---|---|
| @State | 双向数据传递,但是多层对象更新不生效,需要替换整个对象 | 定义变量 |
| @Prop | 父传子的单向数据传递,子组件的更新不影响父组件 | 定义变量 |
| @Link | 父传子的双向数据传递,子组件的更新也会影响父组件 | 定义变量 |
| @Observed & @ObjectLink | 嵌套对象或数组的场景中进行双向数据同步 | 定义类 |
| @Provide & @Consume | 父传隔多层的子组件的数据双向同步 | 定义变量 |
| @Watch | 监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用 | 定义变量 |
| $$语法 | 由@State、@Link和@Prop装饰的变量绑定到系统内置组件变量 | 定义变量 |
| @Track | @Track装饰的属性变化时,只会触发该属性关联的UI更新 | 定义变量 |
| @ObservedV2 & @Trace | 对嵌套类对象属性变化时的双向数据传递 | 定义变量,嵌套对象 |
| @Monitor | 监听状态变量的变化,当状态变量变化时,@Monitor的回调方法将被调用(只对V2有效) | 定义变量 |
@State
State的更新只依赖于第一层对象的嵌套,多层对象嵌套下则更新不生效,可使用console.log(Object.keys(object));的方式看看当前可响应式更新的key值的是有哪些

如果是需要更新嵌套的对象的话,只能更新第一层参数,对象要整个进行替换

@Prop
父传子单向更新传递,只能相隔一层之间的关系
注意
- Prop的变量不能赋值

父组件:字段的数据说明还是用@State来定义

子组件:字段用@Prop定义,子组件对值的修改只能影响自己的更新,不会传递到父组件

@Link
父子双向更新传递,对象和基本数据类型都可以,也是定义在变量上,用法同@Prop,不再赘述
注意
- Link的变量不能赋值

提问
既然@State和@Link都可以双向传递,那@State和@Link有什么区别?
- 页面1

- 页面2

最后结果[点击前和点击后对比]


答案:
- @State装饰的变量是组件内部的状态数据,当状态数据被修改时,会调用当前组件的build()进行UI刷新
- @Link装饰的变量可以和父组件的@State变量进行双向的数据绑定,对子组件和父组件都可以调用组件的build()进行UI刷新
@Observed & @ObjectLink
背景
- 上面的装饰器仅能观察到第一层数据的变化
- 对于多层嵌套的情况,比如对象数组等,他们的第二层的属性变化是无法观察到的
- 这就引出了@Observed/@ObjectLink装饰器
作用域
- 用于在涉及嵌套对象或数组的场景中进行双向数据同步
注意
- ObjectLink修饰符不能用在 Entry 修饰的组件中
对对象类修饰

父组件:对使用的类要加上@State定义,并将数组对象传递过去

子组件:对对象进行数据操作,均会同步到父组件

@Provide & @Consume
当组件存在多层组件的时候,需要在底下多层的组件中获取父组件的数据时用到
注意
- Provide和Consume修饰的变量名字需一致
- Consume的变量不能赋值

父组件修饰

子子子子组件使用

@Watch
当组件的变量变化需要监听使用的时候,可以用到@Watch
注意
- @Watch必须配合@State一起使用
- 在第一次初始化的时候,@Watch装饰的方法不会被调用。只有在后续状态改变时,才会调用@Watch回调方法
- 避免无限循环,不要在@Watch的回调方法里修改当前装饰的状态变量
- @Watch参数为必选,且参数类型必须是string,否则编译期会报错
//正确写法
@State @Watch('change') num: number = 10;
@State total: number = 5;
//正确写法
change() {
//当数量增加时,在这里可以做其他变量的逻辑
console.log(`xxx=`+(this.total + this.num));
}
//错误写法
onChange() {
console.log(`xxx`);
}
build() {
Column() {
Button('增加数量')
.onClick(() => {
this.num++
})
}
}
$$语法
主要是用于内置组件双向同步,比如Checkbox的select属性,TextInput的text属性等等,以TextInput方法的text参数为例
- 错误写法
@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 })
.width(300)
.onChange((value: string) => {
this.text = value
})
}.width('100%').height('100%')
}
}
- 正确写法
@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 })
.width(300)
}.width('100%').height('100%')
}
}
@Track
@Track装饰器装饰的是class类的字段,被@Track装饰的字段可以在UI中使用,非@Track装饰的属性不能在UI中使用,主要是用作优化UI的冗余刷新
注意
- @Track没有深度观测的功能,用于基础数据类型的传递,对象则不行
用法
- 错误写法&正确用法
class Person {
// id被@Track装饰
@Track id: number;
// age未被@Track装饰
age: number;
}
@Entry
@Component
struct Parent {
......
build() {
// 错误写法
// 没有被@Track装饰的属性不可以在UI中使用,运行时会报错
Text(`Parent id is: ${this.parent.id} and Parent age is: ${this.parent.age}`)
// 正确写法
Text(`Parent id is: ${this.parent.id}`)
.onClick(()=>{
//没有被@Track装饰的属性可以在其他地方使用
console.log(`Parent age is: ${this.parent.age}`);
})
}
}
- @Track原理
//带@Track
class LogTrack {
@Track str1: string;
@Track str2: string;
}
//不带@Track
class LogNotTrack {
str1: string;
str2: string;
}
@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) // 带@TrackText1
.fontSize(this.isRender(1))
Text(this.logTrack.str2) // 带@TrackText2
.fontSize(this.isRender(2))
Button('change logTrack.str1')
.onClick(() => {
this.logTrack.str1 = 'Bye'; //按钮1
})
Text(this.logNotTrack.str1) // 不带@TrackText3
.fontSize(this.isRender(3))
Text(this.logNotTrack.str2) // 不带@TrackText4
.fontSize(this.isRender(4))
Button('change logNotTrack.str1')
.onClick(() => {
this.logNotTrack.str1 = '再见'; //按钮2
})
}
.width('100%')
}
.height('100%')
}
}
分别点击按钮1和按钮2,可以看到日志的输出,在带@Track装饰器装饰,只有一条日志输出,避免了冗余刷新,未被@Track装饰器装饰,有两条日志输出,存在冗余刷新
//按钮1的日志
Text 1 is rendered
//按钮2的日志
Text 3 is rendered
Text 4 is rendered
@ObservedV2和@Trace
状态管理V1版本对嵌套类对象属性变化存在局限性,于是创建状态管理V2版本来解决嵌套对象的数据同步问题
- V1版本
点击Text组件增加age的值时,不会触发UI刷新。因为在现有的状态管理框架下,无法观测到嵌套类中属性age的值变化
@Observed
class Father {
son: Son;
}
@Observed
class Son {
name: string;
age: number;
}
@Entry
@Component
struct Index {
@State father: Father = new Father("John", 8);
build() {
Row() {
Column() {
Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
.onClick(() => {
this.father.son.age++;
})
}
}
}
}
-
V1版本解决方案
- 通过@ObjectLink与自定义组件的方式来解决深度嵌套对象的数据同步问题
- 通过接口回调的方式来解决数据嵌套更新的问题
- 通过@ObservedV2和@Trace方式来解决数据嵌套更新的问题
-
方式一:@ObjectLink与自定义组件
@Observed
class Father {
son: Son;
}
@Observed
class Son {
name: string;
age: number;
}
@Component
struct Child {
@ObjectLink son: Son;
build() {
Row() {
Column() {
Text(`name: ${this.son.name} age: ${this.son.age}`)
.onClick(() => {
this.son.age++;
})
}
}
}
}
@Entry
@Component
struct Index {
@State father: Father = new Father("John", 8);
build() {
Column() {
Child({son: this.father.son})
}
}
}
- 方式二:通过接口回调
@Observed
class Father {
son: Son;
}
@Observed
class Son {
name: string;
age: number;
}
@Entry
@Component
struct Index {
private sonListeners = new ArrayList<OnSonChangedListener>()
@State father: Father = new Father("John", 8);
registerSonChanged(listener: OnSonChangedListener) {
if (!this.sonListeners.has(listener)) {
this.sonListeners.add(listener)
}
}
build() {
Row() {
Column() {
Text(`name: ${this.father.son.name} age: ${this.father.son.age}`)
.onClick(() => {
this.sonListeners.forEach(listener => {
listener.onSonChanged()
})
})
}
}
}
}
export interface OnSonChangedListener {
onSonChanged: () => void
}
- 方式三:@ObservedV2和@Trace
注意
- 非@Trace装饰的成员属性用在UI上无法触发UI刷新
- @Trace不能用在没有被@ObservedV2装饰的class上
- @ObservedV2、@Trace不能与@Observed、@Track混合使用
- @ObservedV2、@Trace要使用在@ComponentV2组件中
@ObservedV2
class Son {
@Trace age: number = 100;
}
class Father {
son: Son = new Son();
}
@Entry
@ComponentV2
struct Index {
father: Father = new Father();
build() {
Column() {
// 当点击改变age时,Text组件会刷新
Text(`${this.father.son.age}`)
.onClick(() => {
this.father.son.age++;
})
}
}
}
@Monitor
@Monitor装饰器用于监听状态变量修改,使得状态变量具有深度监听的能力
注意
- @Monitor装饰器支持在@ComponentV2装饰的自定义组件中使用
- 未被@Trace装饰的属性无法被@Monitor监听到变化
- 相比V1版本,增加可以获取变化之前的值的能力
@ObservedV2
class Info {
@Trace name: string = "Tom";
@Trace region: string = "North";
@Trace job: string = "Teacher";
age: number = 25;
@Monitor("name")
onNameChange(monitor: IMonitor) {
console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
}
@Monitor("age")
onAgeChange(monitor: IMonitor) {
console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
}
@Monitor("region", "job")
onChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
})
}
}
@Entry
@ComponentV2
struct Index {
info: Info = new Info();
build() {
Column() {
Button("change name")
.onClick(() => {
this.info.name = "Jack"; // 能够触发onNameChange方法
})
Button("change age")
.onClick(() => {
this.info.age = 26; // 不能够触发onAgeChange方法
})
Button("change region")
.onClick(() => {
this.info.region = "South"; // 能够触发onChange方法
})
Button("change job")
.onClick(() => {
this.info.job = "Driver"; // 能够触发onChange方法
})
}
}
}
高阶用法
① 可以监听数组具体某个Index下的值,但是不能监听(Array、Map、Date、Set)
@ObservedV2
class ArrMonitor {
@Trace dimensionTwo: number[][] = [[1,1,1],[2,2,2],[3,3,3]];
@Trace dimensionThree: number[][][] = [[[1],[2],[3]],[[4],[5],[6]],[[7],[8],[9]]];
@Trace infoArr: Info[] = [new Info("Jack", 24), new Info("Lucy", 18)];
//监听下标为[0][0]和[1][1]的变化
@Monitor("dimensionTwo.0.0", "dimensionTwo.1.1")
onDimensionTwoChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`dimensionTwo path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
})
}
//监听下标为[0][0][0]和[1][1][0]的变化
@Monitor("dimensionThree.0.0.0", "dimensionThree.1.1.0")
onDimensionThreeChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`dimensionThree path: ${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
})
}
//监听下标为[0]和[1]Info类中属性name、age
@Monitor("infoArr.0.name", "infoArr.1.age")
onInfoArrPropertyChange(monitor: IMonitor) {
monitor.dirty.forEach((path: string) => {
console.log(`infoArr path:${path} change from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`);
})
}
//监听到infoArr整体赋值的变化
@Monitor("infoArr")
onInfoArrChange(monitor: IMonitor) {
console.log(`infoArr whole change`);
}
//监听到infoArr的长度变化
@Monitor("infoArr.length")
onInfoArrLengthChange(monitor: IMonitor) {
console.log(`infoArr length change`);
}
}
② 监听对象值未变化时不会回调
@ObservedV2
class Info {
@Trace person: Person;
@Monitor("person.name")
onNameChange(monitor: IMonitor) {
console.log(`name change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
}
@Monitor("person.age")
onAgeChange(monitor: IMonitor) {
console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
}
}
@Entry
@ComponentV2
struct Index {
info: Info = new Info("Tom", 25);
build() {
Column() {
Button("Step1、Only change name")
.onClick(() => {
this.info.person = new Person("Jack", 25); // 名字不同,能够触发onNameChange方法,岁数相同,不触发onAgeChange方法
})
Button("Step2、Only change age")
.onClick(() => {
this.info.person = new Person("Jack", 18); // 岁数不同,能够触发onAgeChange方法,名字相同,不触发onNameChange方法
})
Button("Step3、Change name and age")
.onClick(() => {
this.info.person = new Person("Lucy", 19); // 能够触发onNameChange、onAgeChange方法
})
}
}
}
@Monitor与@Watch对比
| 对比 | @Watch | @Monitor |
|---|---|---|
| 参数 | 回调方法名 | 监听状态变量名、属性名 |
| 监听目标数 | 只能监听单个状态变量 | 能同时监听多个状态变量 |
| 监听能力 | 跟随状态变量观察能力(一层) | 跟随状态变量观察能力(深层) |
| 能否获取变化前的值 | 不能获取变化前的值 | 能获取变化前的值 |
| 监听条件 | 监听对象为状态变量 | 监听对象为状态变量或为@Trace装饰的类成员属性 |
| 使用限制 | 仅能在@Component装饰的自定义组件中使用 | 能在@ComponentV2装饰的自定义组件中使用,也能在@ObservedV2装饰的类中使用 |
更多推荐

所有评论(0)