一、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)
}
}
}
在这个例子中,ChildComponent
的 msg
属性虽然从外部初始化,但组件内部无法感知到外部传入的值。
二、@Local 修饰器概述
在 V2 版本中,ArkTS 引入了 @Local
修饰器,用于管理组件内部的状态变量。与 @State
不同,@Local
修饰的状态变量仅在组件内部有效,且当状态变量发生变化时,依赖该变量的组件会自动重新渲染。
需要注意的是:
@Local
只能在ComponentV2
中使用。- V2 版本的状态修饰器与 V1 版本的状态修饰器不能混用。
三、@Local 的约束
- 显式类型声明 :
@Local
修饰的状态变量必须显式指定类型,不支持类型推断。
less
@Local message = 'Hello World'; // 错误:必须指定类型
//上述代码会报错: The property 'message' must specify a type.
@Local message: string = 'Hello World'; // 正确
- 初始化要求 :
@Local
修饰的状态变量必须"初始化"。
@Local 的语法与特性
@Local
修饰的变量称为状态变量。与普通变量的区别在于,状态变量的变化会触发 UI 的自动更新,从而实现响应式编程。
less
@Entry
@ComponentV2
struct Index {
@Local message: string = '这是一条信息'; // 状态变量
name: string = '这是另一条信息'; // 普通变量
}
五、实践探索
5.1 修饰基础类型
@Local
可以直接修饰基础类型(如 string
、number
、boolean
、null
、undefined
),并在变量变化时更新关联的 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 刷新。undefined
和null
需要结合联合类型使用。
5.2 修饰容器类型
@Local
可以修饰容器类型(如 Array
、Tuple
、Set
、Map
),并在容器变化时触发 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
修饰器的使用场景和约束条件:
- 基础类型 :
@Local
可以直接修饰基础类型,并在变化时触发 UI 刷新。 - 容器类型 :
@Local
修饰的容器类型在增删改查时会触发 UI 刷新。 - 对象类型 :
@Local
修饰的对象类型只有在重新赋值时才会触发 UI 刷新。 - 可选类型 :
@Local
支持修饰可选类型,并在变化时触发 UI 刷新。
对于更复杂的场景(如监听对象属性的变化),可以结合 @ObservedV2
和 @Trace
来实现。