🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区】
🎨 上一篇文章:【HarmonyOS第五章:组件抽取、构建函数抽取@Builder、构建函数插槽@BuilderParam】
🎠 系列专栏:【HarmonyOS系列】
💖 感谢大家点赞👍收藏⭐评论✍
引言:
ArkUI
框架,通过一系列装饰器(Decorators)提供了强大的数据绑定机制,使得开发者能够轻松地管理应用的状态并实现高效的视图更新。本文将深入探讨 ArkUI
中几种关键装饰器的功能及使用场景,包括 @Observed
、@Watch
和 $$
语法等。
文章目录
- 1. 父子组件传值-单向数据流
- 1.1. 直接传递
- 1.2. @Prop变量装饰器
- 2. 父子组件传值-双向数据流
- 2.1. @Link
- 2.1.1. 使用示例-基本的数据传递:
- 2.1.2. 使用示例-父组件修改数据:
- 2.1.3. 使用示例-子组件修改数据源:
- 2.1.4. 动态创建Link
- 3. 多层级组件之间传值
- 3.1. @Provide和@Consume
- 3.1.1. 使用示例-给孙组件传递数据:
- 3.1.2. 使用示例-父组件修改数据:
- 3.1.3. 使用示例-后代组件修改数据:
- 4. @Observed 与 @ObjectLink 嵌套类对象属性变化
- 4.1. 下面这么传递的数据,在子组件中不能修改:
- 4.2. 使用@Observed 和 @ObjectLink:
- 4.3. 可以发现,当我们在子组件中修改数据时,子组件试图更新了,那么父组件是否更新呢?再看下面这个例子:
- 4.4. 解决 @Observed 与 @ObjectLink 试图更新问题:
- 5. @Watch 装饰器:状态变量更改通知
- 5.1. 使用示例-@Watch和@Prop结合使用
- 5.2. 使用示例-@Watch和@Link结合使用
- 5.3. 使用示例-@Watch和@Provide结合使用
- 7. $$语法:内置组件双向同步
- 7.1. 使用规则
- 7.2. 使用示例
1. 父子组件传值-单向数据流
1.1. 直接传递
- 父组件使用子组件时,可直接按引用传递值
使用示例:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {count: number = 0;build() {Text(`这是子组件------${this.count}`)}
}
- 父组件修改数据源时,传递给子组件的数据不更新
此时通过点击按钮,已经修改了数据源的数据,但子组件中并没有更新:
使用示例:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {count: number = 0;build() {Text(`这是子组件------${this.count}`)}
}
- 子组件不能修改数据
使用示例:
子组件修改数据,无反应,因为数据非响应式:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {count: number = 0;build() {Column() {Text(`这是子组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})}}
}
1.2. @Prop变量装饰器
参数 | 同步类型 | 允许装饰的变量类型 |
---|---|---|
无 | 单向同步 | string、number、boolean、enum类型,不支持any,不允许使用undefined和null,必须指定类型 |
- 使用 @Prop 接收的参数可在子组件直接使用
- 使用 @Prop 接收的参数,父组件修改数据,子组件会同步更新
使用示例:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {@Prop count: number = 0;build() {Column() {Text(`这是子组件------${this.count}`)}}
}
- @Prop装饰的变量可以在本地修改,但修改后的变化不会同步回其父组件,采用单向数据流的模式
使用示例:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {@Prop count: number = 0;build() {Column() {Text(`这是子组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})}}
}
2. 父子组件传值-双向数据流
2.1. @Link
参数 | 同步类型 | 允许装饰的变量类型 |
---|---|---|
无 | 双向同步 | Object、class、string、number、boolean、enum类型,以及这些类型的数组,不支持any,不允许使用undefined和null,不支持Length、ResourceStr、ResourceColor类型 |
@Link
装饰的变量与其父组件中对应的数据源建立双向数据绑定,实现了双向同步。- 不能在
@Entry
装饰的自定义组件中使用。 @Link
修饰时数据不能设置默认值。- 父组件传递参数时,参数不能用
this.XXX
,必须使用$XXX
。 @Link
传递复杂数据类型时,只能将数据整体传递,不能只传递其中某一项。
2.1.1. 使用示例-基本的数据传递:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Child({count: $count}) // 必须使用$}.height("100%").width("100%")}
}@Component
struct Child {@Link count: number; // 不能设置默认值build() {Column() {Text(`这是子组件------${this.count}`)}}
}
2.1.2. 使用示例-父组件修改数据:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: $count}) // 必须使用$}.height("100%").width("100%")}
}@Component
struct Child {@Link count: number; // 不能设置默认值build() {Column() {Text(`这是子组件------${this.count}`)}}
}
2.1.3. 使用示例-子组件修改数据源:
@Entry
@Component
struct Parent {@State count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: $count}) // 必须使用$}.height("100%").width("100%")}
}@Component
struct Child {@Link count: number; // 不能设置默认值build() {Column() {Text(`这是子组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})}}
}
2.1.4. 动态创建Link
3. 多层级组件之间传值
3.1. @Provide和@Consume
- 父组件通过
@Provide
声明变量,@Provide
装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”
给他的后代组件。不需要多次在组件之间传递变量。 - 后代通过使用
@Consume
去获取@Provide
提供的变量,建立在@Provide
和@Consume
之间的双向数据同步,与@State/@Link
不同的是,前者可以在多层级的父子组件之间传递。 - 父组件修改数据,后代组件同步更新。
- 后代组件修改数据,父组件同步更新。
- 接收数据时,不能设置默认值。
- 允许传递
Object
、class
、string
、number
、boolean
、enum
类型,以及这些类型的数组。
3.1.1. 使用示例-给孙组件传递数据:
@Entry
@Component
struct Parent {@Provide count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Red)Child()}.height("100%").width("100%")}
}@Component
struct Child {build() {Column() {Text(`这是子组件`)GrandSon()}.width("100%").height(200).backgroundColor(Color.Green)}
}@Component
struct GrandSon {@Consume count: number; // 不能设置默认值build() {Column() {Text(`这是孙组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Yellow)}}
}
3.1.2. 使用示例-父组件修改数据:
@Entry
@Component
struct Parent {@Provide count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Red)Button("修改count").onClick(() => {this.count++;})Child()}.height("100%").width("100%")}
}@Component
struct Child {build() {Column() {Text(`这是子组件`)GrandSon()}.width("100%").height(200).backgroundColor(Color.Green)}
}@Component
struct GrandSon {@Consume count: number; // 不能设置默认值build() {Column() {Text(`这是孙组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Yellow)}}
}
3.1.3. 使用示例-后代组件修改数据:
@Entry
@Component
struct Parent {@Provide count: number = 5;build() {Column() {Text(`这是父组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Red)Button("修改count").onClick(() => {this.count++;})Child()}.height("100%").width("100%")}
}@Component
struct Child {build() {Column() {Text(`这是子组件`)GrandSon()}.width("100%").height(200).backgroundColor(Color.Green)}
}@Component
struct GrandSon {@Consume count: number; // 不能设置默认值build() {Column() {Text(`这是孙组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Yellow)Button("修改count").onClick(() => {this.count++;})}}
}
4. @Observed 与 @ObjectLink 嵌套类对象属性变化
上文所述的装饰器仅能观察到第一层的变化,但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项 class
,或者class的属性是 class
,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink
装饰器。
4.1. 下面这么传递的数据,在子组件中不能修改:
interface BookInfo {id: number;author: string;name: string;pageSize: number;
}class BookItem implements BookInfo {id: number = 0;author: string = "";name: string = "";pageSize: number = 0;constructor(bookInfo: BookInfo) {this.id = bookInfo.id;this.author = bookInfo.author;this.name = bookInfo.name;this.pageSize = bookInfo.pageSize;}
}@Entry
@Component
struct Parent {@State BookArr:BookInfo[] = [new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),]build() {Column() {Text("这是父组件")ForEach(this.BookArr, (item: BookInfo) => {Child({item})})}.width("100%").height("100%")}
}@Component
struct Child {// 非响应式数据传递,需要默认值,否则会报错item:BookInfo = new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000});build() {Column() {Text(`《${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)}.width("100%").height(100).backgroundColor(Color.Pink)}
}
4.2. 使用@Observed 和 @ObjectLink:
- 被
@ObjectLink
修饰的变量,不能设置默认值,否则会报错。 - 被
@ObjectLink
修饰的变量,必须用@Observed
修饰的类作为类型。
interface BookInfo {id: number;author: string;name: string;pageSize: number;
}@Observed
class BookItem implements BookInfo {public id: number = 0;public author: string = "";public name: string = "";public pageSize: number = 0;constructor(bookInfo: BookInfo) {this.id = bookInfo.id;this.author = bookInfo.author;this.name = bookInfo.name;this.pageSize = bookInfo.pageSize;}
}@Entry
@Component
struct Parent {@State BookArr:BookInfo[] = [new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),]build() {Column() {Text(`这是父组件`).width("100%").height(100).backgroundColor(Color.Gray)List({space: 5}) {ForEach(this.BookArr, (item: BookInfo) => {ListItem() {Column() {Text(`这是初始数据:《${item.name}》--------${item.author}-------${item.pageSize}`)Child({item: item})}}})}}.width("100%").height("100%")}
}@Component
struct Child {// @ObjectLink 修饰的变量,不能设置默认值,否则会报错// 注意:此处得 item 必须用 @Observed 修饰的类作为类型@ObjectLink item:BookItem;build() {Column() {Text(`子组件《${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)Button("改变pageSize的值").onClick(() => {this.item.pageSize++;})}.width("100%").height(100).backgroundColor(Color.Pink)}
}
4.3. 可以发现,当我们在子组件中修改数据时,子组件试图更新了,那么父组件是否更新呢?再看下面这个例子:
我们在父组件修改数组第一项的数据,结果发现,子组件试图更新了,但是父组件试图却并没有更新:
interface BookInfo {id: number;author: string;name: string;pageSize: number;
}@Observed
class BookItem implements BookInfo {public id: number = 0;public author: string = "";public name: string = "";public pageSize: number = 0;constructor(bookInfo: BookInfo) {this.id = bookInfo.id;this.author = bookInfo.author;this.name = bookInfo.name;this.pageSize = bookInfo.pageSize;}
}@Entry
@Component
struct Parent {@State BookArr:BookInfo[] = [new BookItem({id: 1, author: "罗贯中", name: "西游记", pageSize: 20000}),new BookItem({id: 2, author: "施耐庵", name: "水浒传", pageSize: 30000}),new BookItem({id: 3, author: "曹雪芹", name: "红楼梦", pageSize: 20000}),new BookItem({id: 4, author: "吴承恩", name: "三国演义", pageSize: 30000}),]build() {Column() {Text(`这是父组件`).width("100%").height(100).backgroundColor(Color.Gray)Button("父组件修改原始数据").onClick(() => {this.BookArr[0].pageSize+=10console.log(`${this.BookArr[0].pageSize}`)})List({space: 5}) {ForEach(this.BookArr, (item: BookInfo) => {ListItem() {Column() {Text(`这是初始数据:《${item.name}》--------${item.author}-------${item.pageSize}`)Child({item: item})}}})}}.width("100%").height("100%")}
}@Component
struct Child {// @ObjectLink 修饰的变量,不能设置默认值,否则会报错// 注意:此处得 item 必须用 @Observed 修饰的类作为类型@ObjectLink item:BookItem;build() {Column() {Text(`子组件《${this.item.name}》--------${this.item.author}-------${this.item.pageSize}`)Button("改变pageSize的值").onClick(() => {this.item.pageSize++;})}.width("100%").height(100).backgroundColor(Color.Pink)}
}
将数组第一项数据输入到控制台,发现其实父组件的数组是更新了,只是试图未更新:
每个装饰器都有自己可以观察的能力,并不是所有的改变都可以被观察到,只有可以被观察到的变化才会进行UI更新。@Observed
装饰器可以观察到嵌套对象的属性变化,其他装饰器仅能观察到第一层的变化。
4.4. 解决 @Observed 与 @ObjectLink 试图更新问题:
多层嵌套的数据更新,子视图会更新,父组件视图不会更新。想让父组件视图更新,可以通过重新赋值的方式实现:
添加重新赋值的代码:
// 更新父组件视图
this.BookArr[0] = new BookItem(this.BookArr[0])
5. @Watch 装饰器:状态变量更改通知
@Watch
用于监听状态变量的变化,当状态变量变化时,@Watch
的回调方法将被调用。@Watch
在 ArkUI
框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为 false
的情况下,就会触发 @Watch
的回调。
5.1. 使用示例-@Watch和@Prop结合使用
import promptAction from '@ohos.promptAction';@Entry
@Component
struct Parent {@State @Watch("update") count: number = 5;update() {promptAction.showToast({message: `count值变成了${this.count}`})}build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: this.count})}.height("100%").width("100%")}
}@Component
struct Child {@Prop count: number = 0;build() {Column() {Text(`这是子组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})}}
}
5.2. 使用示例-@Watch和@Link结合使用
import promptAction from '@ohos.promptAction';@Entry
@Component
struct Parent {@State @Watch("update") count: number = 5;update() {promptAction.showToast({message: `count值变成了${this.count}`})}build() {Column() {Text(`这是父组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})Child({count: $count}) // 必须使用$}.height("100%").width("100%")}
}@Component
struct Child {@Link count: number; // 不能设置默认值build() {Column() {Text(`这是子组件------${this.count}`)Button("修改count").onClick(() => {this.count++;})}}
}
5.3. 使用示例-@Watch和@Provide结合使用
使用时,@Watch 放在紧跟 @Provide 之后的位置:
import promptAction from '@ohos.promptAction';@Entry
@Component
struct Parent {@Provide @Watch("update") count: number = 5;update() {promptAction.showToast({message: `count值变成了${this.count}`})}build() {Column() {Text(`这是父组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Red)Button("修改count").onClick(() => {this.count++;})Child()}.height("100%").width("100%")}
}@Component
struct Child {build() {Column() {Text(`这是子组件`)GrandSon()}.width("100%").height(200).backgroundColor(Color.Green)}
}@Component
struct GrandSon {@Consume count: number; // 不能设置默认值build() {Column() {Text(`这是孙组件------${this.count}`).width("100%").height(100).backgroundColor(Color.Yellow)Button("修改count").onClick(() => {this.count++;})}}
}
7. $$语法:内置组件双向同步
$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。
7.1. 使用规则
- 当前
$$
支持基础类型变量,以及@State
、@Link
和@Prop
装饰的变量。 - 当前
$$
支持的组件:
$$
绑定的变量变化时,会触发UI的同步刷新。
7.2. 使用示例
以 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: '请输入', controller: this.controller }).placeholderColor(Color.Grey).placeholderFont({ size: 14, weight: 400 }).caretColor(Color.Blue).width(300)}.width('100%').height('100%').justifyContent(FlexAlign.Center)}
}