本篇文章详细介绍了V2版本状态类装饰器,及其使用方法。V1版本(状态装饰器V1版本)状态类装饰器请移步上期文章(状态装饰器V1版本)
目录
5、@ObservedV2装饰器和@Trace装饰器:类属性变化观测
6、@Provider装饰器和@Consumer装饰器:跨组件层级双向同步
6.1 @Provider和@Consumer VS @Provide和@Consume能力对比
6.2 @Provider和@Consumer装饰复杂类型,配合@Trace一起使用
8.2 @Monitor 在 @ObservedV2 中使用
1、@Local装饰器:组件内部状态
@Local
装饰器是用来在 V2 组价中定义状态变量, 已达到观测变量的目的( 响应式效果 )。
其作用类似于 V1 版本的@State
【 注:但也只是类似,不一样的地方还是很多的 】
1.1 基础使用
基本使用来说,和 @State 是一样的,定义状态变量。在 UI 部分使用的时候,一旦状态变量发生变化,UI 会被刷新
【 注:定义简单数据类型,和 @State 没有区别 】
@Entry
@ComponentV2
struct Index {
// 使用 @Local 定义状态变量
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 V2 版本自定义组件')
Text(this.message)
Button('Change').onClick(() => this.message = '你好 世界')
}
}
}
1.2 不接受外来赋值
@Local
不接受外来赋值。V1 版本
- 在子组件内使用
@State
定义的状态变量,可以在父组件调用的时候对其进行赋值,覆盖原有初始值V2 版本
- 在子组件内使用
@Local
定义的状态变量,不可以在父组件调用的时候对其进行赋值任何外来数据都不能对@Local
定义的状态变量进行赋值
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 调用子组件的时候, 对子组件内 @Local 定义的 str 进行赋值
// 此时代码调用会报错
Child({ str: '从父组件赋值' })
}
}
}
@ComponentV2
struct Child {
@Local str: string = ''
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
}
}
}
1.3 观测变化
1.3.1 基本数据类型观测变化
对于基本数据类型来说,和 @State 一样,可以观测到数据赋值的变化。
@Entry
@ComponentV2
struct Index {
// 使用 @Local 定义状态变量
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 V2 版本自定义组件')
Text(this.message)
// 直接修改状态变量的值【 可以观测到变化,引起 UI 刷新 】
Button('Change').onClick(() => this.message = '你好 世界')
}
}
}
1.3.2 对象数据类型观测变化
对于对象数据类型来说【 { name: '张三', age: 18 } 】
- 如果直接修改对象本身, 可以观测到变化, 会引起 UI 刷新
- 如果修改的是对象成员的话, 观测不到变化, 不会引起 UI 刷新
class InfoType {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
// @Local 定义的是对象数据类型
@Local info: InfoType = new InfoType('张三', 18)
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Text(`
展示一下对象信息
name => ${ this.info.name }
age => ${ this.info.age }
`)
// 相当于直接修改 @Local 定义的变量 info, 可以观测到变化【 会引起 UI 刷新 】
Button('修改对象本身').onClick(() => this.info = new InfoType('李四', 20))
// 修改对象内的成员, 观测不到变化【 不会引起 UI 刷新 】
Button('修改对象成员').onClick(() => this.info.age++)
}
}
}
如果想要观测到对象成员的变化, 需要配合
@ObservedV2
和@Trace
装饰器使用才可以
@ObservedV2
是 class 的装饰器
@Trace
是对 class 内属性的装饰器
@Trace
装饰了哪个属性,,哪个属性的修改就可以被观测到
@ObservedV2
class InfoType {
name: string
// age 属性被修改的时候就可以被观测到
@Trace age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local info: InfoType = new InfoType('张三', 18)
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Text(`
展示一下对象信息
name => ${ this.info.name }
age => ${ this.info.age }
`)
Button('修改对象本身').onClick(() => this.info = new InfoType('李四', 20))
// 当对象内的 age 属性被修改的时候, 就可以观测到, 会引起 UI 刷新
Button('修改对象成员').onClick(() => this.info.age++)
// 当对象内的 name 属性被修改的时候, 不可以被观测到, 不会引起 UI 刷新(因为没有被@Trace装饰)
Button('修改对象成员').onClick(() => this.info.name = '王五')
}
}
}
1.3.3 数组数据类型观测变化
对于数组数据类型来说
- 如果数组内存储的都是基本数据类型
- 不管是一维数组还是二维数组
- 不管是修改数组整体还是修改数组项
都可以观测到变化, 会引起 UI 刷新
@Entry
@ComponentV2
struct Index {
// 定义数组【 不管一维还是二维, 内部数据一定是基本数据类型 】
@Local list1: number[] = [ 1, 2, 3 ]
@Local list2: number[][] = [ [1, 2], [3, 4] ]
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Text(`
展示一下数组信息
list1 =>
=> ${ this.list1[0] }
=> ${ this.list1[1] }
=> ${ this.list1[2] }
list2 =>
=> ${ this.list2[0][0] }
=> ${ this.list2[0][1] }
=> ${ this.list2[1][0] }
=> ${ this.list2[1][1] }
`)
// 不管修改哪一个数组项中的数据,都可以观测到变化,并触发UI刷新
Button('修改 list1 数组项').onClick(() => this.list1[0]++)
Button('修改 list2 数组项数据').onClick(() => this.list2[0][1]++)
}
}
}
如果数组内存储的都是对象数据类型
不管一维还是二维数组
直接修改数组项成员(对象), 是可以观测到变化的, 会引起 UI 刷新
如果修改的是对象内的属性, 是不能观测到变化的, 不会引起 UI 刷新
class InfoType {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local list1: InfoType[] = [new InfoType('张三', 18), new InfoType('李四', 20)]
@Local list2: InfoType[][] = [
[new InfoType('张三', 18), new InfoType('李四', 20)],
[new InfoType('王五', 22), new InfoType('赵六', 24)]
]
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Text(`
展示一下数组信息
list1 =>
=> ${ this.list1[0].name } - ${ this.list1[0].age }
=> ${ this.list1[1].name } - ${ this.list1[1].age }
list2 =>
=> ${ this.list2[0][0].name } - ${ this.list2[0][0].age }
=> ${ this.list2[0][1].name } - ${ this.list2[0][1].age }
=> ${ this.list2[1][0].name } - ${ this.list2[1][0].age }
=> ${ this.list2[1][1].name } - ${ this.list2[1][1].age }
`)
// 直接修改对象,可以观测到变化,并触发UI刷新。
Button('修改 list1 数组项').onClick(() => this.list1[0] = new InfoType('小红', 100))
Button('修改 list2 数组项数据').onClick(() => this.list2[0][1] = new InfoType('小明狼', 100))
// 修改对象成员(属性),不能观测到变化,不会触发UI刷新。
// 如果想要实现修改对象成员也可以观测到
// 和对象数据类型是一样的, 需要用 @ObservedV2 和 @Trace 装饰器
Button('修改 list1 数组项').onClick(() => this.list1[0].age++)
Button('修改 list2 数组项数据').onClick(() => this.list2[0][1].age++)
}
}
}
1.3.4 其他内置数据类型观测变化
对于其他数据结构来说【 Date,Set,Map,。。。 】
调用内置 API 修改数据是可以观测到变化的
比如 Date
- 调用 setFullYear(), setMonth(), setHours, ... 改变 Date 的值, 是可以观测到的
其他内置数据结构也是如此
2、@Param:组件外部输入
@Param
是专门在子组件内使用,用于接受外部传入的数据。因为在使用@Local
的时候, 不能接收外部传入的数据,所以这里使用@Param
装饰器。【注意:】
@Param
不光可以接收外部数据, 还可以接收@Local
的同步变化- 单独使用
@Param
是必须要在初始化时候赋值的, 外部传入的时候再进行覆盖- 如果想在
@Param
的时候不进行初始化,需要配合@Require
装饰器一起使用- @
Param
装饰的数据是只读的,不可以修改,如果修改的话需要配合@Event
装饰器- 对于观测数据变化,和
@Local
规则是一样的
2.1 基础使用
【 注:在 Child 组件内不能修改 str 的值,因为@Param
装饰的变量是只读的,需要配合@Event
装饰器修改】
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 调用子组件的时候可以不给子组件内的 str 赋值
Child()
// 调用子组件的时候也可以给子组件内的 str 赋值
Child({ str: '父组件赋值' })
}
}
}
@ComponentV2
struct Child {
// 使用 @Param 定义一个 str 变量
// 这里必须要给 str 进行一次赋值, 否则报错
@Param str: string = '子组件初始化默认值'
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
}
}
}
2.2 配合@Require使用
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 因为 Child 子组件内 str 配合了 @Require 装饰器
// 所以如果没有进行赋值, 会直接报错
Child() // => 报错
// 调用子组件的时候也可以给子组件内的 str 赋值
Child({ str: '父组件赋值' })
}
}
}
@ComponentV2
struct Child {
// 使用 @Param 定义一个 str 变量
// 配合 @Require 使用的时候, 可以不用进行初始化赋值
@Require @Param str: string
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
}
}
}
3、@Event 装饰器:规范组件输出
@Event
是为了实现子组件向父组件要求更新@Param
装饰变量的能力。使用
@Event
装饰回调方法是一种规范,表明子组件需要传入更新数据源的回调。
@Event
主要配合@Param
实现数据的双向同步。因为我们通过
@Param
的学习, 发现@Param
是单向数据同步
- 父组件数据变化会引起子组件数据变化
- 子组件数据变化不会引起父组件数据变化
【注意:】
@Event
不能修饰非函数数据@Event
本地可以不初始化或者初始化一个空函数,需要从父组件接受一个函数
3.1 修改@Param装饰的数据
其实就是通过调用父组件传递过来的方法,修改父组件数据。
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 调用子组件的时候也可以给子组件内的 str 赋值
// 并且传递进去一个函数赋值给 @Event 创建的函数变量
Child({
str: this.message,
changeHandler: (val: string) => {
this.message = val
}
})
}
}
}
@ComponentV2
struct Child {
@Param str: string = ''
// 定义一个函数用来修改父组件的数据
@Event changeHandler: (s: string) => void
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
// 当你想在子组件内修改 @Param 定义的数据的时候
Button('子组件内修改数据').onClick(() => this.changeHandler('你好 世界'))
}
}
}
根据上述代码
在 Index(父组件) 内调用 Child(子组件) 的时候, 给 str 和 changeHandler 都进行了赋值
在 Child 组件中, 点击 Button 按钮的时候, 就会触发函数( 该函数是父组件定义传递进来的 )
就会同步修改父组件内的 message 数据
因为 message 数据的修改, 就会同步到 Child 子组件的 str 变量
实现了子组件内数据的修改
【 注:通过调用 @Event 函数通知父组件修改 message 数据是同步的,父组件修改完毕回传回子组件是异步的 】
4、@Once:初始化同步一次
@Once
装饰器仅从外部初始化一次、不接受后续同步变化的能力,你可以使用@Once
装饰器搭配@Param
装饰器使用。只有父组件调用子组件的时候传递参数时赋值一次,后续父组件修改数据,子组件不会同步修改。
【注意:】
@Once
必须和@Param
搭配在一起使用, 不可以单独使用
@Once
不影响@Param
观测数据的规则
@Once
配合@Param
使用的时候,可以在当前组件内直接修改数据
4.1 基础使用
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 调用子组件的时候也可以给子组件内的 str 赋值
Child({ str: this.message })
// 在父组件内修改 message 的值,不会同步到 Child 内, 在 Child 组件内不会进行 UI 刷新
Button('Change').onClick(() => this.message = '你好 世界')
}
}
}
@ComponentV2
struct Child {
// @Once 和 @Param 配合使用定义 str
@Once @Param str: string = ''
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
}
}
}
根据上述代码
- 此时只有在 Index(父组件) 最开始调用 Child(子组件) 的时候进行的赋值是有效的
- 后期在 Index 组件内修改 message 的值的时候, 不会同步到 Child 内, 在 Child 组件内不会进行 UI 刷新
4.2 当前组件内修改
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 调用子组件的时候也可以给子组件内的 str 赋值
Child({ str: this.message })
}
}
}
@ComponentV2
struct Child {
// @Once 和 @Param 配合使用定义 str
@Once @Param str: string = ''
build() {
Column() {
Text('我是 Child 组件')
Text(this.str)
// 在子组件内自己修改
Button('Change').onClick(() => this.str = '你好 世界')
}
}
}
根据上述代码
- Child(子组件) 内可以自己修改 str 这个数据的值, 并且可以观测到变化, 会引起 UI 刷新了
其实
@Once
和@Param
配合就等价于@Local
- 只不过
@Local
不能接收外部数据传递 @Once
和@Param
配合在一起可以接收外部数据【 只能接受一次 】
5、@ObservedV2装饰器和@Trace装饰器:类属性变化观测
@ObservedV2
和@Trace
提供了对嵌套类对象属性变化直接观测的能力
@ObservedV2
装饰Class类
@Trace
装饰Class的属性。被@Trace
装饰的属性变化时,可以被观测到。
@ObservedV2
class InfoType {
name: string
// age 属性被修改的时候就可以被观测到
@Trace age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local info: InfoType = new InfoType('张三', 18)
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Text(`
展示一下对象信息
name => ${ this.info.name }
age => ${ this.info.age }
`)
Button('修改对象本身').onClick(() => this.info = new InfoType('李四', 20))
// 当对象内的 age 属性被修改的时候, 就可以观测到, 会引起 UI 刷新
Button('修改对象成员').onClick(() => this.info.age++)
// 当对象内的 name 属性被修改的时候, 不可以被观测到, 不会引起 UI 刷新(因为没有被@Trace装饰)
Button('修改对象成员').onClick(() => this.info.name = '王五')
}
}
}
6、@Provider装饰器和@Consumer装饰器:跨组件层级双向同步
在状态管理V1版本中,提供跨组件层级双向的装饰器为
@Provide
和@Consume
,当前文档介绍的是状态管理V2装饰器@Provider
和@Consumer
。虽然两者名字和功能类似,但在特性上还存在一些差异。
@Provider
和@Consumer
用于跨组件层级数据双向同步,可以使得开发者不用拘于组件层级。
@Provider
和@Consumer
属于状态管理V2装饰器,所以只能在@ComponentV2
中才能使用,在V1装饰器@Component
中使用会编译报错。
6.1 @Provider和@Consumer VS @Provide和@Consume能力对比
能力 | V2装饰器@Provider和@Consumer | V1装饰器@Provide和@Consume |
@Consume(r) | 允许本地初始化,当找不到@Provider的时候使用本地默认值。 | 禁止本地初始化,当找不到对应的的@Provide时候,会抛出异常。 |
支持类型 | 支持function。 | 不支持function。 |
观察能力 | 仅能观察自身赋值变化,如果要观察嵌套场景,配合@Trace一起使用。 | 观察第一层变化,如果要观察嵌套场景,配合@Observed和@ObjectLink一起使用。 |
alias和属性名 | alias是唯一匹配的key,如果缺省alias,则默认属性名为alias。 | alias和属性名都为key,优先匹配alias,匹配不到可以匹配属性名。 |
@Provide(r) 从父组件初始化 | 禁止。 | 允许。 |
@Provide(r)支持重载 | 默认开启,即@Provider可以重名,@Consumer向上查找最近的@Provider。 | 默认关闭,即在组件树上不允许有同名@Provide。如果需要重载,则需要配置allowOverride。 |
6.2 @Provider和@Consumer装饰复杂类型,配合@Trace一起使用
- @Provider和@Consumer只能观察到数据本身的变化。如果当其装饰复杂数据类型,需要观察属性的变化时,需要配合@Trace一起使用。
- 装饰内置类型:Array、Map、Set、Date时,可以观察到某些API的变化,观察能力同@Trace
@ObservedV2
class User {
@Trace name: string;
@Trace age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
const data: User[] = [new User('Json', 10), new User('Eric', 15)];
@Entry
@ComponentV2
struct Parent {
@Provider('data') users: User[] = data;
build() {
Column() {
Child()
Button('add new user')
.onClick(() => {
this.users.push(new User('Molly', 18));
})
Button('age++')
.onClick(() => {
this.users[0].age++;
})
Button('change name')
.onClick(() => {
this.users[0].name = 'Shelly';
})
}
}
}
@ComponentV2
struct Child {
@Consumer('data') users: User[] = [];
build() {
Column() {
ForEach(this.users, (item: User) => {
Column() {
Text(`name: ${item.name}`).fontSize(30)
Text(`age: ${item.age}`).fontSize(30)
Divider()
}
})
}
}
}
7、@Computed装饰器:计算属性
@Computed
装饰器的能力就是提供一个计算属性的能力【 和 Vue 内的计算属性一个道理 】只有当被计算数据发生变化的时候才会从新计算
主要应用于解决UI多次重用该属性从而重复计算导致的性能问题
@Entry
@ComponentV2
struct Index {
@Local firstName: string = '张'
@Local lastName: string = '小明'
// 定义一个计算属性
@Computed
get fullName() {
return this.firstName + this.lastName
}
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 直接使用
// fullName 的值不能修改,只能是通过修改被计算项( firstName 和 lastName )从而实现重新计算
Text(this.fullName)
}
}
}
8、@Monitor装饰器:状态变量修改监听
@Monitor
装饰器就是监听数据变化的装饰器,类似与 V1 版本里面的@Watch
装饰器【 但功能强大了很多 】
8.1 基础使用
在 V1 版本的
@Watch
监听的时候, 只能是一个属性一个属性的监听
- 每一个要监听的属性都需要写一个
@Watch
装饰器- 虽然可以使用同一个回调函数, 但是需要一个变量一个变量的监听
在 V2 版本的
@Monitor
监听的时候
- 不需要和其他装饰器写在一起,单独使用即可
- 可以直接确定你需要监听的几个属性
// V1 的 @Watch
@Entry
@Component
struct Index {
// 每一个属性都需要进行监听
@State @Watch('changeHandler') message: string = 'hello world'
@State @Watch('changeHandler') count: number = 10
@State @Watch('changeHandler2') foo: boolean = true
changeHandler(propertyName: string) {}
changeHandler2(propertyName: string) {}
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
}
}
}
// V2 的 @Monitor
@Entry
@ComponentV2
struct Index {
@Local message: string = 'hello world'
@Local count: number = 10
@Local foo: boolean = true
// 设置监听器
@Monitor('message', 'count', 'foo')
changeHandler() {}
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
}
}
}
8.2 @Monitor 在 @ObservedV2 中使用
在 V2 版本中
@Monitor
装饰器不光可以使用在自定义组件中也可以直接使用在
@ObservedV2
装饰的 class 里面【 注:在
@ObservedV2
的属性中,被@Trace
修饰的属性才能监听到变化 】
@ObservedV2
class InfoType {
@Trace name: string = '小灰狼'
age: number = 18
gender: string = '男'
@Trace score: number = 100
// 这里可以直接使用 @Monitor 装饰器
@Monitor('name', 'age')
changeHandlerOne() {
console.log('one 触发')
}
@Monitor('score', 'gender')
changeHandlerTwo() {
console.log('two 触发')
}
}
@Entry
@ComponentV2
struct Index {
@Local info: InfoType = new InfoType()
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Button('修改 name').onClick(() => this.info.name = '张三')
Button('修改 age').onClick(() => this.info.age++)
Button('修改 gender').onClick(() => this.info.gender = '女')
Button('修改 score').onClick(() => this.info.score--)
}
}
}
8.3 监听对象属性
@Monitor
装饰器可以直接监听对象内的某一个属性,而不是监控整个对象
@ObservedV2
class InfoType {
@Trace name: string = '小灰狼'
age: number = 18
gender: string = '男'
@Trace score: number = 100
}
@Entry
@ComponentV2
struct Index {
@Local info: InfoType = new InfoType()
// 直接监控对象成员
@Monitor('info.name')
changeHandler() {}
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
// 只有当 info 内 name 成员变化的时候, 才会触发 changeHandler 回调函数
Button('修改 name').onClick(() => this.info.name = '张三')
Button('修改 age').onClick(() => this.info.age++)
Button('修改 gender').onClick(() => this.info.gender = '女')
Button('修改 score').onClick(() => this.info.score--)
}
}
}
根据上述代码, 可以直接监控 info 对象内的 name 成员
只有当 info 内 name 成员变化的时候, 才会触发 changeHandler
回调函数
【 注:只有被 @Trace
装饰的属性才可以监控到变化,否则监控不到 】
8.4 @Monitor回调函数的参数
在 V1 版本的
@Watch
监听时,回调函数的参数只有被监控的变量名,而且我们只能拿到改变后的值在 V2 版本的
@Monitor
监听时,回调函数的参数是一个 IMonitor 对象,可以拿到 属性名、修改之前、修改之后
@ObservedV2
class InfoType {
@Trace name: string = '小灰狼'
@Trace age: number = 18
@Trace gender: string = '男'
@Trace score: number = 100
}
@Entry
@ComponentV2
struct Index {
@Local info: InfoType = new InfoType()
@Monitor('info.name', 'info.age', 'info.gender', 'info.score')
changeHandler(monitor: IMonitor) {
// 利用 IMonitor 参数来拿到 属性名、修改之前、修改之后 的信息
// 当前修改的属性
console.log(monitor.value('info.name')?.path)
// 修改前的值
console.log(monitor.value('info.name')?.before + '')
// 修改后的值
console.log(monitor.value('info.name')?.now + '')
}
build() {
Column({ space: 10 }) {
Text('我是 Index 组件')
Button('修改 name').onClick(() => this.info.name = '张三')
Button('修改 age').onClick(() => this.info.age++)
Button('修改 gender').onClick(() => this.info.gender = '女')
Button('修改 score').onClick(() => this.info.score--)
}
}
}