状态管理
状态管理是 ArkUI 的核心概念。当状态变量变化时,UI 会自动刷新。ArkUI 提供了丰富的状态管理装饰器,帮助开发者在不同场景下高效管理组件状态和全局状态。
本文档系统介绍 ArkUI 状态管理的完整知识体系,包括 V1 状态管理、V2 状态管理(API 12+)、全局状态管理以及最佳实践。
V1 状态管理
V1 状态管理是 ArkUI 早期版本提供的状态管理体系,适用于 API 9~API 11 的项目,在 API 12+ 中仍然兼容可用。
@State(组件内部状态)
@State 是组件内部的状态装饰器,用于声明组件的私有状态。被 @State 装饰的变量必须初始化,修改后会自动触发 UI 刷新。
基本用法
@Entry
@Component
struct CounterPage {
@State count: number = 0
@State message: string = '点击按钮'
build() {
Column() {
Text(this.message)
.fontSize(18)
.margin(20)
Text(`${this.count}`)
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#007DFF')
Row({ space: 16 }) {
Button('-')
.width(60)
.onClick(() => {
if (this.count > 0) {
this.count--
}
})
Button('+')
.width(60)
.onClick(() => {
this.count++
if (this.count >= 10) {
this.message = '已达到最大值!'
}
})
}
.margin({ top: 20 })
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}支持的类型
@State 支持以下类型的状态变量:
| 类型 | 说明 | 示例 |
|---|---|---|
| 简单类型 | number、string、boolean、enum | @State count: number = 0 |
| 对象类型 | 自定义类实例(需配合 @Observed) | @State person: Person = new Person() |
| 数组类型 | 数组整体赋值可观测 | @State list: number[] = [1, 2, 3] |
| Date 类型 | Date 对象 | @State date: Date = new Date() |
使用限制
- 必须初始化:
@State装饰的变量必须在声明时或构造函数中初始化 - 仅当前组件可用:状态变量仅在当前组件内有效,子组件无法直接访问
- 不支持从外部传入:
@State变量不能通过组件参数从父组件传入 - 数组元素修改限制:直接修改数组元素(如
this.arr[0] = 1)不会触发刷新,需要使用数组 API(push、splice等)或整体赋值
@Entry
@Component
struct StateDemo {
@State arr: number[] = [1, 2, 3]
build() {
Column() {
Text(`数组: ${JSON.stringify(this.arr)}`)
Button('错误修改(不会刷新)')
.onClick(() => {
this.arr[0] = 100 // ❌ 不会触发 UI 刷新
})
Button('正确修改(会刷新)')
.onClick(() => {
this.arr.push(4) // ✅ 触发 UI 刷新
// 或:this.arr = [...this.arr, 4]
})
}
}
}@Prop(单向传递)
@Prop 用于父组件向子组件单向传递数据。父组件状态变化会同步到子组件,但子组件修改该变量不会影响父组件(值拷贝机制)。
基本用法
// 子组件
@Component
struct ChildProp {
@Prop count: number // 从父组件接收
build() {
Column() {
Text(`子组件计数: ${this.count}`)
.fontSize(16)
.fontColor('#666')
// 子组件可以修改,但不会影响父组件
Button('子组件+1')
.onClick(() => {
this.count++ // ✅ 可以修改,但仅在子组件生效
})
}
.padding(16)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
}
// 父组件
@Entry
@Component
struct ParentPage {
@State parentCount: number = 0
build() {
Column({ space: 20 }) {
Text(`父组件计数: ${this.parentCount}`)
.fontSize(20)
ChildProp({ count: this.parentCount })
Button('父组件+1')
.onClick(() => {
this.parentCount++
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}所有使用场景
场景 1:父传子基础用法
@Component
struct UserCard {
@Prop name: string
@Prop age: number
build() {
Column() {
Text(`姓名: ${this.name}`)
Text(`年龄: ${this.age}`)
}
}
}
// 使用
UserCard({ name: '张三', age: 25 })场景 2:传递对象属性
class UserInfo {
name: string = ''
avatar: string = ''
}
@Component
struct UserProfile {
@Prop user: UserInfo // 传递对象
build() {
Column() {
Text(this.user.name)
Image(this.user.avatar)
}
}
}场景 3:配合 $ 语法传递(数组元素)
@Entry
@Component
struct ListPage {
@State items: string[] = ['苹果', '香蕉', '橙子']
build() {
List() {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
ItemView({ item: item }) // @Prop 接收
}
})
}
}
}
@Component
struct ItemView {
@Prop item: string
build() {
Text(this.item)
}
}使用限制
- 深拷贝机制:
@Prop采用深拷贝,频繁传递大对象会影响性能 - 同步方向:只能从父到子单向同步,子组件修改不会回传
- 必须声明类型:
@Prop变量必须显式声明类型 - 不可与
@State混用同一变量:父组件用@State,子组件用@Prop接收
@Link(双向绑定)
@Link 用于父子组件之间的双向数据绑定。父子组件共享同一个数据源,任意一方修改都会同步到另一方。
基本用法
// 子组件
@Component
struct ChildLink {
@Link count: number // 双向绑定
build() {
Column() {
Text(`子组件: ${this.count}`)
.fontSize(16)
Button('子组件+1')
.onClick(() => {
this.count++ // ✅ 可以修改,会同步到父组件
})
}
.padding(16)
.backgroundColor('#E8F4FD')
.borderRadius(8)
}
}
// 父组件
@Entry
@Component
struct ParentPage {
@State sharedCount: number = 0
build() {
Column({ space: 20 }) {
Text(`父组件: ${this.sharedCount}`)
.fontSize(20)
// 传递时使用 $ 符号
ChildLink({ count: $sharedCount })
Button('父组件+1')
.onClick(() => {
this.sharedCount++
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}所有使用场景
场景 1:父子组件共享计数器
@Component
struct Counter {
@Link value: number
build() {
Button(`计数: ${this.value}`)
.onClick(() => this.value++)
}
}
@Entry
@Component
struct Page {
@State count: number = 0
build() {
Column() {
Text(`父: ${this.count}`)
Counter({ value: $count }) // 双向绑定
}
}
}场景 2:表单双向绑定
@Component
struct InputField {
@Link text: string
@Prop placeholder: string
build() {
TextInput({ placeholder: this.placeholder, text: $$this.text })
.onChange((value: string) => {
this.text = value
})
}
}
@Entry
@Component
struct FormPage {
@State username: string = ''
@State password: string = ''
build() {
Column() {
InputField({ text: $username, placeholder: '请输入用户名' })
InputField({ text: $password, placeholder: '请输入密码' })
Text(`输入内容: ${this.username} / ${this.password}`)
}
}
}场景 3:数组元素双向绑定
@Component
struct EditableItem {
@Link item: string
build() {
TextInput({ text: $$this.item })
}
}
@Entry
@Component
struct EditableList {
@State items: string[] = ['项目1', '项目2', '项目3']
build() {
List() {
ForEach(this.items, (item: string, index: number) => {
ListItem() {
EditableItem({ item: $items[index] }) // 数组元素双向绑定
}
})
}
}
}使用限制
- 必须使用
$传递:父组件传递@Link变量时必须使用$variable语法 - 不能独立初始化:
@Link变量不能设置默认值,必须从父组件传入 - 类型必须匹配:父子组件的变量类型必须一致
- 不能用于深层嵌套对象:
@Link主要用于简单类型和数组,复杂对象建议使用@ObjectLink
@Provide / @Consume(跨层级共享)
@Provide 和 @Consume 用于跨越多层组件共享状态,无需逐层传递。祖先组件通过 @Provide 提供状态,后代组件通过 @Consume 消费状态。
基本用法
// 祖先组件提供状态
@Entry
@Component
struct GrandParent {
@Provide('themeColor') themeColor: string = '#007DFF'
@Provide('fontSize') fontSize: number = 16
build() {
Column() {
Text('祖父组件')
.fontColor(this.themeColor)
.fontSize(this.fontSize)
Parent()
Button('切换主题')
.onClick(() => {
this.themeColor = this.themeColor === '#007DFF' ? '#FF6B6B' : '#007DFF'
})
}
.width('100%')
.height('100%')
.padding(20)
}
}
// 中间组件(无需传递)
@Component
struct Parent {
build() {
Column() {
Text('父组件')
Child()
}
}
}
// 后代组件消费状态
@Component
struct Child {
@Consume('themeColor') themeColor: string
@Consume('fontSize') fontSize: number
build() {
Column() {
Text('子组件')
.fontColor(this.themeColor)
.fontSize(this.fontSize)
}
.padding(16)
.backgroundColor('#F5F5F5')
}
}所有使用场景
场景 1:主题系统
// 主题配置类
class ThemeConfig {
primaryColor: string = '#007DFF'
backgroundColor: string = '#FFFFFF'
textColor: string = '#333333'
fontSize: number = 16
}
@Entry
@Component
struct App {
@Provide('theme') theme: ThemeConfig = new ThemeConfig()
build() {
Column() {
Header()
Content()
Footer()
}
}
}
@Component
struct Header {
@Consume('theme') theme: ThemeConfig
build() {
Row() {
Text('标题')
.fontColor(this.theme.primaryColor)
.fontSize(this.theme.fontSize + 4)
}
.backgroundColor(this.theme.backgroundColor)
}
}
@Component
struct Content {
@Consume('theme') theme: ThemeConfig
build() {
Column() {
Text('内容区域')
.fontColor(this.theme.textColor)
.fontSize(this.theme.fontSize)
}
}
}
@Component
struct Footer {
@Consume('theme') theme: ThemeConfig
build() {
Row() {
Text('底部')
.fontColor(this.theme.primaryColor)
}
}
}场景 2:用户信息全局共享
class UserInfo {
id: number = 0
name: string = ''
isLogin: boolean = false
}
@Entry
@Component
struct App {
@Provide('user') user: UserInfo = { id: 0, name: '', isLogin: false }
build() {
Column() {
UserProfile()
SettingsMenu()
}
}
}
@Component
struct UserProfile {
@Consume('user') user: UserInfo
build() {
Column() {
if (this.user.isLogin) {
Text(`欢迎, ${this.user.name}`)
Button('退出登录')
.onClick(() => {
this.user.isLogin = false
this.user.name = ''
})
} else {
Button('登录')
.onClick(() => {
this.user.isLogin = true
this.user.name = '张三'
})
}
}
}
}
@Component
struct SettingsMenu {
@Consume('user') user: UserInfo
build() {
Column() {
Text(`用户ID: ${this.user.id}`)
Text(`用户名: ${this.user.name}`)
}
}
}场景 3:多层级导航状态
@Entry
@Component
struct NavigationPage {
@Provide('activeTab') activeTab: number = 0
build() {
Column() {
TabContent()
TabBar()
}
}
}
@Component
struct TabContent {
@Consume('activeTab') activeTab: number
build() {
Stack() {
if (this.activeTab === 0) {
HomePage()
} else if (this.activeTab === 1) {
DiscoverPage()
} else {
ProfilePage()
}
}
}
}
@Component
struct TabBar {
@Consume('activeTab') activeTab: number
build() {
Row() {
TabItem({ label: '首页', index: 0 })
TabItem({ label: '发现', index: 1 })
TabItem({ label: '我的', index: 2 })
}
}
}
@Component
struct TabItem {
@Prop label: string
@Prop index: number
@Consume('activeTab') activeTab: number
build() {
Column() {
Text(this.label)
.fontColor(this.activeTab === this.index ? '#007DFF' : '#999')
}
.onClick(() => {
this.activeTab = this.index
})
}
}使用限制
- 别名匹配:
@Provide('alias')和@Consume('alias')必须通过相同的别名匹配 - 双向同步:
@Consume修改数据会同步到@Provide和所有其他@Consume - 作用域限制:
@Consume只能在其组件树的祖先中找到匹配的@Provide - 不支持跨页面:
@Provide/@Consume仅在单个页面内有效,跨页面请使用AppStorage
@ObjectLink / @Observed(嵌套对象响应式)
对于嵌套对象和数组,需要使用 @Observed 和 @ObjectLink 实现深度响应式。@Observed 装饰类,使类的实例具有响应式能力;@ObjectLink 装饰变量,用于在子组件中引用被 @Observed 装饰的类的实例。
基本用法
// 定义数据模型
@Observed
class TodoItem {
id: number
title: string
completed: boolean
constructor(id: number, title: string) {
this.id = id
this.title = title
this.completed = false
}
}
// 子组件显示单个事项
@Component
struct TodoItemView {
@ObjectLink item: TodoItem
build() {
Row() {
Text(this.item.title)
.fontSize(16)
.decoration({
type: this.item.completed ? TextDecorationType.LineThrough : TextDecorationType.None
})
Toggle({ type: ToggleType.Checkbox, isOn: this.item.completed })
.onChange((isOn: boolean) => {
this.item.completed = isOn // ✅ 会触发刷新
})
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
}
}
// 父组件管理列表
@Entry
@Component
struct TodoList {
@State todoList: TodoItem[] = [
new TodoItem(1, '学习 ArkTS'),
new TodoItem(2, '练习 ArkUI'),
new TodoItem(3, '开发应用')
]
build() {
Column() {
List() {
ForEach(this.todoList, (item: TodoItem) => {
ListItem() {
TodoItemView({ item: item })
}
}, (item: TodoItem) => item.id.toString())
}
Button('添加事项')
.onClick(() => {
const newId = this.todoList.length + 1
this.todoList.push(new TodoItem(newId, `新事项 ${newId}`))
})
}
.width('100%')
.height('100%')
}
}所有使用场景
场景 1:嵌套对象响应式
@Observed
class Address {
city: string
street: string
constructor(city: string, street: string) {
this.city = city
this.street = street
}
}
@Observed
class Person {
name: string
age: number
address: Address
constructor(name: string, age: number, city: string, street: string) {
this.name = name
this.age = age
this.address = new Address(city, street)
}
}
@Component
struct AddressView {
@ObjectLink address: Address
build() {
Column() {
Text(`城市: ${this.address.city}`)
Text(`街道: ${this.address.street}`)
Button('修改城市')
.onClick(() => {
this.address.city = '上海'
})
}
}
}
@Component
struct PersonView {
@ObjectLink person: Person
build() {
Column() {
Text(`姓名: ${this.person.name}`)
Text(`年龄: ${this.person.age}`)
AddressView({ address: this.person.address })
}
}
}
@Entry
@Component
struct NestedDemo {
@State person: Person = new Person('张三', 25, '北京', '长安街')
build() {
PersonView({ person: this.person })
}
}场景 2:对象数组深度响应
@Observed
class Product {
id: number
name: string
price: number
quantity: number
constructor(id: number, name: string, price: number) {
this.id = id
this.name = name
this.price = price
this.quantity = 1
}
get total(): number {
return this.price * this.quantity
}
}
@Component
struct ProductItem {
@ObjectLink product: Product
build() {
Row() {
Text(this.product.name)
Text(`¥${this.product.price}`)
Row() {
Button('-')
.onClick(() => {
if (this.product.quantity > 1) {
this.product.quantity--
}
})
Text(`${this.product.quantity}`)
Button('+')
.onClick(() => {
this.product.quantity++
})
}
Text(`小计: ¥${this.product.total}`)
}
}
}
@Entry
@Component
struct ShoppingCart {
@State products: Product[] = [
new Product(1, '手机', 3999),
new Product(2, '耳机', 299),
new Product(3, '充电器', 99)
]
get totalPrice(): number {
return this.products.reduce((sum, p) => sum + p.total, 0)
}
build() {
Column() {
List() {
ForEach(this.products, (product: Product) => {
ListItem() {
ProductItem({ product: product })
}
})
}
Text(`总计: ¥${this.totalPrice}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
}
}
}场景 3:继承类场景
@Observed
class Animal {
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = age
}
}
@Observed
class Dog extends Animal {
breed: string
constructor(name: string, age: number, breed: string) {
super(name, age)
this.breed = breed
}
}
@Component
struct AnimalCard {
@ObjectLink animal: Animal
build() {
Column() {
Text(`名字: ${this.animal.name}`)
Text(`年龄: ${this.animal.age}`)
if (this.animal instanceof Dog) {
Text(`品种: ${(this.animal as Dog).breed}`)
}
}
}
}使用限制
- 必须配合使用:
@ObjectLink必须配合@Observed使用 - 不能独立初始化:
@ObjectLink变量不能设置默认值,必须从父组件传入被@Observed装饰的实例 - 仅用于子组件:
@ObjectLink只能用于自定义组件内部,不能用于@Entry组件 - 不支持简单类型:
@ObjectLink只能装饰@Observed装饰的类的实例 - 数组整体赋值:
@State装饰的数组整体赋值会触发刷新,但数组内对象属性修改需要通过@ObjectLink
@Watch(状态监听)
@Watch 用于监听状态变量的变化,当状态变量改变时触发指定的回调方法。
基本用法
@Component
struct CounterComp {
@State @Watch('onCountChange') count: number = 0
@State message: string = ''
onCountChange() {
console.log('count 变啦!', this.count)
if (this.count >= 10) {
this.message = '已达到最大值!'
} else {
this.message = ''
}
}
build() {
Column() {
Text(`${this.count}`)
.fontSize(48)
Text(this.message)
.fontColor('#FF6B6B')
Button('增加')
.onClick(() => this.count++)
}
}
}所有使用场景
场景 1:表单验证
@Entry
@Component
struct FormValidation {
@State @Watch('validateName') name: string = ''
@State @Watch('validatePhone') phone: string = ''
@State nameError: string = ''
@State phoneError: string = ''
@State isValid: boolean = false
validateName() {
if (this.name.length < 2) {
this.nameError = '姓名至少2个字符'
} else {
this.nameError = ''
}
this.checkFormValid()
}
validatePhone() {
const phoneRegex = /^1[3-9]\d{9}$/
if (!phoneRegex.test(this.phone)) {
this.phoneError = '手机号格式不正确'
} else {
this.phoneError = ''
}
this.checkFormValid()
}
checkFormValid() {
this.isValid = this.name.length >= 2 && /^1[3-9]\d{9}$/.test(this.phone)
}
build() {
Column() {
TextInput({ placeholder: '姓名' })
.onChange((value: string) => this.name = value)
Text(this.nameError)
.fontColor('#FF6B6B')
.fontSize(12)
TextInput({ placeholder: '手机号' })
.onChange((value: string) => this.phone = value)
Text(this.phoneError)
.fontColor('#FF6B6B')
.fontSize(12)
Button('提交')
.enabled(this.isValid)
.onClick(() => {
console.log('提交表单')
})
}
}
}场景 2:联动效果
@Entry
@Component
struct LinkedSlider {
@State @Watch('onRedChange') red: number = 128
@State @Watch('onGreenChange') green: number = 128
@State @Watch('onBlueChange') blue: number = 128
@State colorHex: string = '#808080'
updateColor() {
this.colorHex = `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue.toString(16).padStart(2, '0')}`
}
onRedChange() {
this.updateColor()
}
onGreenChange() {
this.updateColor()
}
onBlueChange() {
this.updateColor()
}
build() {
Column() {
Text('颜色选择器')
Row() {
Text('R')
Slider({ value: this.red, min: 0, max: 255 })
.onChange((value: number) => this.red = value)
Text(`${this.red}`)
}
Row() {
Text('G')
Slider({ value: this.green, min: 0, max: 255 })
.onChange((value: number) => this.green = value)
Text(`${this.green}`)
}
Row() {
Text('B')
Slider({ value: this.blue, min: 0, max: 255 })
.onChange((value: number) => this.blue = value)
Text(`${this.blue}`)
}
Row()
.width(100)
.height(100)
.backgroundColor(this.colorHex)
Text(this.colorHex)
}
}
}场景 3:数据持久化
@Entry
@Component
struct AutoSave {
@State @Watch('autoSave') content: string = ''
@State lastSaveTime: string = ''
autoSave() {
// 模拟自动保存
this.lastSaveTime = new Date().toLocaleTimeString()
console.log('自动保存:', this.content)
// 实际项目中可调用 PersistentStorage 或网络 API
}
build() {
Column() {
TextInput({ placeholder: '输入内容...' })
.onChange((value: string) => this.content = value)
Text(`上次保存: ${this.lastSaveTime}`)
.fontSize(12)
.fontColor('#999')
}
}
}使用限制
- 回调方法名:
@Watch('methodName')中的方法名必须是当前组件中定义的方法 - 不监听初始化:
@Watch不会在变量初始化时触发,只在值变化时触发 - 同步执行:回调方法同步执行,避免在回调中执行耗时操作
- 不能修改自身:在
@Watch回调中修改被监听的变量可能导致无限循环 - 可监听多个变量:一个方法可以被多个
@Watch装饰器引用
$$ 语法(双向绑定语法糖)
$$ 语法是 ArkUI 提供的双向绑定语法糖,用于简化表单组件的双向绑定。它适用于 TextInput、TextArea、Slider、Toggle、Checkbox 等组件。
基本用法
@Entry
@Component
struct DoubleBindingDemo {
@State username: string = ''
@State password: string = ''
@State isAgree: boolean = false
@State sliderValue: number = 50
build() {
Column({ space: 20 }) {
// TextInput 双向绑定
TextInput({ placeholder: '用户名', text: $$this.username })
.onChange((value: string) => {
this.username = value
})
// TextArea 双向绑定
TextArea({ placeholder: '个人简介', text: $$this.bio })
.onChange((value: string) => {
this.bio = value
})
// Slider 双向绑定
Slider({ value: $$this.sliderValue, min: 0, max: 100 })
Text(`当前值: ${this.sliderValue}`)
// Toggle 双向绑定
Toggle({ type: ToggleType.Switch, isOn: $$this.isAgree })
Text(`是否同意: ${this.isAgree ? '是' : '否'}`)
// Checkbox 双向绑定
Checkbox()
.select($$this.isAgree)
}
.padding(20)
}
}支持的组件
| 组件 | 绑定属性 | 说明 |
|---|---|---|
| TextInput | text | 输入框文本 |
| TextArea | text | 多行输入框文本 |
| Slider | value | 滑块值 |
| Toggle | isOn | 开关状态 |
| Checkbox | select | 选中状态 |
| Radio | checked | 选中状态 |
| Rating | rating | 评分值 |
| DatePicker | selected | 选中日期 |
| TimePicker | selected | 选中时间 |
使用限制
- 仅支持
@State和@Link:$$语法只能绑定@State或@Link装饰的变量 - 类型必须匹配:组件属性类型必须与变量类型一致
- 不适用于自定义组件:
$$语法仅适用于系统组件
@StorageLink / @StorageProp(本地存储绑定)
@StorageLink 和 @StorageProp 用于将组件状态与 AppStorage 中的应用级存储进行绑定,实现跨页面状态共享。
@StorageLink(双向绑定)
// 在 Ability 中初始化
import { UIAbility } from '@kit.AbilityKit'
export default class EntryAbility extends UIAbility {
onCreate() {
AppStorage.setOrCreate('token', '')
AppStorage.setOrCreate('userInfo', null)
AppStorage.setOrCreate('isDarkMode', false)
}
}
// 页面中使用
@Entry
@Component
struct ProfilePage {
@StorageLink('token') token: string = ''
@StorageLink('userInfo') userInfo: UserInfo | null = null
build() {
Column() {
if (this.token) {
Text(`欢迎, ${this.userInfo?.name}`)
Button('退出登录')
.onClick(() => {
this.token = ''
this.userInfo = null
})
} else {
Button('登录')
.onClick(() => {
this.token = 'abc123'
this.userInfo = { name: '张三', id: 1 }
})
}
}
}
}
// 另一个页面也能访问
@Entry
@Component
struct SettingsPage {
@StorageLink('token') token: string = ''
build() {
Text(`当前登录状态: ${this.token ? '已登录' : '未登录'}`)
}
}@StorageProp(单向绑定)
@Entry
@Component
struct ReadOnlyPage {
// 单向绑定:只能读取,不能通过此变量修改 AppStorage
@StorageProp('appVersion') version: string = '1.0.0'
build() {
Column() {
Text(`应用版本: ${this.version}`)
// 修改不会同步到 AppStorage
// this.version = '2.0.0' // ❌ 不推荐
}
}
}使用限制
- 必须初始化 AppStorage:使用
@StorageLink/@StorageProp前必须在AppStorage中创建对应的属性 - 默认值仅在未初始化时生效:如果
AppStorage中已有该属性,组件中的默认值会被忽略 - 类型一致性:组件中声明的类型必须与
AppStorage中的类型一致 - 对象类型限制:
@StorageLink绑定对象类型时,对象的属性修改不会触发刷新(需要整体赋值)
@LocalStorageLink / @LocalStorageProp(页面级存储)
@LocalStorageLink 和 @LocalStorageProp 用于页面级的状态存储,作用范围限定在当前页面及其子组件。
基本用法
// 创建 LocalStorage 实例
let storage = new LocalStorage({
'pageTitle': '默认标题',
'fontSize': 16,
'theme': 'light'
})
// 在 @Entry 中注入
@Entry(storage)
@Component
struct LocalStoragePage {
@LocalStorageLink('pageTitle') title: string = '默认标题'
@LocalStorageLink('fontSize') fontSize: number = 16
@LocalStorageLink('theme') theme: string = 'light'
build() {
Column() {
Text(this.title)
.fontSize(this.fontSize)
Button('修改标题')
.onClick(() => {
this.title = '新标题'
})
Button('切换主题')
.onClick(() => {
this.theme = this.theme === 'light' ? 'dark' : 'light'
})
ChildComponent()
}
}
}
@Component
struct ChildComponent {
@LocalStorageLink('pageTitle') title: string = ''
@LocalStorageProp('theme') theme: string = 'light' // 单向绑定
build() {
Column() {
Text(`子组件读取: ${this.title}`)
Text(`当前主题: ${this.theme}`)
}
}
}使用场景
场景 1:页面内多组件共享状态
let pageStorage = new LocalStorage({
'formData': { name: '', phone: '', address: '' }
})
@Entry(pageStorage)
@Component
struct FormPage {
@LocalStorageLink('formData') formData: Record<string, string>
build() {
Column() {
NameInput()
PhoneInput()
AddressInput()
SubmitButton()
}
}
}
@Component
struct NameInput {
@LocalStorageLink('formData') formData: Record<string, string>
build() {
TextInput({ placeholder: '姓名' })
.onChange((value: string) => {
this.formData.name = value
})
}
}
@Component
struct SubmitButton {
@LocalStorageLink('formData') formData: Record<string, string>
build() {
Button('提交')
.enabled(this.formData.name && this.formData.phone)
.onClick(() => {
console.log('提交:', JSON.stringify(this.formData))
})
}
}使用限制
- 页面级作用域:
LocalStorage仅在当前页面有效,页面销毁后数据丢失 - 需要注入:必须在
@Entry装饰器中注入LocalStorage实例 - 子组件自动继承:子组件无需额外注入,可直接使用
@LocalStorageLink/@LocalStorageProp
V2 状态管理(API 12+)
V2 状态管理是 ArkUI 在 API 12 推出的新一代状态管理体系,提供了更统一、更简洁、更高效的开发体验。新项目强烈推荐使用 V2。
注意
V1 和 V2 装饰器不能混用。使用 @ComponentV2 的组件内部只能使用 V2 装饰器。
@ObservedV2 和 @Trace
@ObservedV2 和 @Trace 是 V2 中实现对象深度观测的核心装饰器。@ObservedV2 装饰类,使类具有响应式能力;@Trace 装饰类的属性,使属性变化时触发 UI 刷新。
基本用法
@ObservedV2
class UserInfo {
@Trace name: string = ''
@Trace age: number = 0
@Trace avatar: string = ''
}
@Entry
@ComponentV2
struct UserProfile {
user: UserInfo = new UserInfo()
aboutToAppear() {
this.user.name = '张三'
this.user.age = 25
}
build() {
Column() {
Text(`姓名: ${this.user.name}`)
Text(`年龄: ${this.user.age}`)
Button('修改信息')
.onClick(() => {
this.user.name = '李四'
this.user.age++
})
}
}
}所有使用场景
场景 1:嵌套类深度观测
@ObservedV2
class Address {
@Trace city: string = ''
@Trace street: string = ''
}
@ObservedV2
class Person {
@Trace name: string = ''
@Trace age: number = 0
@Trace address: Address = new Address()
}
@Entry
@ComponentV2
struct NestedObserved {
person: Person = new Person()
aboutToAppear() {
this.person.name = '张三'
this.person.address.city = '北京'
this.person.address.street = '长安街'
}
build() {
Column() {
Text(`姓名: ${this.person.name}`)
Text(`城市: ${this.person.address.city}`)
Text(`街道: ${this.person.address.street}`)
Button('修改城市')
.onClick(() => {
this.person.address.city = '上海' // 深度观测,会触发刷新
})
}
}
}场景 2:数组元素观测
@ObservedV2
class TodoItem {
@Trace id: number = 0
@Trace title: string = ''
@Trace completed: boolean = false
}
@Entry
@ComponentV2
struct TodoListV2 {
@Local todoList: TodoItem[] = [
new TodoItem().apply(item => { item.id = 1; item.title = '学习 ArkTS' }),
new TodoItem().apply(item => { item.id = 2; item.title = '练习 ArkUI' })
]
build() {
Column() {
List() {
ForEach(this.todoList, (item: TodoItem) => {
ListItem() {
Row() {
Text(item.title)
.decoration({
type: item.completed ? TextDecorationType.LineThrough : TextDecorationType.None
})
Toggle({ type: ToggleType.Checkbox, isOn: item.completed })
.onChange((isOn: boolean) => {
item.completed = isOn
})
}
}
})
}
Button('添加事项')
.onClick(() => {
const newItem = new TodoItem()
newItem.id = this.todoList.length + 1
newItem.title = `新事项 ${newItem.id}`
this.todoList.push(newItem)
})
}
}
}场景 3:继承类观测
@ObservedV2
class BaseModel {
@Trace id: number = 0
@Trace createdAt: Date = new Date()
}
@ObservedV2
class UserModel extends BaseModel {
@Trace name: string = ''
@Trace email: string = ''
}
@Entry
@ComponentV2
struct InheritanceDemo {
user: UserModel = new UserModel()
build() {
Column() {
Text(`ID: ${this.user.id}`)
Text(`姓名: ${this.user.name}`)
Button('修改')
.onClick(() => {
this.user.id = 1
this.user.name = '张三'
})
}
}
}@Trace 可观测的 API
| 类型 | 可观测操作 |
|---|---|
number、string、boolean | 赋值操作 |
Array | push、pop、shift、unshift、splice、sort、reverse、索引赋值 |
Map | set、delete、clear |
Set | add、delete、clear |
Date | setXxx 系列方法 |
嵌套 @ObservedV2 类 | 属性赋值 |
使用限制
- 必须配合使用:
@Trace只能在@ObservedV2装饰的类中使用 - 不支持 JSON 序列化:
@ObservedV2的类实例目前不支持JSON.stringify - 类型限制:
@Trace不支持undefined、null和联合类型 - 方法装饰限制:
@Trace只能装饰属性,不能装饰方法
@ComponentV2
@ComponentV2 是 V2 中定义自定义组件的装饰器,替代 V1 中的 @Component。使用 @ComponentV2 的组件内部只能使用 V2 状态管理装饰器。
基本用法
@ComponentV2
struct MyComponent {
@Local count: number = 0
build() {
Column() {
Text(`${this.count}`)
Button('增加')
.onClick(() => this.count++)
}
}
}
@Entry
@ComponentV2
struct Page {
build() {
MyComponent()
}
}特性说明
- 冻结功能:
@ComponentV2支持freezeWhenInactive属性,组件不可见时自动冻结,优化性能 - 类型安全:V2 装饰器必须显式声明类型
- 不支持组件复用:
@ComponentV2暂不支持@Reusable组件复用
@ComponentV2({ freezeWhenInactive: true })
struct OptimizedComponent {
@Local data: string = ''
build() {
Text(this.data)
}
}@Local(替代 @State)
@Local 是 V2 中用于声明组件内部状态的装饰器,功能类似于 V1 的 @State。
基本用法
@Entry
@ComponentV2
struct CounterPage {
@Local count: number = 0
@Local message: string = '点击按钮'
build() {
Column() {
Text(this.message)
.fontSize(18)
Text(`${this.count}`)
.fontSize(48)
.fontColor('#007DFF')
Button('+')
.onClick(() => {
this.count++
if (this.count >= 10) {
this.message = '已达到最大值!'
}
})
}
}
}与 @State 的区别
| 特性 | @State (V1) | @Local (V2) |
|---|---|---|
| 初始化 | 必须初始化 | 必须初始化 |
| 外部传入 | 不支持 | 不支持 |
| 观测能力 | 简单类型、数组整体 | 简单类型、数组、Set、Map、Date |
| 对象类型 | 需配合 @Observed | 需配合 @ObservedV2 |
| 类型声明 | 推荐显式声明 | 必须显式声明 |
使用限制
- 必须初始化:
@Local变量必须在声明时初始化 - 无法从外部传入:
@Local变量不能通过组件参数传入 - 必须声明类型:API 12+ 要求 V2 装饰器必须显式声明类型
@Param(替代 @Prop)
@Param 是 V2 中用于父组件向子组件传递参数的装饰器,功能类似于 V1 的 @Prop。
基本用法
@ComponentV2
struct ChildComp {
@Param title: string = ''
@Param count: number = 0
build() {
Column() {
Text(`${this.title}: ${this.count}`)
.fontSize(16)
}
}
}
@Entry
@ComponentV2
struct ParentPage {
@Local parentCount: number = 0
build() {
Column() {
ChildComp({ title: '计数器', count: this.parentCount })
Button('父组件+1')
.onClick(() => this.parentCount++)
}
}
}所有使用场景
场景 1:基础参数传递
@ComponentV2
struct UserCard {
@Param name: string = ''
@Param age: number = 0
@Param avatar: string = ''
build() {
Row() {
Image(this.avatar)
Column() {
Text(this.name)
Text(`${this.age}岁`)
}
}
}
}场景 2:对象参数传递
@ObservedV2
class Product {
@Trace name: string = ''
@Trace price: number = 0
}
@ComponentV2
struct ProductCard {
@Param product: Product = new Product()
build() {
Column() {
Text(this.product.name)
Text(`¥${this.product.price}`)
}
}
}
@Entry
@ComponentV2
struct ShopPage {
@Local product: Product = new Product()
aboutToAppear() {
this.product.name = '手机'
this.product.price = 3999
}
build() {
ProductCard({ product: this.product })
}
}场景 3:数组参数传递
@ComponentV2
struct TagList {
@Param tags: string[] = []
build() {
Row() {
ForEach(this.tags, (tag: string) => {
Text(tag)
.padding(8)
.backgroundColor('#F0F0F0')
.borderRadius(4)
})
}
}
}使用限制
- 单向同步:
@Param只能从父到子单向同步,子组件修改不会影响父组件 - 本地修改限制:
@Param变量在子组件中不能直接修改(需配合@Event) - 必须声明类型:必须显式声明类型
- 支持本地初始化:可以设置默认值,但外部传入时会覆盖
@Event(事件回调)
@Event 是 V2 中用于子组件向父组件传递事件的装饰器,替代 V1 中通过回调函数传递的方式。
基本用法
@ComponentV2
struct ChildComp {
@Param title: string = ''
@Event onClick: () => void = () => {}
build() {
Button(this.title)
.onClick(() => {
this.onClick()
})
}
}
@Entry
@ComponentV2
struct ParentPage {
@Local count: number = 0
build() {
Column() {
Text(`${this.count}`)
ChildComp({
title: '点击增加',
onClick: () => {
this.count++
}
})
}
}
}所有使用场景
场景 1:基础事件传递
@ComponentV2
struct CounterButton {
@Param label: string = ''
@Event onAdd: () => void = () => {}
build() {
Button(this.label)
.onClick(() => this.onAdd())
}
}
@Entry
@ComponentV2
struct CounterPage {
@Local count: number = 0
build() {
Column() {
Text(`${this.count}`)
CounterButton({
label: '增加',
onAdd: () => this.count++
})
}
}
}场景 2:带参数的事件
@ComponentV2
struct RatingStars {
@Param rating: number = 0
@Event onRate: (rating: number) => void = () => {}
build() {
Row() {
ForEach([1, 2, 3, 4, 5], (star: number) => {
Image(star <= this.rating ? '/star_filled.png' : '/star_empty.png')
.onClick(() => {
this.onRate(star)
})
})
}
}
}
@Entry
@ComponentV2
struct ProductPage {
@Local rating: number = 0
build() {
Column() {
Text(`当前评分: ${this.rating}`)
RatingStars({
rating: this.rating,
onRate: (value: number) => {
this.rating = value
}
})
}
}
}场景 3:多事件传递
@ComponentV2
struct FormField {
@Param value: string = ''
@Param placeholder: string = ''
@Event onChange: (value: string) => void = () => {}
@Event onBlur: () => void = () => {}
@Event onFocus: () => void = () => {}
build() {
TextInput({ placeholder: this.placeholder, text: this.value })
.onChange((value: string) => this.onChange(value))
.onBlur(() => this.onBlur())
.onFocus(() => this.onFocus())
}
}
@Entry
@ComponentV2
struct FormPage {
@Local username: string = ''
@Local usernameError: string = ''
build() {
Column() {
FormField({
value: this.username,
placeholder: '请输入用户名',
onChange: (value: string) => {
this.username = value
},
onBlur: () => {
if (this.username.length < 2) {
this.usernameError = '用户名太短'
}
}
})
Text(this.usernameError)
.fontColor('#FF6B6B')
}
}
}使用限制
- 只能装饰函数类型:
@Event只能装饰函数类型的变量 - 必须初始化:需要设置默认空函数
- 箭头函数保持 this:使用箭头函数确保
this指向正确 - 不能用于返回值:
@Event不适用于需要返回值的场景
@Once(单次同步)
@Once 用于装饰仅在初始化时同步一次的参数。与 @Param 配合使用,父组件后续修改不会同步到子组件。
基本用法
@ComponentV2
struct UserCard {
@Once @Param userId: string = ''
@Param userName: string = ''
build() {
Column() {
Text(`ID: ${this.userId}`) // 不会随父组件更新
Text(`姓名: ${this.userName}`) // 会随父组件更新
}
}
}
@Entry
@ComponentV2
struct ParentPage {
@Local userId: string = '001'
@Local userName: string = '张三'
build() {
Column() {
UserCard({ userId: this.userId, userName: this.userName })
Button('修改信息')
.onClick(() => {
this.userId = '002'
this.userName = '李四'
})
}
}
}使用限制
- 必须配合 @Param:
@Once只能与@Param一起使用 - 初始化后不同步:父组件后续修改不会同步到子组件
- 子组件可本地修改:
@Once @Param变量可以在子组件中本地修改
@Provider / @Consumer(替代 @Provide/@Consume)
@Provider 和 @Consumer 是 V2 中用于跨组件层级共享状态的装饰器,替代 V1 的 @Provide/@Consume。
基本用法
@Entry
@ComponentV2
struct RootPage {
@Provider() theme: string = 'light'
@Provider() fontSize: number = 16
build() {
Column() {
Text('根组件')
ParentComp()
}
}
}
@ComponentV2
struct ParentComp {
build() {
Column() {
Text('父组件')
ChildComp()
}
}
}
@ComponentV2
struct ChildComp {
@Consumer() theme: string = 'dark' // 默认值为 dark,但实际会获取 Provider 的值
@Consumer() fontSize: number = 14
build() {
Column() {
Text('子组件')
.fontColor(this.theme === 'dark' ? '#FFFFFF' : '#333333')
.fontSize(this.fontSize)
}
}
}所有使用场景
场景 1:主题系统
@ObservedV2
class Theme {
@Trace primaryColor: string = '#007DFF'
@Trace backgroundColor: string = '#FFFFFF'
@Trace textColor: string = '#333333'
}
@Entry
@ComponentV2
struct ThemeApp {
@Provider() theme: Theme = new Theme()
build() {
Column() {
Header()
Content()
ThemeSwitcher()
}
}
}
@ComponentV2
struct Header {
@Consumer() theme: Theme = new Theme()
build() {
Row() {
Text('标题')
.fontColor(this.theme.primaryColor)
}
.backgroundColor(this.theme.backgroundColor)
}
}
@ComponentV2
struct ThemeSwitcher {
@Consumer() theme: Theme = new Theme()
build() {
Button('切换主题')
.onClick(() => {
if (this.theme.primaryColor === '#007DFF') {
this.theme.primaryColor = '#FF6B6B'
this.theme.backgroundColor = '#1A1A1A'
this.theme.textColor = '#FFFFFF'
} else {
this.theme.primaryColor = '#007DFF'
this.theme.backgroundColor = '#FFFFFF'
this.theme.textColor = '#333333'
}
})
}
}场景 2:用户状态共享
@ObservedV2
class UserState {
@Trace isLogin: boolean = false
@Trace userName: string = ''
@Trace userId: number = 0
}
@Entry
@ComponentV2
struct App {
@Provider() user: UserState = new UserState()
build() {
Column() {
UserInfo()
LoginButton()
}
}
}
@ComponentV2
struct UserInfo {
@Consumer() user: UserState = new UserState()
build() {
Column() {
if (this.user.isLogin) {
Text(`欢迎, ${this.user.userName}`)
} else {
Text('未登录')
}
}
}
}
@ComponentV2
struct LoginButton {
@Consumer() user: UserState = new UserState()
build() {
Button(this.user.isLogin ? '退出登录' : '登录')
.onClick(() => {
if (this.user.isLogin) {
this.user.isLogin = false
this.user.userName = ''
} else {
this.user.isLogin = true
this.user.userName = '张三'
}
})
}
}使用限制
- 别名匹配:
@Provider('alias')和@Consumer('alias')通过别名匹配 - 默认值:
@Consumer可以设置默认值,当找不到对应的@Provider时使用默认值 - 双向同步:
@Consumer修改数据会同步到@Provider和所有其他@Consumer - 不能修饰 class 属性:
@Provider/@Consumer只能修饰组件内的属性
@Monitor(状态监听替代 @Watch)
@Monitor 是 V2 中用于监听状态变量变化的装饰器,替代 V1 的 @Watch。支持深度监听和监听多个属性。
基本用法
@Entry
@ComponentV2
struct MonitorDemo {
@Local count: number = 0
@Local name: string = ''
@Monitor('count')
onCountChange(monitor: IMonitor) {
console.log('count changed:', this.count)
const value = monitor.value('count')
console.log('before:', value?.before, 'now:', value?.now)
}
build() {
Column() {
Text(`${this.count}`)
Button('增加')
.onClick(() => this.count++)
}
}
}所有使用场景
场景 1:监听多个属性
@Entry
@ComponentV2
struct MultiMonitor {
@Local firstName: string = ''
@Local lastName: string = ''
@Local fullName: string = ''
@Monitor('firstName', 'lastName')
onNameChange(monitor: IMonitor) {
this.fullName = `${this.firstName} ${this.lastName}`
console.log('变化的属性:', monitor.dirty)
}
build() {
Column() {
TextInput({ placeholder: '姓' })
.onChange((value: string) => this.firstName = value)
TextInput({ placeholder: '名' })
.onChange((value: string) => this.lastName = value)
Text(`全名: ${this.fullName}`)
}
}
}场景 2:深度监听嵌套对象
@ObservedV2
class Address {
@Trace city: string = ''
@Trace street: string = ''
}
@ObservedV2
class Person {
@Trace name: string = ''
@Trace address: Address = new Address()
}
@Entry
@ComponentV2
struct DeepMonitor {
@Local person: Person = new Person()
@Monitor('person.address.city')
onCityChange(monitor: IMonitor) {
console.log('城市变化了')
const value = monitor.value('person.address.city')
console.log('从', value?.before, '变为', value?.now)
}
build() {
Column() {
Text(`城市: ${this.person.address.city}`)
Button('修改城市')
.onClick(() => {
this.person.address.city = '上海'
})
}
}
}场景 3:在 @ObservedV2 类中使用
@ObservedV2
class CounterModel {
@Trace count: number = 0
@Monitor('count')
onCountChange(monitor: IMonitor) {
console.log('计数变化:', this.count)
}
}
@Entry
@ComponentV2
struct ClassMonitor {
model: CounterModel = new CounterModel()
build() {
Column() {
Text(`${this.model.count}`)
Button('增加')
.onClick(() => this.model.count++)
}
}
}IMonitor 接口说明
interface IMonitor {
dirty: string[] // 发生变化的属性名数组
value(path?: string): IMonitorValue | undefined // 获取指定路径的变化值
}
interface IMonitorValue<T = Object> {
before: T // 变化前的值
now: T // 变化后的值
path: string // 监听的属性路径
}使用限制
- 路径格式:监听嵌套属性时使用点号路径,如
'person.address.city' - 单次事件合并:多个属性在一次事件中同时变化时,只会触发一次回调
- 继承场景:父子组件可以对同一属性分别定义
@Monitor,都会触发 - 未装饰属性无法监听:未使用
@ObservedV2和@Trace的属性变化无法被监听
@Computed(计算属性)
@Computed 是 V2 中用于声明计算属性的装饰器。计算属性会根据依赖的状态自动重新计算,并缓存结果,避免重复计算。
基本用法
@Entry
@ComponentV2
struct ComputedDemo {
@Local num: number = 10
@Computed
get double(): number {
console.log('计算 double')
return this.num * 2
}
@Computed
get square(): number {
console.log('计算 square')
return this.num * this.num
}
build() {
Column() {
Text(`数字: ${this.num}`)
Text(`双倍: ${this.double}`)
Text(`平方: ${this.square}`)
Button('增加')
.onClick(() => this.num++)
}
}
}所有使用场景
场景 1:派生数据计算
@ObservedV2
class CartItem {
@Trace name: string = ''
@Trace price: number = 0
@Trace quantity: number = 0
}
@Entry
@ComponentV2
struct ShoppingCart {
@Local items: CartItem[] = []
@Computed
get totalCount(): number {
return this.items.reduce((sum, item) => sum + item.quantity, 0)
}
@Computed
get totalPrice(): number {
return this.items.reduce((sum, item) => sum + item.price * item.quantity, 0)
}
@Computed
get isEmpty(): boolean {
return this.items.length === 0
}
build() {
Column() {
Text(`商品数量: ${this.totalCount}`)
Text(`总计: ¥${this.totalPrice}`)
if (this.isEmpty) {
Text('购物车是空的')
} else {
List() {
ForEach(this.items, (item: CartItem) => {
ListItem() {
Text(`${item.name} x${item.quantity}`)
}
})
}
}
}
}
}场景 2:过滤和排序
@Entry
@ComponentV2
struct FilterList {
@Local searchText: string = ''
@Local items: string[] = ['苹果', '香蕉', '橙子', '葡萄', '西瓜']
@Computed
get filteredItems(): string[] {
if (!this.searchText) {
return this.items
}
return this.items.filter(item => item.includes(this.searchText))
}
build() {
Column() {
TextInput({ placeholder: '搜索...' })
.onChange((value: string) => this.searchText = value)
List() {
ForEach(this.filteredItems, (item: string) => {
ListItem() {
Text(item)
}
})
}
}
}
}场景 3:格式化显示
@Entry
@ComponentV2
struct FormatDemo {
@Local timestamp: number = Date.now()
@Computed
get formattedDate(): string {
const date = new Date(this.timestamp)
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
}
@Computed
get formattedTime(): string {
const date = new Date(this.timestamp)
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`
}
build() {
Column() {
Text(`日期: ${this.formattedDate}`)
Text(`时间: ${this.formattedTime}`)
Button('更新')
.onClick(() => this.timestamp = Date.now())
}
}
}使用限制
- 只能用于 getter:
@Computed只能装饰 getter 方法 - 依赖追踪:自动追踪 getter 中访问的状态变量,依赖变化时自动重新计算
- 缓存机制:依赖未变化时直接返回缓存结果,避免重复计算
- 不能有副作用:
@Computedgetter 中不应有副作用(如修改其他状态)
全局状态管理
全局状态管理用于在应用级别或页面级别共享状态,实现跨组件、跨页面的数据同步。
AppStorage(应用级存储)
AppStorage 是应用级别的全局状态存储,所有页面共享同一个 AppStorage 实例。应用启动时创建,应用销毁时释放。
基本用法
// 在 EntryAbility 中初始化
import { UIAbility } from '@kit.AbilityKit'
export default class EntryAbility extends UIAbility {
onCreate() {
// 初始化全局状态
AppStorage.setOrCreate('token', '')
AppStorage.setOrCreate('userInfo', null)
AppStorage.setOrCreate('isDarkMode', false)
AppStorage.setOrCreate('language', 'zh-CN')
}
}在组件中使用
@Entry
@Component
struct ProfilePage {
@StorageLink('token') token: string = ''
@StorageLink('userInfo') userInfo: UserInfo | null = null
build() {
Column() {
if (this.token) {
Text(`欢迎, ${this.userInfo?.name}`)
Button('退出登录')
.onClick(() => {
this.token = ''
this.userInfo = null
})
} else {
Button('登录')
.onClick(() => {
this.token = 'abc123'
this.userInfo = { name: '张三', id: 1 }
})
}
}
}
}API 方法
// 设置或创建属性
AppStorage.setOrCreate('key', value)
// 获取属性值
const value = AppStorage.get('key')
// 删除属性
AppStorage.delete('key')
// 属性是否存在
const exists = AppStorage.has('key')
// 监听属性变化
AppStorage.link('key').subscribe((value) => {
console.log('属性变化:', value)
})使用限制
- 进程级共享:
AppStorage仅在当前应用进程内共享 - 不持久化:应用退出后数据丢失,需要配合
PersistentStorage实现持久化 - 类型安全:建议统一封装
AppStorage的读写操作,避免类型错误
LocalStorage(页面级存储)
LocalStorage 是页面级的状态存储,用于在页面内部及其组件之间共享状态。可以在 UIAbility 内页面间共享状态。
基本用法
// 创建 LocalStorage 实例
let storage = new LocalStorage({
'pageTitle': '默认标题',
'fontSize': 16
})
// 在 @Entry 中注入
@Entry(storage)
@Component
struct LocalStoragePage {
@LocalStorageLink('pageTitle') title: string = '默认标题'
@LocalStorageLink('fontSize') fontSize: number = 16
build() {
Column() {
Text(this.title)
.fontSize(this.fontSize)
Button('修改标题')
.onClick(() => {
this.title = '新标题'
})
ChildComponent()
}
}
}
@Component
struct ChildComponent {
@LocalStorageLink('pageTitle') title: string = ''
build() {
Text(`子组件: ${this.title}`)
}
}在 UIAbility 中共享
import { UIAbility } from '@kit.AbilityKit'
import { window } from '@kit.ArkUI'
export default class EntryAbility extends UIAbility {
localStorage: LocalStorage = new LocalStorage()
onCreate() {
this.localStorage.setOrCreate('sharedData', 'Hello World')
}
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.localStorage)
}
}使用限制
- 页面级作用域:
LocalStorage仅在当前页面及其子组件中有效 - 需要注入:必须在
@Entry或windowStage.loadContent中注入 - 生命周期:页面销毁时
LocalStorage数据丢失
PersistentStorage(持久化存储)
PersistentStorage 用于将选定的 AppStorage 属性持久化到设备磁盘上,应用关闭后仍然保持。
基本用法
// 在 EntryAbility 中初始化
import { UIAbility } from '@kit.AbilityKit'
export default class EntryAbility extends UIAbility {
onCreate() {
// 将 AppStorage 的属性持久化
PersistentStorage.persistProp('token', '')
PersistentStorage.persistProp('userInfo', null)
PersistentStorage.persistProp('isDarkMode', false)
PersistentStorage.persistProp('language', 'zh-CN')
}
}在组件中使用
@Entry
@Component
struct SettingsPage {
@StorageLink('isDarkMode') isDarkMode: boolean = false
@StorageLink('language') language: string = 'zh-CN'
build() {
Column() {
Toggle({ type: ToggleType.Switch, isOn: this.isDarkMode })
.onChange((isOn: boolean) => {
this.isDarkMode = isOn // 自动持久化
})
Text(`当前语言: ${this.language}`)
Button('切换语言')
.onClick(() => {
this.language = this.language === 'zh-CN' ? 'en-US' : 'zh-CN'
})
}
}
}使用限制
- 仅支持简单类型:
PersistentStorage仅支持number、string、boolean等简单类型 - 对象类型限制:对象类型需要先序列化为字符串再存储
- 异步写入:数据写入磁盘是异步的,极端情况下可能丢失最后一次修改
- 容量限制:持久化数据有容量限制,不宜存储大量数据
// 对象类型的持久化处理
class UserInfo {
name: string = ''
id: number = 0
}
// 保存时序列化
function saveUserInfo(user: UserInfo) {
AppStorage.setOrCreate('userInfo', JSON.stringify(user))
}
// 读取时反序列化
function getUserInfo(): UserInfo {
const json = AppStorage.get('userInfo') as string
return json ? JSON.parse(json) : new UserInfo()
}Environment(环境变量)
Environment 用于获取设备环境信息,如系统语言、颜色模式、时区等。环境变量变化时会自动通知 UI 刷新。
基本用法
@Entry
@Component
struct EnvironmentDemo {
@StorageProp('languageCode') languageCode: string = 'zh'
@StorageProp('colorMode') colorMode: ColorMode = ColorMode.LIGHT
@StorageProp('fontScale') fontScale: number = 1.0
@StorageProp('layoutDirection') layoutDirection: LayoutDirection = LayoutDirection.LTR
build() {
Column() {
Text(`语言: ${this.languageCode}`)
Text(`颜色模式: ${this.colorMode === ColorMode.DARK ? '深色' : '浅色'}`)
Text(`字体缩放: ${this.fontScale}`)
Text(`布局方向: ${this.layoutDirection === LayoutDirection.RTL ? 'RTL' : 'LTR'}`)
}
}
}支持的环境变量
| 属性名 | 类型 | 说明 |
|---|---|---|
languageCode | string | 系统语言代码,如 'zh'、'en' |
colorMode | ColorMode | 颜色模式,ColorMode.LIGHT 或 ColorMode.DARK |
fontScale | number | 字体缩放比例 |
layoutDirection | LayoutDirection | 布局方向,LayoutDirection.LTR 或 LayoutDirection.RTL |
screenDensity | number | 屏幕密度 |
fontWeightScale | number | 字重缩放比例 |
使用限制
- 只读属性:
Environment变量是只读的,不能通过组件修改 - 系统触发更新:环境变量变化时由系统自动触发 UI 刷新
- 使用
@StorageProp:环境变量通过@StorageProp绑定(单向)
状态管理最佳实践
状态提升模式
当多个兄弟组件需要共享状态时,将状态提升到它们的共同父组件中管理。
// 反例:状态分散在子组件中,无法同步
@Component
struct CounterA {
@State count: number = 0 // ❌ 状态分散
build() {
Button(`A: ${this.count}`)
.onClick(() => this.count++)
}
}
@Component
struct CounterB {
@State count: number = 0 // ❌ 状态分散
build() {
Button(`B: ${this.count}`)
.onClick(() => this.count++)
}
}
// 正例:状态提升到父组件
@Entry
@Component
struct CounterPage {
@State count: number = 0 // ✅ 状态提升
build() {
Row({ space: 20 }) {
CounterA({ count: $count })
CounterB({ count: $count })
Text(`总计: ${this.count}`)
}
}
}
@Component
struct CounterA {
@Link count: number
build() {
Button(`A: ${this.count}`)
.onClick(() => this.count++)
}
}
@Component
struct CounterB {
@Link count: number
build() {
Button(`B: ${this.count}`)
.onClick(() => this.count++)
}
}状态下沉模式
将状态尽量靠近使用它的组件,避免不必要的组件刷新。
// 反例:所有状态都在父组件,导致整个页面刷新
@Entry
@Component
struct BadExample {
@State headerTitle: string = ''
@State contentText: string = ''
@State footerText: string = ''
build() {
Column() {
Header({ title: this.headerTitle })
Content({ text: this.contentText }) // contentText 变化时 Header 也会刷新
Footer({ text: this.footerText }) // contentText 变化时 Footer 也会刷新
}
}
}
// 正例:状态下沉到各自组件
@Entry
@Component
struct GoodExample {
build() {
Column() {
Header() // ✅ 状态在 Header 内部管理
Content() // ✅ 状态在 Content 内部管理
Footer() // ✅ 状态在 Footer 内部管理
}
}
}
@Component
struct Header {
@State title: string = '标题' // ✅ 状态下沉
build() {
Text(this.title)
}
}
@Component
struct Content {
@State text: string = '内容' // ✅ 状态下沉
build() {
Text(this.text)
}
}单一数据源原则
每个状态应该有且只有一个数据源,避免多个状态表示相同的数据。
// 反例:多个状态表示相同数据
@Entry
@Component
struct BadExample {
@State firstName: string = '张'
@State lastName: string = '三'
@State fullName: string = '张三' // ❌ 冗余状态
aboutToAppear() {
this.fullName = `${this.firstName}${this.lastName}`
}
build() {
Column() {
Text(this.fullName)
}
}
}
// 正例:使用计算属性(V2)
@Entry
@ComponentV2
struct GoodExample {
@Local firstName: string = '张'
@Local lastName: string = '三'
@Computed
get fullName(): string { // ✅ 计算属性
return `${this.firstName}${this.lastName}`
}
build() {
Column() {
Text(this.fullName)
}
}
}
// 正例:使用 getter 方法(V1)
@Entry
@Component
struct GoodExampleV1 {
@State firstName: string = '张'
@State lastName: string = '三'
get fullName(): string { // ✅ getter 方法
return `${this.firstName}${this.lastName}`
}
build() {
Column() {
Text(this.fullName)
}
}
}性能优化(避免不必要的刷新)
1. 避免在循环中访问状态变量
// 反例:循环中频繁读取状态变量
@Entry
@Component
struct BadLoop {
@State items: number[] = [1, 2, 3, 4, 5]
build() {
Column() {
ForEach(this.items, (item: number) => {
Text(`${item * this.items.length}`) // ❌ 每次循环都读取 this.items.length
})
}
}
}
// 正例:在循环外读取状态变量
@Entry
@Component
struct GoodLoop {
@State items: number[] = [1, 2, 3, 4, 5]
build() {
Column() {
// ✅ 循环外读取
this.buildList(this.items.length)
}
}
@Builder
buildList(length: number) {
ForEach(this.items, (item: number) => {
Text(`${item * length}`)
})
}
}2. 使用 @ObjectLink 避免大对象拷贝
// 反例:使用 @Prop 传递大对象(深拷贝)
@Component
struct BadChild {
@Prop user: UserInfo // ❌ 深拷贝,性能差
build() {
Text(this.user.name)
}
}
// 正例:使用 @ObjectLink 传递对象引用
@Component
struct GoodChild {
@ObjectLink user: UserInfo // ✅ 引用传递,性能好
build() {
Text(this.user.name)
}
}3. 合理使用 @Provide/@Consume
// 反例:频繁变化的属性使用 @Provide/@Consume
@Entry
@Component
struct BadProvide {
@Provide('count') count: number = 0 // ❌ 高频变化属性
build() {
Column() {
DeepChild()
Button('增加')
.onClick(() => this.count++)
}
}
}
// 正例:高频变化属性使用参数传递,@Provide 用于低频共享
@Entry
@Component
struct GoodProvide {
@State count: number = 0
@Provide('theme') theme: string = 'light' // ✅ 低频变化属性
build() {
Column() {
CounterDisplay({ count: this.count })
ThemedChild()
Button('增加')
.onClick(() => this.count++)
}
}
}4. 避免不必要的 @Watch
// 反例:不必要的 @Watch
@Entry
@Component
struct BadWatch {
@State @Watch('onCountChange') count: number = 0
@State doubleCount: number = 0
onCountChange() {
this.doubleCount = this.count * 2 // ❌ 可以用计算替代
}
build() {
Column() {
Text(`${this.count}`)
Text(`${this.doubleCount}`)
}
}
}
// 正例:使用 getter 或 @Computed
@Entry
@ComponentV2
struct GoodComputed {
@Local count: number = 0
@Computed
get doubleCount(): number { // ✅ 自动计算,无需监听
return this.count * 2
}
build() {
Column() {
Text(`${this.count}`)
Text(`${this.doubleCount}`)
}
}
}V1 和 V2 的选择指南
| 场景 | 推荐方案 | 说明 |
|---|---|---|
| 新项目(API 12+) | V2 装饰器 | @Local / @Param / @ComponentV2 |
| 老项目维护 | V1 装饰器 | 保持原有代码风格 |
| 组件内部状态 | @State (V1) / @Local (V2) | 仅当前组件使用 |
| 父传子(只读) | @Prop (V1) / @Param (V2) | 子组件不修改 |
| 父子双向同步 | @Link (V1) | V2 推荐使用 @Event |
| 跨层级共享 | @Provide/@Consume (V1) / @Provider/@Consumer (V2) | 无需逐层传递 |
| 应用全局状态 | AppStorage | 所有页面共享 |
| 复杂对象数组 | @Observed/@ObjectLink (V1) / @ObservedV2/@Trace (V2) | 深度响应式 |
| 状态监听 | @Watch (V1) / @Monitor (V2) | 监听变量变化 |
| 计算属性 | getter (V1) / @Computed (V2) | 派生数据 |
注意
V1 和 V2 装饰器不能混用。使用 @ComponentV2 的组件内部只能使用 V2 装饰器。
常见错误和解决方案
错误 1:@State 变量未初始化
// ❌ 错误
@Entry
@Component
struct BadExample {
@State count: number // 未初始化
}
// ✅ 正确
@Entry
@Component
struct GoodExample {
@State count: number = 0 // 必须初始化
}错误 2:@Link 未使用 $ 传递
// ❌ 错误
@Entry
@Component
struct BadExample {
@State count: number = 0
build() {
Child({ count: this.count }) // 缺少 $
}
}
// ✅ 正确
@Entry
@Component
struct GoodExample {
@State count: number = 0
build() {
Child({ count: $count }) // 使用 $ 传递
}
}错误 3:修改 @Prop 期望同步到父组件
// ❌ 错误理解
@Component
struct Child {
@Prop count: number
build() {
Button('增加')
.onClick(() => {
this.count++ // 只在子组件生效,不会同步到父组件
})
}
}
// ✅ 正确做法:使用 @Link 或 @Event
@Component
struct GoodChild {
@Link count: number // 或 @Event onAdd: () => void
build() {
Button('增加')
.onClick(() => {
this.count++ // 双向同步
})
}
}错误 4:@ObjectLink 用于 @Entry 组件
// ❌ 错误
@Entry
@Component
struct BadExample {
@ObjectLink item: TodoItem // @ObjectLink 不能用于 @Entry
}
// ✅ 正确
@Entry
@Component
struct GoodExample {
@State item: TodoItem = new TodoItem()
build() {
Child({ item: this.item }) // 传递给子组件
}
}
@Component
struct Child {
@ObjectLink item: TodoItem // ✅ 子组件中使用
}错误 5:V1 和 V2 混用
// ❌ 错误
@ComponentV2
struct BadExample {
@State count: number = 0 // V1 装饰器不能在 @ComponentV2 中使用
}
// ✅ 正确
@ComponentV2
struct GoodExample {
@Local count: number = 0 // 使用 V2 装饰器
}错误 6:@ObservedV2 类未使用 @Trace
// ❌ 错误
@ObservedV2
class BadModel {
name: string = '' // 未使用 @Trace,属性变化不会触发刷新
}
// ✅ 正确
@ObservedV2
class GoodModel {
@Trace name: string = '' // 使用 @Trace,属性变化触发刷新
}错误 7:PersistentStorage 存储对象类型
// ❌ 错误
PersistentStorage.persistProp('user', { name: '张三' }) // 对象类型不支持
// ✅ 正确
PersistentStorage.persistProp('user', JSON.stringify({ name: '张三' }))
// 读取时
const userJson = AppStorage.get('user') as string
const user = JSON.parse(userJson)错误 8:在 @Watch 中修改被监听的变量
// ❌ 错误:可能导致无限循环
@Entry
@Component
struct BadWatch {
@State @Watch('onCountChange') count: number = 0
onCountChange() {
if (this.count > 10) {
this.count = 10 // 可能触发无限循环
}
}
}
// ✅ 正确:避免在回调中修改被监听的变量
@Entry
@Component
struct GoodWatch {
@State @Watch('onCountChange') count: number = 0
@State limitedCount: number = 0
onCountChange() {
this.limitedCount = Math.min(this.count, 10) // 修改另一个变量
}
}总结
ArkUI 提供了丰富而强大的状态管理能力,从 V1 到 V2 的演进体现了框架对开发体验的不断优化。
- V1 状态管理:功能完整,适用于 API 9~11 的项目,概念相对分散
- V2 状态管理:API 12+ 推荐,更统一、更简洁、性能更好
- 全局状态管理:
AppStorage、LocalStorage、PersistentStorage、Environment覆盖不同层级的状态共享需求 - 最佳实践:状态提升、状态下沉、单一数据源、性能优化等模式帮助构建可维护的应用
建议新项目优先使用 V2 状态管理,老项目可以逐步迁移。无论使用哪个版本,遵循最佳实践都能帮助你构建高性能、可维护的 ArkUI 应用。