HarmonyOS Next 开发系列:Local 状态管理实践

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

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

以下是一个示例:

less 复制代码
@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 修饰的状态变量必须显式指定类型,不支持类型推断。
less 复制代码
@Local message = 'Hello World'; // 错误:必须指定类型
//上述代码会报错: The property 'message' must specify a type.

@Local message: string = 'Hello World'; // 正确
  1. 初始化要求@Local 修饰的状态变量必须"初始化"。

@Local 的语法与特性

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

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

五、实践探索

5.1 修饰基础类型

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

typescript 复制代码
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

完整代码:

kotlin 复制代码
@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 更新。

less 复制代码
@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 刷新,只有重新赋值时才会触发。

typescript 复制代码
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的作用

typescript 复制代码
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中可选类型的问号标记在变量名之后,如下码所示。

c 复制代码
name?: string

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

typescript 复制代码
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、鸿蒙开发者官方文档

相关推荐
一个处女座的程序猿O(∩_∩)O2 小时前
鸿蒙开发技术指南:从入门到精通
华为·harmonyos
陈无左耳、4 小时前
HarmonyOS学习第4天: DevEco Studio初体验
学习·华为·harmonyos
openinstall全渠道统计19 小时前
免填邀请码工具:赋能六大核心场景,重构App增长新模型
android·ios·harmonyos
shaodong112319 小时前
鸿蒙系统-同应用跨设备数据同步(分布式功能)
分布式·华为·harmonyos
敢嗣先锋19 小时前
鸿蒙5.0实战案例:基于ImageKit对图片进行处理
移动开发·harmonyos·arkui·组件化·鸿蒙开发
陈无左耳、19 小时前
HarmonyOS学习第3天: 环境搭建开启鸿蒙开发新世界
学习·华为·harmonyos
敢嗣先锋19 小时前
鸿蒙5.0实战案例:基于ArkUI的验证码实现
移动开发·harmonyos·openharmony·arkui·鸿蒙开发
别说我什么都不会1 天前
鸿蒙轻内核M核源码分析系列十二 事件Event
操作系统·harmonyos
Huang兄1 天前
鸿蒙-canvas-刮刮乐
华为·harmonyos