🎉 博客主页:【剑九_六千里-CSDN博客】【剑九_六千里-掘金社区】
🎨 上一篇文章:【HarmonyOS第五章:组件抽取、构建函数抽取@Builder、构建函数插槽@BuilderParam】
🎠 系列专栏:【HarmonyOS系列】
💖 感谢大家点赞👍收藏⭐评论✍
引言:
ArkUI
框架,通过一系列装饰器(Decorators)提供了强大的数据绑定机制,使得开发者能够轻松地管理应用的状态并实现高效的视图更新。本文将深入探讨 ArkUI
中几种关键装饰器的功能及使用场景,包括 @Observed
、@Watch
和 $$
语法等。
文章目录
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+=10
console.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)
}
}