目录
- HarmonyOS Next 状态管理:Local 装饰器实践
- HarmonyOS Next 状态管理:Param 装饰器实践
- HarmonyOS Next 状态管理:Once 装饰器实践
- HarmonyOS Next 状态管理:Event 装饰器实践
- HarmonyOS Next 状态管理:!! 状态装饰器实践
- HarmonyOS Next 状态管理:@ObserverV2和@Trace 装饰器实践
- HarmonyOS Next 状态管理:Provider和Consumer 装饰器实践
一. @Param 修饰器概述
@Param 修饰器用于修饰组件中的变量,支持从外部传入或自身初始化。它使得父子组件之间的数据传递更加灵活,当父组件的数据发生变化时,子组件中关联 @Param 的变量也会同步更新,从而触发 UI 刷新。
二. @Param 修饰器的限制
- 使用范围:
@Param只能在ComponentV2中使用。 - 初始化限制:
@Param修饰的变量只能初始化一次,之后不可再次初始化。 - 优先级:
@Param支持由父组件的数据初始化,也支持自身初始化。若父组件和自身同时初始化,则优先使用父组件的数据。 - 单向绑定:
@Param修饰的变量与父组件形成单向绑定。当父组件中的状态变量发生变化时,子组件中@Param修饰的变量会同步更新。
三. @Param 的语法与特性
@ComponentV2
struct ChildComponent {
@Param msg: string // 错误!
@Param name: string = "孙膑"
@Param @Require occupation: string
build() {
Column() {
Text(this.msg)
.onClick(() => {
// this.msg = "自组件内修改数据"
})
}
}
}
关键点:
- 初始化要求:
@Param修饰的变量必须初始化,否则会报错。例如,@Param msg: string未初始化,会提示错误:When a variable decorated with @Param is not assigned a default value, it must also be decorated with @Require.。 - 可选初始化:如果
@Param修饰的变量不需要初始化,则必须添加@Require修饰器。此时,父组件在创建子组件时必须传入该参数。
四、实践探索
4.1 修饰基础类型
@Param 可以直接修饰基础类型(如 string、number、boolean、enum、null、undefined 等),并在变量变化时更新关联的 UI 组件。
enum Gender {
Female,
Male
}
@Entry
@ComponentV2
struct Index {
msg: string = "非状态变量"
@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
// @Local skillCount: any = 4 // 不支持
// @Local skill: unknown = 3 // 不支持
build() {
Column({ space: 10 }) {
ChildComponent({cMsg: this.msg, cName: this.name, cAge: this.age, cIsMaimed: this.isMaimed,
cOccupation: this.occupation, cConsort: this.consort, cGender: this.gender })
Button('更新数据')
.onClick(() => {
this.msg = "非状态变量" + `${Math.round(Math.random() * 1000)}`
this.name = "小乔"+ `${Math.round(Math.random() * 1000)}`
this.age++
this.isMaimed = !this.isMaimed
this.occupation = "未知" + `${Math.round(Math.random() * 1000)}`
this.consort = "周瑜" + `${Math.round(Math.random() * 1000)}`
this.gender = (this.gender == Gender.Female) ? Gender.Male : Gender.Female
})
}
.height('100%')
.width('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Require cMsg: string
@Param @Require cName: string
@Param @Require cAge: number = 100
@Param @Require cIsMaimed: boolean = false
@Param @Require cOccupation: undefined | string
@Param @Require cConsort: null | string
@Param @Require cGender: Gender
build() {
Column({space: 8}) {
Text(`msg:${this.cMsg}`)
Text(`name:${this.cName}`)
Text(`age:${this.cAge}`)
Text(`isMaimed:${this.cIsMaimed}`)
Text(`occupation:${this.cOccupation}`)
Text(`consort:${this.cConsort}`)
Text(`gender:${this.cGender}`)
}
}
}
关键点:
- 非状态变量的更新:非状态变量与
@Param修饰的变量绑定后,当非状态变量更新时,@Param修饰的变量也会同步更新。 - 支持的基础类型:
@Param支持修饰string、number、boolean、enum、null、undefined等基础类型。 - 不支持的类型:
@Param不支持修饰any和unknown类型。
4.2 修饰容器类型
以数组为例,其他容器类型(如 Array、Tuple、Set、Map)的行为类似。
@Entry
@ComponentV2
struct Index {
@Local strings: string[] = ["a", "b", "c"]
digits: number[] = [1, 2, 3]
@Local names: Set<string> = new Set(["孙膑"])
@Local owners: Map<string, string> = new Map([["勾践", "湛卢"]])
build() {
Column({ space: 10 }) {
ChildComponent({cStrings: this.strings, cDigits: this.digits, cNames: this.names, cOwners: this.owners})
Button('更新数据')
.onClick(() => {
this.strings.push("d")
this.digits.push(4)
this.names.add("庞涓")
this.owners.set("皇帝", "轩辕")
})
Button('清除数据')
.onClick(() => {
this.strings = []
this.digits = []
this.names.clear()
this.owners.clear()
})
Button('重新赋值')
.onClick(() => {
this.strings = ["c", "b", "a"]
this.digits = [3, 2, 1]
this.names = new Set(["孙悟空"])
this.owners = new Map([["黑神话", "悟空"]])
})
}
.height('100%')
.width('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Require cStrings: string[] = []
@Param @Require cDigits: number[] = []
@Param @Require cNames: Set<string>
@Param @Require cOwners: Map<string, string>
build() {
Column({space: 12}) {
Row({space: 8}) {
Text("字符串")
ForEach(this.cStrings, (item: string) => {
Text(item)
.fontColor(Color.Red)
})
}
Row({space: 8}) {
Text("数字")
ForEach(this.cDigits, (item: number) => {
Text(item.toString())
.fontColor(Color.Orange)
})
}
Row({space: 8}) {
Text("姓名")
ForEach(Array.from(this.cNames), (item: string) => {
Text(item)
.fontColor(Color.Blue)
})
}
Row({space: 8}) {
Text("名册")
ForEach(Array.from(this.cOwners.entries()), (item: [string, string]) => {
Text(`持有者: ${item[0]} 兵器: ${item[1]}`)
.fontColor(Color.Gray)
})
}
}
}
}
关键点:
- 数组的更新:
@Param修饰的数组变量可以观察到数据源对数组的整体赋值以及调用数组的接口(如push、pop等)。 - 其他容器类型:
@Param修饰的Set、Map等容器类型变量也可以观察到数据源的整体赋值和增删改查操作。 - 非状态变量的绑定:非状态变量与
@Param修饰的容器变量绑定后,数据源的变化会触发子组件的更新。
4.3 修饰实例对象
以 UserInfo 类为例,研究 @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("孙膑", 29)
@Local msg: string = "一条消息"
build() {
Column({ space: 10 }) {
Text(`父组件:${this.userInfo.name}, age:${this.userInfo.age}, msg:${this.msg}`)
Button('更新数据')
.onClick(() => {
this.userInfo.name = "庞涓"
this.userInfo.age = 30
this.msg = this.msg + `${Math.round(Math.random() * 100)}`
})
Button('赋值')
.onClick(() => {
this.userInfo = new UserInfo("大乔", 18)
this.msg += "new one"
})
ChildComponent({cUserInfo: this.userInfo, cMsg: this.msg})
}
.height('100%')
.width('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Require cUserInfo: UserInfo
@Param @Require cMsg: string
build() {
Column({space: 12}) {
Text(`name:${this.cUserInfo.name}, age:${this.cUserInfo.age}`)
.onClick(() => {
this.cUserInfo.name = "孟尝君"
this.cUserInfo.age = 37
// this.cMsg = "这是子组件的消息" // Cannot assign to 'cMsg' because it is a read-only property.
})
Text(`${this.cMsg}`)
.onClick(() => {
// this.cUserInfo = new UserInfo("月未央", 35)
// this.cMsg = "月未央"
})
}
}
}
关键点:
- 实例属性的更新:
@Param修饰的实例变量在数据源更新实例属性时,不会触发子组件的 UI 更新。 - 基础类型的不可变性:
@Param修饰的基础类型变量是只读的,尝试修改会报错:Cannot assign to 'cMsg' because it is a read-only property.。 - 实例变量的不可重新赋值:
@Param修饰的实例变量不可重新赋值,但可以修改其属性。属性值的变化不会触发父子组件的 UI 更新。
4.4 修饰可选类型
对于可选类型,我们研究实例变量和普通变量
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("孙膑", 29)
@Local msg: string = "一条消息"
build() {
Column({ space: 10 }) {
Text(`父组件:${this.userInfo.name}, age:${this.userInfo.age}, msg:${this.msg}`)
Button('更新数据')
.onClick(() => {
this.userInfo.name = "庞涓"
this.userInfo.age = 30
this.msg = this.msg + `${Math.round(Math.random() * 100)}`
})
Button('赋值')
.onClick(() => {
this.userInfo = new UserInfo("大乔", 18)
this.msg += "new one"
})
ChildComponent({
cUserInfo: this.userInfo,
cMsg: this.msg
})
}
.height('100%')
.width('100%')
}
}
@ComponentV2
struct ChildComponent {
@Param @Require cUserInfo?: UserInfo
@Param @Require cMsg?: string
build() {
Column({space: 12}) {
Text(`name:${this.cUserInfo?.name}, age:${this.cUserInfo?.age}`)
.onClick(() => {
this.cUserInfo!.name = "月未央"
this.cUserInfo!.age = 35
// this.cMsg = "new one"
})
Text(`${this.cMsg}`)
}
}
}
关键点:
- 可选类型的初始化:
@Param修饰的可选类型变量可以不初始化,但需要添加@Require修饰器。 - 实例属性的更新:
@Param修饰的实例变量在数据源更新实例属性时,不会触发子组件的 UI 更新。 - 基础类型的不可变性:
@Param修饰的基础类型变量是只读的,尝试修改会报错。
4.5 父子组件的双向更新
有时候我们需要在子组件中更新数据,同时父子组件的UI同步刷新。除了使用@Event修饰器外,我们尝试用其他的方式来解决。
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("孙膑", 29)
build() {
Column({ space: 10 }) {
Text(`父组件:${this.userInfo.name}, age:${this.userInfo.age}`)
Button('赋值')
.onClick(() => {
this.userInfo = new UserInfo("大乔", 18)
})
ChildComponent({
cUserInfo: this.userInfo,
callback: (name: string, age: number) => {
this.userInfo = new UserInfo(name, age)
}
})
}
.height('100%')
.width('100%')
}
}
关键点:
- 回调函数的使用:通过
@Param修饰的回调函数,可以在子组件中更新数据并通知父组件,从而实现父子组件的双向更新。 - 数据同步:父组件接收到子组件的回调后,更新数据并触发 UI 刷新。
总结
@Param 修饰器在父子组件数据传递中起到了关键作用,支持基础类型、容器类型、实例对象等多种数据类型的绑定。通过合理使用 @Param,可以实现父子组件之间的数据同步和 UI 更新。
1622

被折叠的 条评论
为什么被折叠?



