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

发布于:2025-03-18 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

一. @Once 修饰器概述

@Once 装饰器用于修饰变量,确保该变量仅在初始化时接受一次外部传入的值。初始化完成后,即使数据源发生变化,@Once 修饰的变量也不会’随之更新’。

二、@Once 修饰器的限制

使用范围:

- @Once 只能在 ComponentV2 中使用。
搭配使用

-   @Once 必须与 @Param 一起使用,不能单独使用或与其他装饰器搭配。

-   @Once 可以放置在 @Param 之前或之后。
功能等效

-   @Once 和 @Param 组合使用时,功能上可以看似等同于 @Local,但是效果比@Local强大。
数据拦截

-   @Once 仅拦截数据源的变化,不会限制 @Param 的观测能力。

三、实践探索

3.1 修饰基础类型

@Once 和 @Param 可以修饰基础类型(如 stringnumberbooleanenumnullundefined 等),并在变量变化时更新关联的 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 支持修饰 stringnumberbooleanenumnullundefined 等基础类型,初始化后将拦截数据源的变化。
  • 不支持的类型@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 修饰容器对象

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


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 修饰的变量值同样可以修改,且不会影响到父组件(数据源)