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

目录

一、V1 版本中 @State 的局限性

在 ArkTS 的 V1 版本中,@State 用于修饰状态变量。当状态变量发生变化时,依赖该变量的组件会自动更新。然而,@State 存在一个明显的局限性:状态变量可以从外部初始化,但组件内部无法感知到外部初始化的变化

以下是一个示例:

@Entry
    @Component
    struct Index {
      @State message: string = 'Hello World';

      build() {
        Column({ space: 10 }) {
          ChildComponent({ msg: '这是外部给到的初始化信息'})
        }
        .height('100%')
        .width('100%')
      }
    }

    @Component
    struct ChildComponent {
      @State msg: string = '这个信息是在组件内部初始化';

      build() {
        Column() {
          Text(this.msg)
        }
      }
    }

在这个例子中,ChildComponentmsg 属性虽然从外部初始化,但组件内部无法感知到外部传入的值。

二、@Local 修饰器概述

在 V2 版本中,ArkTS 引入了 @Local 修饰器,用于管理组件内部的状态变量。与 @State 不同,@Local 修饰的状态变量仅在组件内部有效,且当状态变量发生变化时,依赖该变量的组件会自动重新渲染。

需要注意的是:

  • @Local 只能在 ComponentV2 中使用。
  • V2 版本的状态修饰器与 V1 版本的状态修饰器不能混用

三、@Local 的约束

  1. 显式类型声明@Local 修饰的状态变量必须显式指定类型,不支持类型推断。
@Local message = 'Hello World'; // 错误:必须指定类型
//上述代码会报错: The property 'message' must specify a type.

@Local message: string = 'Hello World'; // 正确

  1. 初始化要求@Local 修饰的状态变量必须"初始化"。

四、@Local 的语法与特性

@Local 修饰的变量称为状态变量。与普通变量的区别在于,状态变量的变化会触发 UI 的自动更新,从而实现响应式编程。

    @Entry
    @ComponentV2
    struct Index {
      @Local message: string = '这是一条信息'; // 状态变量
      name: string = '这是另一条信息';         // 普通变量
    }

五、实践探索

5.1 修饰基础类型

@Local 可以直接修饰基础类型(如 stringnumberbooleannullundefined),并在变量变化时更新关联的 UI 组件。

message: string = "Hello World"
@Local name: string = "孙膑"
@Local age: number = 25
@Local isMaimed: boolean = true
@Local occupation: undefined = undefined
@Local consort: null = null
@Local skillCount: any = 4
@Local skill: unknown = 3

❌ 报错: Use explicit types instead of “any”, “unknown” (arkts-no-any-unknown)

所以,@Local修饰的基础类型不能包含: any和unknow

完整代码:

@Entry
    @ComponentV2
    struct Index {
      message: string = "Hello World"
      @Local name: string = "孙膑"
      @Local age: number = 25
      @Local isMaimed: boolean = true
      @Local occupation: undefined | string = undefined
      @Local consort: null | string = null

      build() {
        Column({ space: 10 }) {
          Text(`将军:(${this.name}),年龄:${this.age},被刑否:${this.isMaimed},
                职业:${this.occupation},配偶:${this.consort}`)
            .fontSize(18)
            .fontStyle(FontStyle.Italic)
          Button('更新数据')
            .onClick(() => {
              this.message = this.message + "ab"
              this.name = this.name == "孙膑" ? "庞涓" : "孙膑"
              this.age++
              this.isMaimed = !this.isMaimed
              this.occupation = this.occupation === undefined ? "军师" : undefined
              this.consort = this.consort === null ? "王氏" : null
            })
        }
        .height('100%')
        .width('100%')
      }
    }

关键点

  • @Local 修饰的变量即使变更也不会引起 UI 刷新。
  • @Local 修饰的基础变量变化后会触发 UI 刷新。
  • undefinednull 需要结合联合类型使用。
5.2 修饰容器类型

@Local 可以修饰容器类型(如 ArrayTupleSetMap),并在容器变化时触发 UI 更新。

@Entry
    @ComponentV2
    struct Index {
      @Local scholars: string[] = ["孙膑", "苏秦"]
      @Local profile: [string, number] = ["孙膑", 25]
      @Local swords: Set<string> = new Set(["轩辕", "太阿"])
      @Local owners: Map<string, string> = new Map([["皇帝", "轩辕"], ["勾践", "湛卢"]])

      build() {
        Column({ space: 10 }) {
          Text('人物:' + `${this.scholars}`)
          Text('人物信息:' + `${this.profile[0]}, 年龄:${this.profile[1]}`)
          Text('名剑山庄')
          ForEach(Array.from(this.swords), (sword: string) => {
            Text(sword)
          })
          Text('名册')
          ForEach(Array.from(this.owners.entries()), (item: [string, string]) => {
            Text(`持有者: ${item[0]} 兵器: ${item[1]}`)
          })
          Button('增加数据')
            .onClick(() => {
              this.scholars.push("庞涓")
              this.swords.add("龙泉")
              this.owners.set("不详", "龙泉")
            })
          Button('删除数据')
            .onClick(() => {
              this.scholars.pop()
              this.profile = ["", -1]
              this.swords.delete("龙泉")
              this.owners.delete("不详")
            })
          Button('替换数据')
            .onClick(() => {
              this.scholars[0] = "扶苏"
              this.profile = ["张三丰", 200]
              this.swords.delete("轩辕")
              this.swords.add("鱼肠")
              this.owners["皇帝"] = "圣剑"
            })
          Button('清除数据')
            .onClick(() => {
              this.scholars = []
              this.profile = [String(), Number()]
              this.swords.clear()
              this.owners.clear()
            })
          Button('新创建')
            .onClick(() => {
              this.scholars = ["孙膑", "苏秦"]
              this.profile = ["孙膑", 25]
              this.swords = new Set(["轩辕", "太阿"])
              this.owners = new Map([["皇帝", "轩辕"], ["勾践", "湛卢"]])
            })
        }
        .height('100%')
        .width('100%')
      }
    }

关键点

  • @Local 修饰的容器对象在增删改查时会触发 UI 刷新。
5.3 修饰对象类型

@Local 修饰的对象类型在属性变化时不会触发 UI 刷新,只有重新赋值时才会触发。

class Country {
      name: string
      code: string

      constructor(name: string, code: string) {
        this.name = name
        this.code = code
      }
    }

    class UserInfo {
      name: string
      age: number
      nationality: Country

      constructor(name: string, age: number, nationality: Country) {
        this.name = name
        this.age = age
        this.nationality = nationality
      }
    }

    @Entry
    @ComponentV2
    struct Index {
      @Local userInfo: UserInfo = new UserInfo("孙膑", 22, new Country("中国", "156"))

      build() {
        Column({ space: 10 }) {
          Text(`姓名:${this.userInfo.name},年龄:${this.userInfo.age},
                国籍:${this.userInfo.nationality.name},国家码:${this.userInfo.nationality.code}`)
          Button('更新姓名')
            .onClick(() => {
               this.userInfo.name = "庞涓"
            })
          Button('更新国家名称')
            .onClick(() => {
              this.userInfo.nationality.name = "CHN"
            })
          Button('更新国籍')
            .onClick(() => {
              this.userInfo.nationality = new Country("CHN", "156")
            })
          Button('更新用户')
            .onClick(() => {
              this.userInfo = new UserInfo("孟尝君", 30, new Country("齐国", "156"))
            })
        }
        .height('100%')
        .width('100%')
      }
    }

关键点

  • @Local修饰的实例对象的属性变化不会触发UI的刷新
  • @Local修饰的实例对象的嵌套对象以及嵌套对象的属性变化均不会触发UI的刷新
  • @Local修饰的实例对象的赋值会触发UI的刷新
    那么如何监听类的属性变化,从而来更新UI呢,会使用到后面说的 @ObservedV2和@Trace这两个状态修饰器。
5.4 容器对象类型

这里以数组容器包含类对象来研究,分别尝试怎删改查对于UI的作用

class Country {
      name: string
      code: string

      constructor(name: string, code: string) {
        this.name = name
        this.code = code
      }
    }

    class UserInfo {
      name: string
      age: number
      nationality: Country

      constructor(name: string, age: number, nationality: Country) {
        this.name = name
        this.age = age
        this.nationality = nationality
      }
    }

    @Entry
    @ComponentV2
    struct Index {
      @Local userInfos: UserInfo[] = [new UserInfo("孙膑", 22, new Country("中国", "156"))]

      build() {
        Column({ space: 10 }) {
          ForEach(this.userInfos, (userInfo: UserInfo) => {
            Text(`${userInfo.name},年龄:${userInfo.age},国籍:${userInfo.nationality.name},国家码:${userInfo.nationality.code}`)
          })
          Button('增加用户')
            .onClick(() => {
              this.userInfos.push(new UserInfo("孟尝君", 30, new Country("齐国", "156")))
            })
          Button('删除用户')
            .onClick(() => {
              this.userInfos.shift()
            })
          Button('清空用户')
            .onClick(() => {
              this.userInfos = []
            })
          Button('更新用户姓名')
            .onClick(() => {
              this.userInfos[0].name = ""
            })
          Button('更新用户国籍')
            .onClick(() => {
              this.userInfos[0].nationality.name = "CHN"
            })
        }
        .height('100%')
        .width('100%')
      }
    }
  • @Local修饰的容器数组本身的增删改查会触发UI的刷新,与其内容器元素类型无关
  • @Local修饰的容器数组,元素是实例,那么对于实例的操作均不会触发UI的刷新;若想要触发UI的刷新,需要结合@ObservedV2和@Trace来达到目标效果
5.3 可选类型

arkts支持可选类型,并使用?来标记。不同于其他语言的可选类型标记,arkts中可选类型的问号标记在变量名之后,如下码所示。

name?: string

对于可选类型的普通变量,容器变量和实例变量,使用@Local是否会触发UI刷新呢?

class Country {
      name: string
      code: string

      constructor(name: string, code: string) {
        this.name = name
        this.code = code
      }
    }

    class UserInfo {
      name: string
      age: number
      nationality: Country

      constructor(name: string, age: number, nationality: Country) {
        this.name = name
        this.age = age
        this.nationality = nationality
      }
    }

    @Entry
    @ComponentV2
    struct Index {
      @Local userInfos?: UserInfo[]
      @Local msgs?: string[]
      @Local brandName?: string
      @Local address?: string = ""

      build() {
        Column({ space: 10 }) {
          Text(`乐队名称:${this.brandName}`)
          Text(`地址:${this.address}`)
          Text(`用户信息: ${typeof this.userInfos}`)
          ForEach(this.userInfos, (userInfo: UserInfo) => {
            Text(`${userInfo.name},年龄:${userInfo.age},国籍:${userInfo.nationality.name},国家码:${userInfo.nationality.code}`)
          })
          Text(`消息数组: ${typeof this.msgs}`)
          ForEach(this.msgs, (msg: string) => {
            Text('msg:' + `${msg}`)
          })
          Button('修改组合名称:')
            .onClick(() => {
              this.brandName = (this.brandName == undefined ? "海尔兄弟" : undefined)
            })
          Button('修改地址:')
            .onClick(() => {
              this.address = (this.address == undefined ? "大秦古道" : undefined)
            })
          Button('修改用户')
            .onClick(() => {
              if (this.userInfos == undefined) {
                this.userInfos = [new UserInfo("孙膑", 22, new Country("中国", "156"))]
              } else {
                this.userInfos = undefined
              }
            })
          Button('修改消息')
            .onClick(() => {
              this.msgs = (this.msgs == undefined ? [`${Math.round(Math.random() * 1000)}`] : undefined)
            })
        }
        .height('100%')
        .width('100%')
      }
    }
  • @Local修饰的变量是非可选类型的情况下,一定需要初始化。所以在文章的开头,对于@Local的变量"初始化"是加了引号的,需要了解到这个点。
  • @Local修饰的可选类型变量默认指是undefined
  • 对于容器和对象的可选类型约束同之前的容器和对象类型

六、总结

通过本文的实践探索,我们深入了解了 @Local 修饰器的使用场景和约束条件:

  1. 基础类型@Local 可以直接修饰基础类型,并在变化时触发 UI 刷新。
  2. 容器类型@Local 修饰的容器类型在增删改查时会触发 UI 刷新。
  3. 对象类型@Local 修饰的对象类型只有在重新赋值时才会触发 UI 刷新。
  4. 可选类型@Local 支持修饰可选类型,并在变化时触发 UI 刷新。

对于更复杂的场景(如监听对象属性的变化),可以结合 @ObservedV2@Trace 来实现。

参考文献:

1、鸿蒙开发者官方文档

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值