HarmonyOS Next 状态管理:Param 装饰器实践

目录

一. @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 可以直接修饰基础类型(如 stringnumberbooleanenumnullundefined 等),并在变量变化时更新关联的 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 支持修饰 stringnumberbooleanenumnullundefined 等基础类型。
  • 不支持的类型@Param 不支持修饰 anyunknown 类型。
4.2 修饰容器类型

以数组为例,其他容器类型(如 ArrayTupleSetMap)的行为类似。

@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 修饰的数组变量可以观察到数据源对数组的整体赋值以及调用数组的接口(如 pushpop 等)。
  • 其他容器类型@Param 修饰的 SetMap 等容器类型变量也可以观察到数据源的整体赋值和增删改查操作。
  • 非状态变量的绑定:非状态变量与 @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 更新。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值