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、鸿蒙开发者官方文档

相关推荐
遇到困难睡大觉哈哈8 小时前
HarmonyOS —— Remote Communication Kit 拦截器(Interceptor)高阶定制能力笔记
笔记·华为·harmonyos
遇到困难睡大觉哈哈10 小时前
HarmonyOS —— Remote Communication Kit 定制处理行为(ProcessingConfiguration)速记笔记
笔记·华为·harmonyos
氤氲息10 小时前
鸿蒙 ArkTs 的WebView如何与JS交互
javascript·交互·harmonyos
遇到困难睡大觉哈哈10 小时前
HarmonyOS支付接入证书准备与生成指南
华为·harmonyos
赵浩生10 小时前
鸿蒙技术干货10:鸿蒙图形渲染基础,Canvas绘图与自定义组件实战
harmonyos
赵浩生10 小时前
鸿蒙技术干货9:deviceInfo 设备信息获取与位置提醒 APP 整合
harmonyos
BlackWolfSky10 小时前
鸿蒙暂未归类知识记录
华为·harmonyos
L、21813 小时前
Flutter 与开源鸿蒙(OpenHarmony):跨平台开发的新未来
flutter·华为·开源·harmonyos
L、21813 小时前
Flutter 与 OpenHarmony 深度融合实践:打造跨生态高性能应用(进阶篇)
javascript·flutter·华为·智能手机·harmonyos