目录
- HarmonyOS Next 状态管理:Local 装饰器实践
- HarmonyOS Next 状态管理:Param 装饰器实践
- HarmonyOS Next 状态管理:Once 装饰器实践
- HarmonyOS Next 状态管理:Event 装饰器实践
- HarmonyOS Next 状态管理:!! 状态装饰器实践
- HarmonyOS Next 状态管理:@ObserverV2和@Trace 装饰器实践
- HarmonyOS Next 状态管理:Provider和Consumer 装饰器实践
一. @Once 修饰器概述
@Once
装饰器用于修饰变量,确保该变量仅在初始化时接受一次外部传入的值。初始化完成后,即使数据源发生变化,@Once
修饰的变量也不会’随之更新’。
二、@Once 修饰器的限制
使用范围:
- @Once
只能在 ComponentV2
中使用。
搭配使用:
- @Once
必须与 @Param
一起使用,不能单独使用或与其他装饰器搭配。
- @Once
可以放置在 @Param
之前或之后。
功能等效:
- @Once
和 @Param
组合使用时,功能上可以看似等同于 @Local
,但是效果比@Local
强大。
数据拦截:
- @Once
仅拦截数据源的变化,不会限制 @Param
的观测能力。
三、实践探索
3.1 修饰基础类型
@Once
和 @Param
可以修饰基础类型(如 string
、number
、boolean
、enum
、null
、undefined
等),并在变量变化时更新关联的 UI 组件。
enum Gender {
Female,
Male
}
@Entry
@ComponentV2
struct Index {
msg: string = “msg”
@Local name: string = “孙膑”
@Local age: number = 28
@Local isMaimed: boolean = true
@Local occupation: undefined | string = undefined
@Local consort: null | string = null
@Local gender: Gender = Gender.Male
build() {
Column({space: 10}) {
Column({space: 5}) {
Text(msg:${this.msg}
)
Text(name: ${this.name}
)
Text(age:${this.age}
)
Text(maimed:${this.isMaimed}
)
Text(occupation:${this.occupation}
)
Text(consort:${this.consort}
)
Text(gender:${this.gender}
)
}
.backgroundColor(Color.Orange)
ChildComponent({cMsg: this.msg, cName: this.name, cAge: this.age, cIsMaimed: this.isMaimed,
cOccupation: this.occupation, cConsort: this.consort, cGender: this.gender})
Button(‘Change’)
.onClick(() => {
this.msg += “abc”
this.name = (this.name == “孙膑” ? “周瑜” : “孙膑”)
this.age++
this.isMaimed = !this.isMaimed
this.occupation = (this.occupation == “剑士” ? undefined : “剑士”)
this.consort = (this.consort == “X” ? null : “X”)
this.gender = (this.gender == Gender.Male ? Gender.Female: Gender.Male)
})
}
.width(‘100%’)
.height(‘100%’)
}
}
@ComponentV2
struct ChildComponent {
@Param @Once cMsg: string = “cMsg”
@Param @Once cName: string = “cName”
@Param @Once cAge: number = 0
@Param @Once cIsMaimed: boolean = false
@Param @Once cOccupation: undefined | string = “undefined”
@Param @Once cConsort: null | string = “string”
@Param @Once cGender: Gender = Gender.Female
build() {
Column({space: 5}) {
Text(msg:${this.cMsg}
)
Text(name: ${this.cName}
)
Text(age:${this.cAge}
)
Text(maimed:${this.cIsMaimed}
)
Text(occupation:${this.cOccupation}
)
Text(consort:${this.cConsort}
)
Text(gender:${this.cGender}
)
}
.backgroundColor(Color.Green)
}
}
关键点:
- 非状态变量的更新:非状态变量与
@Once
和@Param
修饰的变量绑定后,当非状态变量更新时,@Once
和@Param
修饰的变量不会同步更新,但会初始化这些变量。 - 支持的基础类型:
@Once
和@Param
支持修饰string
、number
、boolean
、enum
、null
、undefined
等基础类型,初始化后将拦截数据源的变化。 - 不支持的类型:
@Once
和@Param
不支持修饰any
和unknown
类型。
3.2 @Once
和 @Param
等同于@Local
@Entry
@ComponentV2
struct Index {
msg: string = "msg"
@Local name: string = "孙膑"
build() {
Column({space: 10}) {
Column({space: 5}) {
Text(`父组件, msg:${this.msg}`)
Text(`父组件, name:${this.name}`)
Button('父组件更新数据')
.onClick(() => {
this.msg += "abc"
this.name += `${Math.round(Math.random() * 100)}`
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ChildComponent({cMsg: this.msg, cName: this.name})
}
}
.width('100%')
.height('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Once cMsg: string = "cMsg"
@Param @Once cName: string = "cName"
build() {
Column({space: 5}) {
Text(`子组件,cMsg:${this.cMsg}`)
Text(`子组件,cName:${this.cName}`)
Button('子组件更新数据')
.onClick(() => {
this.cMsg += "cMsg"
this.cName += "c孙膑"
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ItemComponent({iMsg: this.cMsg, iName: this.cName})
}
}
}
@ComponentV2
struct ItemComponent {
@Param iMsg: string = "iMsg"
@Param iName: string = "iName"
build() {
Column({space: 5}) {
Text(`元素,msg:${this.iMsg}`)
Text(`元素,name:${this.iName}`)
.onClick(() => {
// this.iMsg = "imsg"
})
}
.backgroundColor(Color.Orange)
}
}
关键点:
- 非状态变量的更新:非状态变量可以初始化
@Once
和@Param
修饰的变量,且仅会初始化一次。 - 等同于
@Local
:@Once
和@Param
修饰的变量等同于@Local
,可以初始化子组件中@Param
修饰的变量,并传递数据变化。与@Local
不同的是,@Once
和@Param
修饰的变量可以被父组件(数据源)初始化,而子组件中的@Local
修饰的变量则不能被父组件初始化。
2.3 修饰实例对象
以 UserInfo
类为例,研究 @Once
和@Param
修饰的实例对象的初始化和数据传递。
class UserInfo {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local userInfo: UserInfo = new UserInfo("孙膑", 19)
build() {
Column({space: 10}) {
Column({space: 5}) {
Text(`父组件, name:${this.userInfo.name}, age:${this.userInfo.age}`)
Button('父组件更新对象数据')
.onClick(() => {
this.userInfo = new UserInfo(`父组件:${Math.round(Math.random() * 100)}`, this.userInfo.age++)
})
Button('父组件更新对象属性数据')
.onClick(() => {
this.userInfo.name += "父组件"
this.userInfo.age += 1
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ChildComponent({userInfo: this.userInfo})
}
}
.width('100%')
.height('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Once userInfo: UserInfo = new UserInfo("c孙膑", 29)
build() {
Column({space: 5}) {
Text(`子组件,cMsg:${this.userInfo.name}`)
Text(`子组件,cName:${this.userInfo.age}`)
Button('子组件更新对象数据')
.onClick(() => {
this.userInfo = new UserInfo(`子组件:${Math.round(Math.random() * 100)}`, this.userInfo.age + 2)
})
Button('子组件更新对象属性数据')
.onClick(() => {
this.userInfo.name += "子组件"
this.userInfo.age += 2
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ItemComponent({userInfo: this.userInfo})
}
}
}
@ComponentV2
struct ItemComponent {
@Param userInfo: UserInfo = new UserInfo("i孙膑", 39)
build() {
Column({space: 5}) {
Text(`元素,msg:${this.userInfo.name}`)
Text(`元素,name:${this.userInfo.age}`)
.onClick(() => {
// this.iMsg = "imsg"
})
}
}
}
关键点:
- 初始化覆盖:
@Once
和@Param
修饰的变量在初始化时,本地数据会被形参覆盖。 - 数据拦截:
@Once
和@Param
仅能被父组件(数据源)初始化一次,之后父组件的变化会被拦截,不会更新@Once
和@Param
修饰的变量。 - 数据传递:
@Once
和@Param
修饰的变量等同于@Local
,可以初始化子组件中@Param
修饰的变量,并传递数据变化。
2.3 修饰容器对象
以数组为例,其他容器类型(如 Array
、Tuple
、Set
、Map
)的行为类似。
class UserInfo {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local userInfos: UserInfo[] = [new UserInfo("孙膑", 19)]
@Local books: string[] = ["xx"]
build() {
Column({space: 10}) {
Column({space: 5}) {
Text('用户信息')
ForEach(this.userInfos, (item: UserInfo) => {
Text(`name:${item.name}, age:${item.age})`)
})
Text('书籍')
ForEach(this.books, (book: string) => {
Text(`book name:${book}`)
})
Button('父组件更新数组')
.onClick(() => {
this.userInfos.push(new UserInfo("赵云", 26))
this.books.push("《本草纲目》")
})
Button('父组件更新数组元素')
.onClick(() => {
this.userInfos[0].name = `父组件, name:${Math.round(Math.random() * 100)}`
this.books[0] = `父组件, book:${Math.round(Math.random() * 100)}`
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ChildComponent({cUserInfos: this.userInfos, cBooks: this.books})
}
}
.width('100%')
.height('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Once cUserInfos: UserInfo[] = [new UserInfo("庞涓", 21)]
@Param @Once cBooks: string[] = ["《海底两万里》"]
build() {
Column({space: 5}) {
ForEach(this.cUserInfos, (userInfo: UserInfo) => {
Text(`name:${userInfo.name}, age:${userInfo.age}`)
})
ForEach(this.cBooks, (book: string) => {
Text(`book name:${book}`)
})
Button('子组件更新数组')
.onClick(() => {
this.cUserInfos.push(new UserInfo("Rabbit", 1))
this.cBooks.push("《Fly》")
})
Button('子组件更新数组元素')
.onClick(() => {
this.cUserInfos[0].name = `子组件, name:${Math.round(Math.random() * 100)}`
this.cBooks[0] = `子组件, book:${Math.round(Math.random() * 100)}`
})
Line()
.width('100%')
.height(1)
.backgroundColor(Color.Gray)
ItemComponent({iUserInfos: this.cUserInfos, iBooks: this.cBooks})
}
}
}
@ComponentV2
struct ItemComponent {
@Param iUserInfos: UserInfo[] = [new UserInfo("张仪", 21)]
@Param iBooks: string[] = ["《大秦帝国》"]
build() {
Column({space: 5}) {
ForEach(this.iUserInfos, (userInfo: UserInfo) => {
Text(`name:${userInfo.name}, age:${userInfo.age}`)
})
ForEach(this.iBooks, (book: string) => {
Text(`book name:${book}`)
})
// Button() // 报错:因为变量时只读类型
// .onClick(() => {
// this.iUserInfos = []
// this.iBooks = []
// })
}
}
}
关键点:
@Once
和@Param
修饰的变量,在初始化时本地数据会被形参覆盖。@Once
和@Param
修饰的容器变量,若元素是普通数据类型,在父组件(数据源)发生变化时,会接收到变化从而更新UI,且可以多次接受父组件(数据源)的变化;若元素是实例对象类型,父组件(数据源)发生变化时,父组件不会刷新UI,子组件也不会刷新UI。@Once
和@Param
修饰的容器变量,若元素是普通数据类型,在本身变化时,会影响父组件(数据源)的变化,且会刷新父组件(数据源)关联的UI。若元素是实例对象类型,在本身变化时,不会刷新本身关联的组件UI,且不会刷新父组件(数据源)关联的UI。@Once
和@Param
可以初始化子组件@Param
修饰的容器变量,并可以传递数据变化。@Once
和@Param
修饰的变量可以将数据变化传递给子组件中@Param
修饰的变量。但是子组件中@Param
修饰的变量无法修改,因为时read-only的。
2.4 修饰可选类型
@Once
和 @Param
修饰的变量可以是可选类型,那么就可以不用初始化该变量。
class UserInfo {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Entry
@ComponentV2
struct Index {
@Local userInfo: UserInfo = new UserInfo(“xx”, 100)
@Local msg: string = “Beijing”
build() {
Column({space: 20}) {
Text(name:${this.userInfo.name}, age:${this.userInfo.age}
)
Text(msg:${this.msg}
)
Button(‘change value’)
.onClick(() => {
this.userInfo = new UserInfo(“孙膑”, 27)
this.msg = “父组件消息”
})
Button(‘change property value’)
.onClick(() => {
this.userInfo.name = “庞涓”
})
Line().width(‘100%’).height(1).backgroundColor(Color.Gray)
ChildComponent({cUserInfo: this.userInfo, cMsg: this.msg})
}
}
}
@ComponentV2
struct ChildComponent {
@Param @Once @Require cUserInfo?: UserInfo
@Param @Once @Require cMsg?: string
build() {
Column() {
// Text(name:${this.cUserInfo.name}, age:${this.cUserInfo.age}
) // 报错:不可以使用。报undefine错误
Text(msg:${this.cMsg}
)
.onClick(() => {
this.cMsg = 子组件消息:${Math.round(Math.random() * 100)}
})
}
}
}
关键点:
@Once
和@Param
可以修饰普通类型变量和实例类型变量。@Once
和@Param
修饰的实例对象在使用的时候会报错 undefine, 即可以申明但不可以使用。@Once
和@Param
修饰的变量值同样可以修改,且不会影响到父组件(数据源)