4 第3章 高级状态管理
HarmonyOS应用开发者高级认证教程
5 第3章 高级状态管理
5.1 学习目标
完成本章学习后,你将能够:
- 深入理解状态管理V2的设计动机和使用方法
- 掌握AppStorage、LocalStorage、PersistentStorage等全局状态管理方案
- 熟练运用@State/@Prop/@Link/@Provide/@Consume等状态装饰器
- 理解响应式数据流的底层原理(数据劫持、依赖收集、触发更新)
- 设计合理的状态管理架构,实现状态分层和持久化
5.2 3.1 状态管理V2
5.2.1 3.1.1 V2状态管理的设计动机
HarmonyOS状态管理V2是对V1版本的重大升级,解决了V1中的多个痛点:
V1存在的问题:
// V1的问题1:@Observed只能观察第一层属性
@Observed
class V1Model {
name: string = ""
address: Address = new Address() // 嵌套对象需要额外@Observed
}
@Observed
class Address {
city: string = ""
street: string = ""
}
// V1中修改嵌套属性可能不会触发UI更新
// model.address.city = "北京" // 可能不触发更新
// V1的问题2:@ObjectLink不能接收@State的简单类型
// @ObjectLink只能接收@Observed类的实例
// V1的问题3:深层嵌套观察需要大量装饰器
// V1的问题4:缺少细粒度的状态控制V2的设计目标:
- 更精确的观察粒度:支持深层属性自动观察
- 更简洁的API:减少装饰器数量,降低学习成本
- 更好的性能:按需更新,减少不必要的重渲染
- 更强的类型安全:编译时检查更完善
5.2.2 3.1.2 @ObservedV2装饰器
// 1. @ObservedV2基本使用
import { ObservedV2, Trace, local, param } from '@kit.ArkUI'
// @ObservedV2标记的类,其属性变化会被自动追踪
@ObservedV2
class TaskModel {
// @Trace标记需要被追踪的属性
@Trace title: string = ""
@Trace completed: boolean = false
@Trace priority: number = 0
@Trace tags: string[] = []
@Trace createdAt: number = Date.now()
constructor(title: string, priority: number = 0) {
this.title = title
this.priority = priority
}
toggleComplete(): void {
this.completed = !this.completed
}
addTag(tag: string): void {
this.tags.push(tag)
}
}
// 2. 嵌套对象的自动观察
@ObservedV2
class Address {
@Trace city: string = ""
@Trace street: string = ""
@Trace zipCode: string = ""
constructor(city: string, street: string, zipCode: string) {
this.city = city
this.street = street
this.zipCode = zipCode
}
}
@ObservedV2
class UserProfile {
@Trace name: string = ""
@Trace age: number = 0
@Trace address: Address = new Address("", "", "")
@Trace hobbies: string[] = []
// 深层属性变化也会触发UI更新
updateCity(city: string): void {
this.address.city = city // 会自动触发UI更新
}
}
// 3. 在组件中使用@ObservedV2
@Component
struct TaskItemComponent {
// 使用@ObjectLinkV2接收@ObservedV2对象
@ObjectLinkV2 task: TaskModel
build() {
Row() {
Checkbox()
.select(this.task.completed)
.onChange((checked: boolean) => {
this.task.toggleComplete()
})
Column() {
Text(this.task.title)
.fontSize(16)
.decoration({
type: this.task.completed
? TextDecorationType.LineThrough
: TextDecorationType.None
})
Text(`优先级: ${this.task.priority}`)
.fontSize(12)
.fontColor('#666666')
if (this.task.tags.length > 0) {
Row() {
ForEach(this.task.tags, (tag: string) => {
Text(tag)
.fontSize(10)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.backgroundColor('#E3F2FD')
.borderRadius(4)
.margin({ right: 4 })
}, (tag: string) => tag)
}
.margin({ top: 4 })
}
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
.padding({ left: 12 })
}
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.margin({ bottom: 8 })
}
}
// 4. 父组件管理任务列表
@Component
struct TaskListView {
@State tasks: TaskModel[] = []
@State newTaskTitle: string = ""
aboutToAppear(): void {
// 初始化任务数据
this.tasks = [
new TaskModel("完成项目报告", 1),
new TaskModel("准备会议材料", 2),
new TaskModel("代码审查", 1)
]
this.tasks[0].addTag("工作")
this.tasks[1].addTag("会议")
this.tasks[2].addTag("开发")
}
build() {
Column() {
// 添加任务
Row() {
TextInput({ placeholder: "添加新任务..." })
.layoutWeight(1)
.onChange((value: string) => {
this.newTaskTitle = value
})
Button("添加")
.onClick(() => {
if (this.newTaskTitle.length > 0) {
let newTask = new TaskModel(this.newTaskTitle)
this.tasks.push(newTask)
this.newTaskTitle = ""
}
})
}
.padding(16)
// 任务列表
List() {
ForEach(this.tasks, (task: TaskModel) => {
ListItem() {
TaskItemComponent({ task: task })
}
}, (task: TaskModel) => task.title + task.createdAt.toString())
}
.width('100%')
.layoutWeight(1)
// 统计信息
Text(`总计: ${this.tasks.length} | 完成: ${this.getCompletedCount()}`)
.fontSize(14)
.fontColor('#999999')
.padding(16)
}
}
private getCompletedCount(): number {
return this.tasks.filter((t: TaskModel) => t.completed).length
}
}5.2.3 3.1.3 @ObjectLinkV2使用
// 1. @ObjectLinkV2与@ObjectLink的区别
// @ObjectLinkV2:
// - 配合@ObservedV2使用
// - 支持更细粒度的属性观察
// - 只追踪实际被UI使用的属性
// @ObjectLink:
// - 配合@Observed使用
// - 观察整个对象的变化
// - 可能导致不必要的重渲染
// 2. @ObjectLinkV2的初始化方式
@Component
struct ChildComponent {
// 方式1:通过@ObjectLinkV2接收父组件传递的对象
@ObjectLinkV2 model: UserProfile
build() {
Column() {
Text(`姓名: ${this.model.name}`)
Text(`年龄: ${this.model.age}`)
Text(`城市: ${this.model.address.city}`)
Button("修改城市")
.onClick(() => {
// 修改嵌套属性也会触发更新
this.model.address.city = "上海"
})
}
}
}
// 3. 使用@Local和@Param装饰器
@Component
struct CounterComponent {
// @Local: 组件内部本地状态
@Local count: number = 0
// @Param: 从父组件接收的参数(单向)
@Param step: number = 1
build() {
Column({ space: 16 }) {
Text(`计数: ${this.count}`)
.fontSize(24)
Row({ space: 12 }) {
Button(`+${this.step}`)
.onClick(() => { this.count += this.step })
Button(`-${this.step}`)
.onClick(() => { this.count -= this.step })
Button("重置")
.onClick(() => { this.count = 0 })
}
}
}
}
// 父组件使用
@Component
struct ParentComponent {
@State stepValue: number = 5
build() {
Column({ space: 20 }) {
CounterComponent({ step: this.stepValue })
Row({ space: 12 }) {
Button("步长=1")
.onClick(() => { this.stepValue = 1 })
Button("步长=5")
.onClick(() => { this.stepValue = 5 })
Button("步长=10")
.onClick(() => { this.stepValue = 10 })
}
}
}
}
// 4. @ObservedV2中的计算属性
@ObservedV2
class CartModel {
@Trace items: CartItem[] = []
@Trace discountRate: number = 1.0 // 1.0表示无折扣
addItem(item: CartItem): void {
this.items.push(item)
}
removeItem(index: number): void {
this.items.splice(index, 1)
}
// 计算属性 - 总价
get totalPrice(): number {
return this.items.reduce(
(sum: number, item: CartItem) => sum + item.price * item.quantity,
0
) * this.discountRate
}
// 计算属性 - 商品数量
get itemCount(): number {
return this.items.reduce(
(sum: number, item: CartItem) => sum + item.quantity,
0
)
}
applyDiscount(rate: number): void {
this.discountRate = rate
}
}
class CartItem {
name: string
price: number
quantity: number
constructor(name: string, price: number, quantity: number) {
this.name = name
this.price = price
this.quantity = quantity
}
}
// 在组件中使用计算属性
@Component
struct CartView {
@ObjectLinkV2 cart: CartModel
build() {
Column() {
List() {
ForEach(this.cart.items, (item: CartItem, index: number) => {
ListItem() {
Row() {
Text(item.name).layoutWeight(1)
Text(`¥${item.price} x ${item.quantity}`)
Button("删除")
.onClick(() => {
this.cart.removeItem(index)
})
}
.padding(12)
}
}, (item: CartItem) => item.name)
}
.layoutWeight(1)
// 底部结算栏
Column() {
Text(`商品数量: ${this.cart.itemCount}`)
Text(`总计: ¥${this.cart.totalPrice.toFixed(2)}`)
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor('#FF4444')
Button("结算")
.width('100%')
.margin({ top: 12 })
}
.padding(16)
.backgroundColor('#F5F5F5')
}
}
}5.2.4 3.1.4 V1与V2的对比与迁移
// V1到V2的迁移对照表
/*
┌──────────────────┬──────────────────────┬──────────────────────┐
│ 功能 │ V1 │ V2 │
├──────────────────┼──────────────────────┼──────────────────────┤
│ 组件内部状态 │ @State │ @Local │
│ 父传子(单向) │ @Prop │ @Param │
│ 父传子(双向) │ @Link │ @Event │
│ 观察对象 │ @Observed │ @ObservedV2 │
│ 接收观察对象 │ @ObjectLink │ @ObjectLinkV2 │
│ 跨层级传递 │ @Provide/@Consume │ @Provider/@Consumer │
│ 全局状态 │ AppStorage │ AppStorage(保持) │
│ 页面级状态 │ LocalStorage │ LocalStorage(保持) │
│ 持久化 │ PersistentStorage │ PersistentStorage │
│ 环境状态 │ Environment │ Environment │
│ 状态监听 │ @Watch │ @Monitor │
└──────────────────┴──────────────────────┴──────────────────────┘
*/
// V1写法
@Observed
class V1UserModel {
name: string = ""
age: number = 0
}
@Component
struct V1ChildComponent {
@ObjectLink user: V1UserModel
build() {
Text(`${this.user.name}, ${this.user.age}岁`)
}
}
// V2写法(迁移后)
@ObservedV2
class V2UserModel {
@Trace name: string = ""
@Trace age: number = 0
}
@Component
struct V2ChildComponent {
@ObjectLinkV2 user: V2UserModel
build() {
Text(`${this.user.name}, ${this.user.age}岁`)
}
}
// 迁移注意事项:
// 1. @Observed -> @ObservedV2,属性需要加@Trace
// 2. @ObjectLink -> @ObjectLinkV2
// 3. @State -> @Local(组件内部状态)
// 4. @Prop -> @Param(单向传递)
// 5. @Link -> @Event(双向绑定)
// 6. V1和V2可以混合使用,但建议逐步迁移5.3 3.2 全局状态管理方案
5.3.1 3.2.1 AppStorage深入
// AppStorage是应用级别的全局状态存储
// 所有页面和组件都可以访问
// 1. AppStorage基本操作
@Component
struct AppStorageExample {
// 通过@StorageLink双向绑定AppStorage
@StorageLink('userName'): string = ''
// 通过@StorageProp单向读取AppStorage
@StorageProp('appVersion'): appVersion: string = '1.0.0'
// 通过@StorageLink绑定复杂对象
@StorageLink('userSettings'): settings: UserSettings = new UserSettings()
aboutToAppear(): void {
// 初始化AppStorage
if (!AppStorage.has('userName')) {
AppStorage.setOrCreate('userName', '默认用户')
}
if (!AppStorage.has('appVersion')) {
AppStorage.setOrCreate('appVersion', '1.0.0')
}
if (!AppStorage.has('userSettings')) {
AppStorage.setOrCreate('userSettings', new UserSettings())
}
}
build() {
Column({ space: 16 }) {
// 双向绑定 - 修改会同步到AppStorage
TextInput({ text: this.userName, placeholder: "用户名" })
.onChange((value: string) => {
this.userName = value
})
// 单向读取
Text(`版本: ${this.appVersion}`)
.fontSize(14)
.fontColor('#666666')
// 修改AppStorage中的值
Button("更新用户名")
.onClick(() => {
this.userName = "新用户名"
// 等价于 AppStorage.set('userName', '新用户名')
})
// 监听AppStorage变化
Button("读取设置")
.onClick(() => {
let settings: UserSettings =
AppStorage.get('userSettings') as UserSettings
console.log(`主题: ${settings.theme}`)
console.log(`字体大小: ${settings.fontSize}`)
})
}
.padding(24)
}
}
// 2. AppStorage的高级用法
class UserSettings {
theme: string = "light"
fontSize: number = 14
language: string = "zh-CN"
notifications: boolean = true
}
// 全局状态管理器
class GlobalStateManager {
// 初始化所有全局状态
static initialize(): void {
// 用户信息
AppStorage.setOrCreate('currentUser', null)
AppStorage.setOrCreate('isAuthenticated', false)
// 应用设置
let defaultSettings = new UserSettings()
AppStorage.setOrCreate('userSettings', defaultSettings)
// 网络状态
AppStorage.setOrCreate('isOnline', true)
AppStorage.setOrCreate('networkType', 'wifi')
// 应用级UI状态
AppStorage.setOrCreate('isLoading', false)
AppStorage.setOrCreate('errorMessage', '')
}
// 用户登录
static login(user: Object): void {
AppStorage.set('currentUser', user)
AppStorage.set('isAuthenticated', true)
}
// 用户登出
static logout(): void {
AppStorage.set('currentUser', null)
AppStorage.set('isAuthenticated', false)
}
// 更新网络状态
static updateNetworkStatus(online: boolean, type: string): void {
AppStorage.set('isOnline', online)
AppStorage.set('networkType', type)
}
// 显示全局加载
static showLoading(): void {
AppStorage.set('isLoading', true)
}
static hideLoading(): void {
AppStorage.set('isLoading', false)
}
}
// 3. 在EntryAbility中初始化全局状态
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化全局状态
GlobalStateManager.initialize()
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index')
}
}5.3.2 3.2.2 LocalStorage与PersistentStorage
// 1. LocalStorage - 页面级别的状态存储
// 每个UIAbility页面有独立的LocalStorage
// 在UIAbility中创建LocalStorage
export default class DetailAbility extends UIAbility {
// 创建页面级LocalStorage
private pageStorage: LocalStorage = new LocalStorage()
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 初始化页面级状态
this.pageStorage.setOrCreate('pageTitle', '详情页')
this.pageStorage.setOrCreate('itemId', 0)
// 从Want中获取参数存入LocalStorage
if (want.parameters) {
let itemId: number = want.parameters['itemId'] as number ?? 0
this.pageStorage.set('itemId', itemId)
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// 将LocalStorage与窗口关联
windowStage.loadContent('pages/Detail', (err) => {
if (err.code) return
})
}
}
// 在组件中使用LocalStorage
@Entry(new LocalStorage())
@Component
struct DetailPage {
@LocalStorageLink('pageTitle') pageTitle: string = ''
@LocalStorageLink('itemId') itemId: number = 0
@LocalStorageProp('pageTitle') title: string = ''
build() {
Column() {
Text(this.pageTitle)
.fontSize(24)
.fontWeight(FontWeight.Bold)
Text(`商品ID: ${this.itemId}`)
.fontSize(16)
}
}
}
// 2. PersistentStorage - 持久化存储
// 数据会在应用重启后保留
@Component
struct PersistentStorageExample {
// 通过@StorageLink自动持久化
@StorageLink('theme'): string = 'light'
@StorageLink('fontSize'): number = 14
@StorageLink('lastLoginTime'): number = 0
aboutToAppear(): void {
// 配置PersistentStorage
// 通常在EntryAbility的onCreate中配置
// PersistentStorage.persistProp('theme', 'light')
// PersistentStorage.persistProp('fontSize', 14)
// PersistentStorage.persistProp('lastLoginTime', 0)
}
build() {
Column({ space: 20 }) {
// 主题选择
Text("主题设置")
.fontSize(18)
.fontWeight(FontWeight.Medium)
Row({ space: 12 }) {
Button("浅色")
.backgroundColor(this.theme === 'light' ? '#2196F3' : '#E0E0E0')
.onClick(() => { this.theme = 'light' })
Button("深色")
.backgroundColor(this.theme === 'dark' ? '#2196F3' : '#E0E0E0')
.onClick(() => { this.theme = 'dark' })
}
// 字体大小
Text(`字体大小: ${this.fontSize}`)
Slider({
value: this.fontSize,
min: 12,
max: 24,
step: 1
})
.onChange((value: number) => {
this.fontSize = value
})
// 上次登录时间
Text(`上次登录: ${this.formatTime(this.lastLoginTime)}`)
.fontSize(12)
.fontColor('#999999')
Button("记录登录时间")
.onClick(() => {
this.lastLoginTime = Date.now()
})
}
.padding(24)
}
private formatTime(timestamp: number): string {
if (timestamp === 0) return "从未登录"
let date = new Date(timestamp)
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`
}
}
// 3. 在EntryAbility中配置PersistentStorage
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// 配置需要持久化的属性
PersistentStorage.persistProp('theme', 'light')
PersistentStorage.persistProp('fontSize', 14)
PersistentStorage.persistProp('lastLoginTime', 0)
PersistentStorage.persistProp('userName', '')
PersistentStorage.persistProp('isFirstLaunch', true)
}
onWindowStageCreate(windowStage: window.WindowStage): void {
windowStage.loadContent('pages/Index')
}
}5.3.3 3.2.3 Environment环境变量管理
// Environment用于管理设备环境相关的状态
@Component
struct EnvironmentExample {
// 通过@StorageLink读取环境变量
@StorageLink('systemLanguage') systemLanguage: string = ''
@StorageLink('colorMode') colorMode: number = 0
aboutToAppear(): void {
// 环境变量由系统自动管理
// 可用的环境变量包括:
// - colorMode: 颜色模式(0=浅色,1=深色)
// - systemLanguage: 系统语言
// - fontScale: 字体缩放比例
}
build() {
Column({ space: 16 }) {
// 根据颜色模式切换样式
Text("环境感知组件")
.fontSize(24)
.fontColor(this.colorMode === 1 ? '#FFFFFF' : '#000000')
Text(`系统语言: ${this.systemLanguage}`)
.fontSize(16)
Text(`颜色模式: ${this.colorMode === 1 ? '深色' : '浅色'}`)
.fontSize(16)
// 根据环境调整UI
Column() {
Text("自适应内容")
.fontSize(16)
.fontColor(this.colorMode === 1 ? '#CCCCCC' : '#333333')
}
.width('100%')
.padding(16)
.backgroundColor(this.colorMode === 1 ? '#1E1E1E' : '#FFFFFF')
.borderRadius(12)
}
.width('100%')
.height('100%')
.padding(24)
.backgroundColor(this.colorMode === 1 ? '#121212' : '#F5F5F5')
}
}
// 自定义环境配置管理
class EnvironmentConfig {
// 根据设备类型返回不同配置
static getConfig(): DeviceConfig {
let config: DeviceConfig = {
isPhone: true,
isTablet: false,
screenDensity: 2,
maxColumns: 1
}
// 实际项目中通过deviceInfo获取设备信息
// import { deviceInfo } from '@kit.BasicServicesKit'
// config.isTablet = deviceInfo.deviceType === 'tablet'
if (config.isTablet) {
config.maxColumns = 3
config.screenDensity = 2
}
return config
}
}
interface DeviceConfig {
isPhone: boolean
isTablet: boolean
screenDensity: number
maxColumns: number
}5.3.4 3.2.4 全局状态的最佳实践
// 全局状态管理最佳实践
// 1. 状态分类与存储位置选择
/*
┌────────────────────┬───────────────────┬──────────────────────┐
│ 状态类型 │ 存储位置 │ 示例 │
├────────────────────┼───────────────────┼──────────────────────┤
│ 组件内部UI状态 │ @Local/@State │ 展开/折叠状态 │
│ 父子组件共享状态 │ @Param/@Event │ 表单数据 │
│ 跨组件共享状态 │ @Provide/@Consume │ 主题配置 │
│ 应用级运行时状态 │ AppStorage │ 用户登录状态 │
│ 页面级状态 │ LocalStorage │ 页面导航参数 │
│ 需要持久化的状态 │ PersistentStorage │ 用户偏好设置 │
│ 环境变量 │ Environment │ 颜色模式、语言 │
└────────────────────┴───────────────────┴──────────────────────┘
*/
// 2. 全局状态管理器设计模式
@ObservedV2
class AuthStore {
@Trace currentUser: UserInfo | null = null
@Trace isAuthenticated: boolean = false
@Trace token: string = ""
async login(username: string, password: string): Promise<boolean> {
try {
// 模拟API调用
let response: Object = await this.callLoginApi(username, password)
let record = response as Record<string, Object>
this.currentUser = new UserInfo(
record['id'] as number,
record['name'] as string,
record['email'] as string
)
this.token = record['token'] as string
this.isAuthenticated = true
// 同步到AppStorage
AppStorage.set('currentUser', this.currentUser)
AppStorage.set('isAuthenticated', true)
AppStorage.set('token', this.token)
return true
} catch (error) {
console.error("登录失败:", error)
return false
}
}
logout(): void {
this.currentUser = null
this.token = ""
this.isAuthenticated = false
AppStorage.set('currentUser', null)
AppStorage.set('isAuthenticated', false)
AppStorage.set('token', '')
}
private async callLoginApi(
username: string,
password: string
): Promise<Object> {
return new Promise<Object>((resolve) => {
setTimeout(() => {
resolve({
id: 1,
name: username,
email: `${username}@example.com`,
token: "mock_token_12345"
})
}, 1000)
})
}
}
class UserInfo {
id: number
name: string
email: string
constructor(id: number, name: string, email: string) {
this.id = id
this.name = name
this.email = email
}
}
// 3. 全局状态单例管理
class StoreRegistry {
private static authStore: AuthStore | null = null
private static cartStore: CartStore | null = null
static getAuth(): AuthStore {
if (!StoreRegistry.authStore) {
StoreRegistry.authStore = new AuthStore()
}
return StoreRegistry.authStore
}
static getCart(): CartStore {
if (!StoreRegistry.cartStore) {
StoreRegistry.cartStore = new CartStore()
}
return StoreRegistry.cartStore
}
}
@ObservedV2
class CartStore {
@Trace items: CartItemV2[] = []
get totalAmount(): number {
return this.items.reduce(
(sum: number, item: CartItemV2) => sum + item.totalPrice,
0
)
}
addItem(product: ProductInfo, quantity: number = 1): void {
let existing: CartItemV2[] = this.items.filter(
(item: CartItemV2) => item.productId === product.id
)
if (existing.length > 0) {
existing[0].quantity += quantity
} else {
this.items.push(new CartItemV2(product, quantity))
}
}
removeItem(productId: number): void {
this.items = this.items.filter(
(item: CartItemV2) => item.productId !== productId
)
}
clear(): void {
this.items = []
}
}
@ObservedV2
class CartItemV2 {
@Trace productId: number
@Trace productName: string
@Trace price: number
@Trace quantity: number
constructor(product: ProductInfo, quantity: number) {
this.productId = product.id
this.productName = product.name
this.price = product.price
this.quantity = quantity
}
get totalPrice(): number {
return this.price * this.quantity
}
}
class ProductInfo {
id: number
name: string
price: number
constructor(id: number, name: string, price: number) {
this.id = id
this.name = name
this.price = price
}
}5.4 3.3 组件间状态共享
5.4.1 3.3.1 @State/@Prop/@Link深入
// 1. @State - 组件内部状态
@Component
struct StateExample {
@State count: number = 0
@State message: string = "Hello"
@State isVisible: boolean = true
@State items: string[] = ["苹果", "香蕉", "橙子"]
build() {
Column({ space: 12 }) {
Text(`计数: ${this.count}`)
Button("+1").onClick(() => { this.count++ })
Text(this.message)
Button("修改消息").onClick(() => {
this.message = "消息已更新 " + this.count
})
if (this.isVisible) {
Text("可见内容")
}
Button("切换可见性").onClick(() => {
this.isVisible = !this.isVisible
})
// 数组状态
List() {
ForEach(this.items, (item: string) => {
ListItem() {
Text(item)
}
}, (item: string) => item)
}
}
}
}
// 2. @Prop - 父到子的单向数据流
@Component
struct ChildDisplay {
// @Prop: 从父组件接收,子组件修改不会影响父组件
@Prop title: string = ""
@Prop count: number = 0
@State localCount: number = 0
build() {
Column({ space: 8 }) {
Text(`标题: ${this.title}`)
.fontSize(18)
Text(`来自父组件: ${this.count}`)
.fontSize(16)
Text(`本地计数: ${this.localCount}`)
.fontSize(16)
Button("本地+1")
.onClick(() => {
this.localCount++
// 修改@Prop值不会影响父组件
this.count++ // 只修改本地副本
})
}
.padding(16)
.backgroundColor('#F0F0F0')
.borderRadius(8)
}
}
@Component
struct ParentWithProp {
@State parentCount: number = 0
build() {
Column({ space: 16 }) {
Text(`父组件计数: ${this.parentCount}`)
Button("父+1").onClick(() => { this.parentCount++ })
// 传递给子组件
ChildDisplay({
title: "子组件示例",
count: this.parentCount
})
}
.padding(24)
}
}
// 3. @Link - 父到子的双向数据绑定
@Component
struct ChildEditor {
// @Link: 与父组件双向绑定
@Link value: number
@Link text: string
build() {
Column({ space: 12 }) {
Text(`子组件值: ${this.value}`)
.fontSize(18)
Button("子+1").onClick(() => {
this.value++ // 修改会同步到父组件
})
Button("子-1").onClick(() => {
this.value-- // 修改会同步到父组件
})
TextInput({ text: this.text })
.onChange((val: string) => {
this.text = val // 文本变化也会同步
})
}
.padding(16)
.backgroundColor('#E8F5E9')
.borderRadius(8)
}
}
@Component
struct ParentWithLink {
@State parentValue: number = 10
@State parentText: string = "初始文本"
build() {
Column({ space: 16 }) {
Text(`父组件值: ${this.parentValue}`)
.fontSize(20)
Text(`父组件文本: ${this.parentText}`)
.fontSize(16)
// 使用$传递引用
ChildEditor({
value: $parentValue,
text: $parentText
})
}
.padding(24)
}
}5.4.2 3.3.2 @Provide/@Consume跨层级通信
// @Provide/@Consume允许跨越多层组件直接传递状态
// 1. 基本使用
@Component
struct ThemeProvider {
@Provide('theme') themeColor: string = '#2196F3'
@Provide('fontSize') baseFontSize: number = 14
build() {
Column() {
Text("主题提供者")
.fontSize(20)
.fontColor(this.themeColor)
// 中间可能有多层组件嵌套
MiddleComponent()
Row({ space: 12 }) {
Button("蓝色主题")
.onClick(() => { this.themeColor = '#2196F3' })
Button("绿色主题")
.onClick(() => { this.themeColor = '#4CAF50' })
Button("红色主题")
.onClick(() => { this.themeColor = '#F44336' })
}
}
.padding(24)
}
}
@Component
struct MiddleComponent {
build() {
Column() {
Text("中间组件(不需要传递状态)")
.fontSize(14)
.fontColor('#666666')
// 直接嵌套深层组件,不需要手动传递theme
DeepChildComponent()
}
.padding(16)
.margin(8)
}
}
@Component
struct DeepChildComponent {
// @Consume自动从祖先组件获取@Provide的值
@Consume('theme') themeColor: string
@Consume('fontSize') baseFontSize: number
build() {
Column() {
Text("深层子组件")
.fontSize(this.baseFontSize + 4)
.fontColor(this.themeColor)
Text("自动获取了祖先组件的主题色")
.fontSize(this.baseFontSize)
Button("使用主题色")
.backgroundColor(this.themeColor)
}
.padding(16)
.margin(8)
.border({ width: 2, color: this.themeColor })
.borderRadius(8)
}
}
// 2. 复杂场景:全局配置注入
@ObservedV2
class AppConfig {
@Trace language: string = "zh-CN"
@Trace theme: string = "light"
@Trace region: string = "CN"
get isDarkMode(): boolean {
return this.theme === "dark"
}
get isChinese(): boolean {
return this.language.startsWith("zh")
}
}
@Component
struct ConfigProvider {
@Provide('appConfig') config: AppConfig = new AppConfig()
build() {
Column() {
// 配置面板
Row({ space: 12 }) {
Button("中文")
.onClick(() => { this.config.language = "zh-CN" })
Button("English")
.onClick(() => { this.config.language = "en-US" })
Button("深色")
.onClick(() => { this.config.theme = "dark" })
Button("浅色")
.onClick(() => { this.config.theme = "light" })
}
.padding(16)
// 子组件自动获取配置
ConfigConsumer()
}
}
}
@Component
struct ConfigConsumer {
@Consume('appConfig') config: AppConfig
build() {
Column({ space: 8 }) {
Text(this.config.isChinese ? "当前语言:中文" : "Current: English")
Text(this.config.isDarkMode ? "深色模式" : "浅色模式")
}
.padding(16)
.backgroundColor(this.config.isDarkMode ? '#1E1E1E' : '#FFFFFF')
}
}
// 3. @Provide/@Consume与@StorageLink的配合使用
@Component
struct GlobalThemeProvider {
@Provide('globalTheme') globalTheme: string = 'light'
@StorageLink('theme') storedTheme: string = 'light'
aboutToAppear(): void {
this.globalTheme = this.storedTheme
}
build() {
Column() {
Text(`全局主题: ${this.globalTheme}`)
Button("切换主题")
.onClick(() => {
let newTheme: string =
this.globalTheme === 'light' ? 'dark' : 'light'
this.globalTheme = newTheme
this.storedTheme = newTheme // 同步到持久化存储
})
}
}
}5.4.3 3.3.3 @Watch状态监听
// 1. @Watch基本使用
@Component
struct WatchExample {
@State @Watch('onCountChange') count: number = 0
@State @Watch('onTextChange') text: string = ""
@State log: string[] = []
// 当count变化时触发
onCountChange(): void {
this.log.push(`count变为: ${this.count}`)
if (this.log.length > 5) {
this.log.shift()
}
}
// 当text变化时触发
onTextChange(): void {
this.log.push(`text变为: "${this.text}" (长度: ${this.text.length})`)
if (this.log.length > 5) {
this.log.shift()
}
}
build() {
Column({ space: 16 }) {
Text(`计数: ${this.count}`)
.fontSize(24)
Row({ space: 12 }) {
Button("+1").onClick(() => { this.count++ })
Button("-1").onClick(() => { this.count-- })
Button("x2").onClick(() => { this.count *= 2 })
}
TextInput({ placeholder: "输入文本..." })
.onChange((value: string) => { this.text = value })
// 显示变化日志
Column() {
Text("变化日志:")
.fontSize(14)
.fontWeight(FontWeight.Medium)
ForEach(this.log, (entry: string) => {
Text(entry)
.fontSize(12)
.fontColor('#666666')
}, (entry: string) => entry)
}
.alignItems(HorizontalAlign.Start)
.padding(12)
.backgroundColor('#F5F5F5')
.borderRadius(8)
}
.padding(24)
}
}
// 2. @Watch实现表单验证
@Component
struct FormValidationExample {
@State @Watch('validateEmail') email: string = ""
@State @Watch('validatePassword') password: string = ""
@State @Watch('validateConfirm') confirmPassword: string = ""
@State emailError: string = ""
@State passwordError: string = ""
@State confirmError: string = ""
@State isFormValid: boolean = false
validateEmail(): void {
if (this.email.length === 0) {
this.emailError = "邮箱不能为空"
} else if (!this.email.includes('@')) {
this.emailError = "请输入有效的邮箱地址"
} else {
this.emailError = ""
}
this.checkFormValidity()
}
validatePassword(): void {
if (this.password.length === 0) {
this.passwordError = "密码不能为空"
} else if (this.password.length < 8) {
this.passwordError = "密码至少8个字符"
} else if (!/[A-Z]/.test(this.password)) {
this.passwordError = "密码需要包含大写字母"
} else {
this.passwordError = ""
}
this.checkFormValidity()
this.validateConfirm()
}
validateConfirm(): void {
if (this.confirmPassword !== this.password) {
this.confirmError = "两次密码输入不一致"
} else {
this.confirmError = ""
}
this.checkFormValidity()
}
checkFormValidity(): void {
this.isFormValid =
this.emailError.length === 0 &&
this.passwordError.length === 0 &&
this.confirmError.length === 0 &&
this.email.length > 0 &&
this.password.length > 0 &&
this.confirmPassword.length > 0
}
build() {
Column({ space: 16 }) {
Text("用户注册")
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 邮箱
Column() {
TextInput({ placeholder: "邮箱地址" })
.onChange((value: string) => { this.email = value })
if (this.emailError.length > 0) {
Text(this.emailError)
.fontSize(12)
.fontColor('#F44336')
}
}
.alignItems(HorizontalAlign.Start)
// 密码
Column() {
TextInput({ placeholder: "密码" })
.type(InputType.Password)
.onChange((value: string) => { this.password = value })
if (this.passwordError.length > 0) {
Text(this.passwordError)
.fontSize(12)
.fontColor('#F44336')
}
}
.alignItems(HorizontalAlign.Start)
// 确认密码
Column() {
TextInput({ placeholder: "确认密码" })
.type(InputType.Password)
.onChange((value: string) => { this.confirmPassword = value })
if (this.confirmError.length > 0) {
Text(this.confirmError)
.fontSize(12)
.fontColor('#F44336')
}
}
.alignItems(HorizontalAlign.Start)
Button("注册")
.enabled(this.isFormValid)
.width('100%')
.onClick(() => {
console.log("注册成功")
})
}
.padding(24)
}
}
// 3. @Watch与@Monitor(V2)的对比
// @Watch: V1版本,监听单个状态变化
// @Monitor: V2版本,可以监听多个状态变化
@Component
struct MonitorExample {
@Local firstName: string = ""
@Local lastName: string = ""
@State fullName: string = ""
// V2的@Monitor可以监听多个属性
// @Monitor(['firstName', 'lastName'])
onNameChange(): void {
this.fullName = `${this.firstName} ${this.lastName}`
}
build() {
Column({ space: 12 }) {
TextInput({ placeholder: "名" })
.onChange((value: string) => { this.firstName = value })
TextInput({ placeholder: "姓" })
.onChange((value: string) => { this.lastName = value })
Text(`全名: ${this.fullName}`)
.fontSize(18)
}
.padding(24)
}
}5.4.4 3.3.4 状态管理的性能影响
// 1. 避免不必要的重渲染
// 错误示例:在build()中创建大量不必要的状态依赖
@Component
struct BadPerformanceExample {
@State allData: string[] = []
@State selectedIndex: number = -1
build() {
Column() {
// 问题:每次selectedIndex变化,整个列表都会重渲染
ForEach(this.allData, (item: string, index: number) => {
ListItem() {
Text(item)
.backgroundColor(
index === this.selectedIndex ? '#2196F3' : '#FFFFFF'
)
}
}, (item: string) => item)
}
}
}
// 优化方案:将选中状态隔离到子组件
@Component
struct GoodPerformanceExample {
@State allData: string[] = []
@State selectedIndex: number = -1
build() {
Column() {
ForEach(this.allData, (item: string, index: number) => {
ListItem() {
// 选中状态隔离在子组件中
IsolatedItem({
text: item,
isSelected: index === this.selectedIndex,
onSelect: () => { this.selectedIndex = index }
})
}
}, (item: string) => item)
}
}
}
@Component
struct IsolatedItem {
text: string = ""
isSelected: boolean = false
onSelect: () => void = () => {}
build() {
Text(this.text)
.backgroundColor(this.isSelected ? '#2196F3' : '#FFFFFF')
.onClick(() => { this.onSelect() })
}
}
// 2. 使用LazyForEach优化长列表
@Component
struct LazyListExample {
private dataSource: MyDataSource = new MyDataSource()
aboutToAppear(): void {
// 初始化大量数据
for (let i: number = 0; i < 10000; i++) {
this.dataSource.addData(`项目 ${i + 1}`)
}
}
build() {
List() {
LazyForEach(this.dataSource, (item: string) => {
ListItem() {
Text(item)
.fontSize(16)
.padding(16)
}
}, (item: string) => item)
}
.width('100%')
.height('100%')
}
}
// LazyForEach数据源实现
class MyDataSource implements IDataSource {
private data: string[] = []
private listeners: DataChangeListener[] = []
totalCount(): number {
return this.data.length
}
getData(index: number): string {
return this.data[index]
}
addData(item: string): void {
this.data.push(item)
// 通知LazyForEach数据变化
for (let listener of this.listeners) {
listener.onDataAdded(this.data.length - 1)
}
}
registerDataChangeListener(
listener: DataChangeListener
): void {
this.listeners.push(listener)
}
unregisterDataChangeListener(
listener: DataChangeListener
): void {
let index: number = this.listeners.indexOf(listener)
if (index >= 0) {
this.listeners.splice(index, 1)
}
}
}
// 3. 状态更新批处理
// 在一次事件处理中多次修改状态,框架会自动批处理
@Component
struct BatchUpdateExample {
@State a: number = 0
@State b: number = 0
@State c: number = 0
build() {
Column({ space: 12 }) {
Text(`A: ${this.a}, B: ${this.b}, C: ${this.c}`)
Button("同时更新")
.onClick(() => {
// 这些状态更新会被批处理
// 只会触发一次UI重渲染
this.a++
this.b += 2
this.c += 3
})
}
}
}5.5 3.4 响应式数据流
5.5.1 3.4.1 数据劫持与代理模式
// HarmonyOS状态管理的底层原理
// 1. 数据劫持 - Object.defineProperty方式(V1原理)
function createReactiveV1<T extends Object>(obj: T): T {
let reactive = { ...obj } as T
for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
let value: Object = obj[key]
Object.defineProperty(reactive, key, {
get(): Object {
// 依赖收集
console.log(`读取属性: ${key}`)
// track(key) - 收集当前正在执行的副作用
return value
},
set(newValue: Object): void {
if (newValue !== value) {
console.log(`设置属性: ${key} = ${newValue}`)
value = newValue
// trigger(key) - 触发依赖此属性的副作用
}
},
enumerable: true,
configurable: true
})
}
}
return reactive
}
// 2. Proxy代理方式(V2原理)
function createReactiveV2<T extends Object>(target: T): T {
return new Proxy(target, {
get(obj: T, key: string | symbol): Object {
let value: Object = Reflect.get(obj, key) as Object
// 依赖收集
console.log(`Proxy读取: ${String(key)}`)
// track(obj, key)
// 如果值是对象,递归代理
if (typeof value === 'object' && value !== null) {
return createReactiveV2(value as Object)
}
return value
},
set(obj: T, key: string | symbol, value: Object): boolean {
let oldValue: Object = Reflect.get(obj, key) as Object
let result: boolean = Reflect.set(obj, key, value)
if (oldValue !== value) {
console.log(`Proxy设置: ${String(key)} = ${value}`)
// trigger(obj, key)
}
return result
}
}) as T
}
// 3. 使用示例
interface ReactiveState {
name: string
count: number
nested: { value: number }
}
// V1方式
let stateV1 = createReactiveV1<ReactiveState>({
name: "测试",
count: 0,
nested: { value: 1 }
})
// V2方式
let stateV2 = createReactiveV2<ReactiveState>({
name: "测试",
count: 0,
nested: { value: 1 }
})5.5.2 3.4.2 依赖收集与触发更新
// 简化的响应式系统实现
// 依赖收集器
class DependencyTracker {
private static currentEffect: (() => void) | null = null
private static targetMap: Map<Object, Map<string, Set<() => void>>> =
new Map()
// 开始追踪依赖
static startTrack(effect: () => void): void {
DependencyTracker.currentEffect = effect
}
// 结束追踪
static endTrack(): void {
DependencyTracker.currentEffect = null
}
// 收集依赖(在getter中调用)
static track(target: Object, key: string): void {
if (!DependencyTracker.currentEffect) return
let depsMap = DependencyTracker.targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
DependencyTracker.targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(DependencyTracker.currentEffect)
}
// 触发更新(在setter中调用)
static trigger(target: Object, key: string): void {
let depsMap = DependencyTracker.targetMap.get(target)
if (!depsMap) return
let deps = depsMap.get(key)
if (!deps) return
deps.forEach((effect: () => void) => {
effect()
})
}
}
// 创建响应式对象
function reactive<T extends Object>(target: T): T {
return new Proxy(target, {
get(obj: T, key: string | symbol): Object {
let value: Object = Reflect.get(obj, key) as Object
// 收集依赖
DependencyTracker.track(obj, String(key))
return value
},
set(obj: T, key: string | symbol, value: Object): boolean {
let result: boolean = Reflect.set(obj, key, value)
// 触发更新
DependencyTracker.trigger(obj, String(key))
return result
}
}) as T
}
// 副作用函数(类似组件的build方法)
function effect(fn: () => void): void {
DependencyTracker.startTrack(fn)
fn()
DependencyTracker.endTrack()
}
// 使用示例
let state = reactive({ count: 0, name: "测试" })
// 模拟组件渲染
effect(() => {
console.log(`渲染: count=${state.count}, name=${state.name}`)
})
// 输出: 渲染: count=0, name=测试
state.count = 1
// 输出: 渲染: count=1, name=测试
state.name = "新名称"
// 输出: 渲染: count=1, name=新名称5.5.3 3.4.3 计算属性与派生状态
// 1. 计算属性实现
class ComputedProperty<T> {
private value: T | null = null
private dirty: boolean = true
private getter: () => T
private deps: Set<Set<() => void>> = new Set()
constructor(getter: () => T) {
this.getter = getter
}
get Value(): T {
if (this.dirty) {
this.value = this.getter()
this.dirty = false
}
// 收集依赖
DependencyTracker.track(this as unknown as Object, 'value')
return this.value as T
}
// 标记为脏数据
invalidate(): void {
if (!this.dirty) {
this.dirty = true
// 触发依赖此计算属性的副作用
DependencyTracker.trigger(this as unknown as Object, 'value')
}
}
}
// 2. 在状态管理中使用计算属性
@ObservedV2
class OrderModel {
@Trace items: OrderItem[] = []
@Trace couponCode: string = ""
@Trace couponDiscount: number = 0
// 计算属性 - 小计
get subtotal(): number {
return this.items.reduce(
(sum: number, item: OrderItem) => sum + item.price * item.quantity,
0
)
}
// 计算属性 - 税费
get tax(): number {
return this.subtotal * 0.06 // 6%税率
}
// 计算属性 - 总价
get total(): number {
return this.subtotal + this.tax - this.couponDiscount
}
// 计算属性 - 商品总数
get itemCount(): number {
return this.items.reduce(
(sum: number, item: OrderItem) => sum + item.quantity,
0
)
}
addItem(name: string, price: number, quantity: number = 1): void {
this.items.push(new OrderItem(name, price, quantity))
}
applyCoupon(code: string, discount: number): void {
this.couponCode = code
this.couponDiscount = discount
}
}
class OrderItem {
name: string
price: number
quantity: number
constructor(name: string, price: number, quantity: number) {
this.name = name
this.price = price
this.quantity = quantity
}
}
// 在组件中使用
@Component
struct OrderSummary {
@ObjectLinkV2 order: OrderModel
build() {
Column({ space: 12 }) {
// 商品列表
ForEach(this.order.items, (item: OrderItem) => {
Row() {
Text(item.name).layoutWeight(1)
Text(`¥${item.price} x ${item.quantity}`)
Text(`= ¥${(item.price * item.quantity).toFixed(2)}`)
.width(80)
.textAlign(TextAlign.End)
}
.padding({ top: 8, bottom: 8 })
}, (item: OrderItem) => item.name)
Divider()
// 费用明细
Row() {
Text("小计").layoutWeight(1)
Text(`¥${this.order.subtotal.toFixed(2)}`)
}
Row() {
Text("税费 (6%)").layoutWeight(1)
Text(`¥${this.order.tax.toFixed(2)}`)
}
if (this.order.couponDiscount > 0) {
Row() {
Text(`优惠券 (${this.order.couponCode})`)
.layoutWeight(1)
Text(`-¥${this.order.couponDiscount.toFixed(2)}`)
.fontColor('#4CAF50')
}
}
Divider()
Row() {
Text("总计")
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
Text(`¥${this.order.total.toFixed(2)}`)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#FF4444')
}
Text(`共 ${this.order.itemCount} 件商品`)
.fontSize(12)
.fontColor('#999999')
}
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
}
}5.5.4 3.4.4 状态变更的批处理
// 1. 自动批处理 - 同一事件处理中的多次修改
@Component
struct AutoBatchExample {
@State x: number = 0
@State y: number = 0
@State z: number = 0
@State renderCount: number = 0
build() {
// 每次build执行时计数
this.renderCount++
Column({ space: 12 }) {
Text(`渲染次数: ${this.renderCount}`)
.fontSize(14)
.fontColor('#666666')
Text(`X: ${this.x}, Y: ${this.y}, Z: ${this.z}`)
.fontSize(18)
Button("单次更新三个状态")
.onClick(() => {
// 虽然修改了三个状态,但只会触发一次渲染
this.x++
this.y += 2
this.z += 3
})
Button("异步更新")
.onClick(async () => {
// 异步操作中的状态更新
this.x++
await new Promise<void>((resolve) =>
setTimeout(resolve, 100)
)
// await之后的修改会触发新的渲染
this.y++
})
}
.padding(24)
}
}
// 2. 手动批处理 - 使用requestAnimationFrame
@Component
struct ManualBatchExample {
@State positions: number[] = [0, 0, 0, 0, 0]
@State frameCount: number = 0
private animate(): void {
// 使用requestAnimationFrame确保动画流畅
requestAnimationFrame(() => {
this.frameCount++
// 在同一帧中更新所有位置
for (let i: number = 0; i < this.positions.length; i++) {
this.positions[i] = Math.sin(
(this.frameCount + i * 30) * Math.PI / 180
) * 50 + 50
}
// 递归调用继续动画
this.animate()
})
}
aboutToAppear(): void {
this.animate()
}
build() {
Column() {
ForEach(this.positions, (pos: number, index: number) => {
Row() {
Circle()
.width(20)
.height(20)
.fill('#2196F3')
.margin({ left: pos })
Text(`元素 ${index + 1}`)
.margin({ left: 12 })
}
.width('100%')
.padding(8)
}, (pos: number, index: number) => index.toString())
}
}
}
// 3. 避免状态更新的反模式
@Component
struct AntiPatternsExample {
@State data: string[] = []
build() {
Column() {
// 反模式1:在build中执行副作用
// 错误:build() { this.fetchData() }
// 反模式2:直接修改数组索引(可能不触发更新)
// 错误:this.data[0] = "新值"
// 正确:
Button("正确更新数组")
.onClick(() => {
// 创建新数组触发更新
let newData: string[] = [...this.data]
if (newData.length > 0) {
newData[0] = "已更新"
}
this.data = newData
})
// 反模式3:在循环中频繁修改状态
// 错误:
// for (let i = 0; i < 100; i++) {
// this.data.push(`item${i}`) // 每次push都触发更新
// }
// 正确:
Button("批量添加")
.onClick(() => {
let newItems: string[] = []
for (let i: number = 0; i < 100; i++) {
newItems.push(`item${i}`)
}
this.data = [...this.data, ...newItems] // 一次性更新
})
List() {
ForEach(this.data, (item: string) => {
ListItem() {
Text(item)
}
}, (item: string) => item)
}
}
}
}5.6 3.5 状态管理架构设计
5.6.1 3.5.1 状态分层策略
// 状态分为三个层次:UI状态、业务状态、应用状态
// 1. UI状态 - 仅在单个组件内使用
// 特征:生命周期与组件绑定,不需要跨组件共享
@Component
struct UIStateExample {
// 展开/折叠状态
@State isExpanded: boolean = false
// 输入框焦点状态
@State isFocused: boolean = false
// 加载状态
@State isLoading: boolean = false
// 动画状态
@State animationProgress: number = 0
// 临时选择状态
@State tempSelection: number = -1
build() {
Column() {
// 可折叠面板
Row() {
Text("面板标题")
.layoutWeight(1)
.onClick(() => {
this.isExpanded = !this.isExpanded
})
Text(this.isExpanded ? "▲" : "▼")
}
.padding(16)
if (this.isExpanded) {
Text("面板内容")
.padding(16)
}
// 加载按钮
Button(this.isLoading ? "加载中..." : "提交")
.enabled(!this.isLoading)
.onClick(async () => {
this.isLoading = true
await this.submitData()
this.isLoading = false
})
}
}
private async submitData(): Promise<void> {
await new Promise<void>((resolve) => setTimeout(resolve, 2000))
}
}
// 2. 业务状态 - 跨组件共享的业务数据
@ObservedV2
class UserBusinessState {
@Trace profile: UserProfile | null = null
@Trace orders: OrderInfo[] = []
@Trace favorites: number[] = [] // 收藏的商品ID列表
@Trace isLoadingProfile: boolean = false
@Trace isLoadingOrders: boolean = false
private apiService: ApiService
constructor(apiService: ApiService) {
this.apiService = apiService
}
async loadProfile(userId: number): Promise<void> {
this.isLoadingProfile = true
try {
this.profile = await this.apiService.getUserProfile(userId)
} finally {
this.isLoadingProfile = false
}
}
async loadOrders(): Promise<void> {
this.isLoadingOrders = true
try {
this.orders = await this.apiService.getUserOrders()
} finally {
this.isLoadingOrders = false
}
}
async toggleFavorite(productId: number): Promise<void> {
let index: number = this.favorites.indexOf(productId)
if (index >= 0) {
this.favorites.splice(index, 1)
await this.apiService.removeFavorite(productId)
} else {
this.favorites.push(productId)
await this.apiService.addFavorite(productId)
}
}
get isFavorite(productId: number): boolean {
return this.favorites.includes(productId)
}
}
// 3. 应用状态 - 全局共享的应用级数据
@ObservedV2
class ApplicationState {
@Trace isAuthenticated: boolean = false
@Trace currentTheme: string = "light"
@Trace networkStatus: NetworkStatus = NetworkStatus.Online
@Trace appVersion: string = "1.0.0"
@Trace lastSyncTime: number = 0
// 单例模式
private static instance: ApplicationState | null = null
static getInstance(): ApplicationState {
if (!ApplicationState.instance) {
ApplicationState.instance = new ApplicationState()
}
return ApplicationState.instance
}
updateNetworkStatus(status: NetworkStatus): void {
this.networkStatus = status
}
setAuthenticated(authenticated: boolean): void {
this.isAuthenticated = authenticated
}
switchTheme(theme: string): void {
this.currentTheme = theme
// 同步到持久化存储
AppStorage.set('theme', theme)
}
}
enum NetworkStatus {
Online,
Offline,
Poor
}
class UserProfile {
id: number = 0
name: string = ""
avatar: string = ""
email: string = ""
}
class OrderInfo {
id: number = 0
productName: string = ""
amount: number = 0
status: string = ""
}
// 4. 状态分层使用示例
@Component
struct LayeredStateExample {
// UI状态
@State showOrderDetail: boolean = false
@State selectedOrderIndex: number = -1
// 业务状态(通过依赖注入获取)
@ObjectLinkV2 businessState: UserBusinessState
// 应用状态
@Consume('appState') appState: ApplicationState
build() {
Column() {
// 网络状态指示器(应用状态)
if (this.appState.networkStatus !== NetworkStatus.Online) {
Text("网络连接异常")
.width('100%')
.padding(8)
.backgroundColor('#FFF3E0')
.fontColor('#E65100')
.textAlign(TextAlign.Center)
}
// 用户信息(业务状态)
if (this.businessState.profile) {
Row() {
Text(this.businessState.profile.name)
.fontSize(18)
.fontWeight(FontWeight.Bold)
Text(this.businessState.profile.email)
.fontSize(14)
.fontColor('#666666')
}
.padding(16)
}
// 订单列表(业务状态 + UI状态)
List() {
ForEach(this.businessState.orders,
(order: OrderInfo, index: number) => {
ListItem() {
OrderItemView({
order: order,
isSelected: index === this.selectedOrderIndex,
onSelect: () => {
this.selectedOrderIndex = index
this.showOrderDetail = true
}
})
}
},
(order: OrderInfo) => order.id.toString()
)
}
.layoutWeight(1)
}
}
}
@Component
struct OrderItemView {
order: OrderInfo = new OrderInfo()
isSelected: boolean = false
onSelect: () => void = () => {}
build() {
Row() {
Column() {
Text(this.order.productName)
.fontSize(16)
Text(`¥${this.order.amount} - ${this.order.status}`)
.fontSize(12)
.fontColor('#666666')
}
.layoutWeight(1)
.alignItems(HorizontalAlign.Start)
}
.padding(16)
.backgroundColor(this.isSelected ? '#E3F2FD' : '#FFFFFF')
.onClick(() => { this.onSelect() })
}
}5.6.2 3.5.2 状态管理规范与约束
// 1. 状态管理命名规范
// 状态变量命名:使用camelCase
// 状态类型命名:使用PascalCase
// 事件处理函数:使用handle前缀或on前缀
// 计算属性:使用get前缀或is/has/can前缀
@ObservedV2
class NamingConvention {
// 状态变量
@Trace userName: string = ""
@Trace isLoading: boolean = false
@Trace errorMessage: string = ""
// 计算属性
get isValid(): boolean {
return this.userName.length > 0
}
get displayName(): string {
return this.userName || "匿名用户"
}
// 操作方法
setName(name: string): void {
this.userName = name
}
async loadData(): Promise<void> {
this.isLoading = true
this.errorMessage = ""
try {
// 加载数据
} catch (error) {
this.errorMessage = "加载失败"
} finally {
this.isLoading = false
}
}
}
// 2. 状态更新规范
class StateUpdateRules {
// 规则1:使用不可变方式更新数组
static addItem<T>(array: T[], item: T): T[] {
return [...array, item]
}
static removeItem<T>(array: T[], index: number): T[] {
return array.filter((_: T, i: number) => i !== index)
}
static updateItem<T>(array: T[], index: number, newItem: T): T[] {
return array.map((item: T, i: number) =>
i === index ? newItem : item
)
}
// 规则2:使用不可变方式更新对象
static updateObject<T extends Object>(obj: T, updates: Partial<T>): T {
return { ...obj, ...updates }
}
// 规则3:状态更新应该通过明确的方法
// 错误:直接修改深层属性
// state.user.address.city = "新城市"
// 正确:通过方法更新
static updateUserCity(state: Object, city: string): void {
let userState = state as Record<string, Object>
let address = userState['address'] as Record<string, Object>
address['city'] = city
}
}
// 3. 状态管理约束检查器
class StateConstraintChecker {
// 检查状态值是否合法
static validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
static validateAge(age: number): boolean {
return age >= 0 && age <= 150
}
static validatePageSize(page: number, pageSize: number): boolean {
return page >= 1 && pageSize >= 1 && pageSize <= 100
}
// 状态变更拦截器
static withValidation<T>(
setter: (value: T) => void,
validator: (value: T) => boolean,
defaultValue: T
): (value: T) => void {
return (value: T): void => {
if (validator(value)) {
setter(value)
} else {
console.warn("状态值不合法,使用默认值")
setter(defaultValue)
}
}
}
}5.6.3 3.5.3 状态持久化方案
// 1. 基于PersistentStorage的简单持久化
class SimplePersistence {
static initialize(): void {
// 用户偏好
PersistentStorage.persistProp('theme', 'light')
PersistentStorage.persistProp('fontSize', 14)
PersistentStorage.persistProp('language', 'zh-CN')
// 用户会话
PersistentStorage.persistProp('lastUserId', 0)
PersistentStorage.persistProp('rememberMe', false)
// 应用状态
PersistentStorage.persistProp('firstLaunch', true)
PersistentStorage.persistProp('agreedTerms', false)
}
}
// 2. 基于Preferences的复杂数据持久化
import { dataPreferences } from '@kit.ArkDataKit'
class PreferencesManager {
private static preferences: dataPreferences.Preferences | null = null
private static PREF_NAME: string = "app_data"
static async initialize(context: common.Context): Promise<void> {
PreferencesManager.preferences =
await dataPreferences.getPreferences(context, PreferencesManager.PREF_NAME)
}
// 保存对象
static async saveObject(key: string, value: Object): Promise<void> {
if (PreferencesManager.preferences) {
await PreferencesManager.preferences.put(key, JSON.stringify(value))
await PreferencesManager.preferences.flush()
}
}
// 读取对象
static async loadObject<T>(key: string, defaultValue: T): Promise<T> {
if (!PreferencesManager.preferences) return defaultValue
let json: string =
await PreferencesManager.preferences.get(key, '') as string
if (json.length === 0) return defaultValue
try {
return JSON.parse(json) as T
} catch {
return defaultValue
}
}
// 保存数组
static async saveArray<T>(key: string, value: T[]): Promise<void> {
await PreferencesManager.saveObject(key, value)
}
// 读取数组
static async loadArray<T>(key: string): Promise<T[]> {
return await PreferencesManager.loadObject<T[]>(key, [])
}
// 删除键
static async remove(key: string): Promise<void> {
if (PreferencesManager.preferences) {
await PreferencesManager.preferences.delete(key)
await PreferencesManager.preferences.flush()
}
}
// 清除所有数据
static async clear(): Promise<void> {
if (PreferencesManager.preferences) {
await PreferencesManager.preferences.clear()
await PreferencesManager.preferences.flush()
}
}
}
// 3. 状态持久化服务
class PersistenceService {
// 自动保存装饰器逻辑
static async autoSave(
key: string,
data: Object,
debounceMs: number = 1000
): Promise<void> {
// 防抖保存
let timerId: number = -1
if (timerId !== -1) {
clearTimeout(timerId)
}
timerId = setTimeout(async () => {
await PreferencesManager.saveObject(key, data)
timerId = -1
}, debounceMs)
}
// 恢复状态
static async restoreState<T>(
key: string,
defaultState: T
): Promise<T> {
return await PreferencesManager.loadObject(key, defaultState)
}
}
// 4. 使用示例
@Component
struct PersistentFormExample {
@State formData: FormData = new FormData()
@State isSaved: boolean = false
private saveTimerId: number = -1
aboutToAppear(): void {
// 恢复之前保存的数据
this.loadSavedData()
}
private async loadSavedData(): Promise<void> {
this.formData = await PreferencesManager.loadObject(
'formData',
new FormData()
)
}
private scheduleSave(): void {
this.isSaved = false
if (this.saveTimerId !== -1) {
clearTimeout(this.saveTimerId)
}
this.saveTimerId = setTimeout(async () => {
await PreferencesManager.saveObject('formData', this.formData)
this.isSaved = true
this.saveTimerId = -1
}, 2000) // 2秒防抖
}
aboutToDisappear(): void {
// 组件销毁时立即保存
if (this.saveTimerId !== -1) {
clearTimeout(this.saveTimerId)
}
PreferencesManager.saveObject('formData', this.formData)
}
build() {
Column({ space: 16 }) {
Text("自动保存表单")
.fontSize(20)
.fontWeight(FontWeight.Bold)
TextInput({ placeholder: "姓名", text: this.formData.name })
.onChange((value: string) => {
this.formData.name = value
this.scheduleSave()
})
TextInput({ placeholder: "邮箱", text: this.formData.email })
.onChange((value: string) => {
this.formData.email = value
this.scheduleSave()
})
TextInput({ placeholder: "备注", text: this.formData.notes })
.onChange((value: string) => {
this.formData.notes = value
this.scheduleSave()
})
Text(this.isSaved ? "已保存" : "编辑中...")
.fontSize(12)
.fontColor(this.isSaved ? '#4CAF50' : '#FF9800')
}
.padding(24)
}
}
class FormData {
name: string = ""
email: string = ""
notes: string = ""
}5.6.4 3.5.4 大型应用的状态管理实践
// 大型应用状态管理架构
// 1. 状态Store设计
// 每个功能模块有独立的Store
// 用户Store
@ObservedV2
class UserStore {
@Trace currentUser: UserInfoV2 | null = null
@Trace isAuthenticated: boolean = false
@Trace permissions: string[] = []
private static instance: UserStore | null = null
static getInstance(): UserStore {
if (!UserStore.instance) {
UserStore.instance = new UserStore()
}
return UserStore.instance
}
async login(username: string, password: string): Promise<boolean> {
try {
// 调用登录API
let result: LoginResult = await this.apiLogin(username, password)
this.currentUser = result.user
this.isAuthenticated = true
this.permissions = result.permissions
// 持久化登录状态
await PreferencesManager.saveObject('auth', {
token: result.token,
userId: result.user.id
})
return true
} catch (error) {
return false
}
}
logout(): void {
this.currentUser = null
this.isAuthenticated = false
this.permissions = []
PreferencesManager.remove('auth')
}
hasPermission(permission: string): boolean {
return this.permissions.includes(permission)
}
private async apiLogin(
username: string,
password: string
): Promise<LoginResult> {
return new Promise<LoginResult>((resolve) => {
setTimeout(() => {
resolve({
user: new UserInfoV2(1, username, `${username}@example.com`),
token: "mock_token",
permissions: ["read", "write"]
})
}, 1000)
})
}
}
class UserInfoV2 {
id: number
name: string
email: string
constructor(id: number, name: string, email: string) {
this.id = id
this.name = name
this.email = email
}
}
class LoginResult {
user: UserInfoV2 = new UserInfoV2(0, "", "")
token: string = ""
permissions: string[] = []
}
// 商品Store
@ObservedV2
class ProductStore {
@Trace products: ProductV2[] = []
@Trace categories: string[] = []
@Trace selectedCategory: string = "全部"
@Trace searchKeyword: string = ""
@Trace isLoading: boolean = false
@Trace currentPage: number = 1
@Trace hasMore: boolean = true
private static instance: ProductStore | null = null
static getInstance(): ProductStore {
if (!ProductStore.instance) {
ProductStore.instance = new ProductStore()
}
return ProductStore.instance
}
async loadProducts(reset: boolean = false): Promise<void> {
if (this.isLoading) return
if (!reset && !this.hasMore) return
if (reset) {
this.products = []
this.currentPage = 1
this.hasMore = true
}
this.isLoading = true
try {
let newProducts: ProductV2[] =
await this.fetchProducts(this.currentPage)
this.products = [...this.products, ...newProducts]
this.hasMore = newProducts.length >= 20
this.currentPage++
} finally {
this.isLoading = false
}
}
get filteredProducts(): ProductV2[] {
let result: ProductV2[] = this.products
if (this.selectedCategory !== "全部") {
result = result.filter(
(p: ProductV2) => p.category === this.selectedCategory
)
}
if (this.searchKeyword.length > 0) {
result = result.filter(
(p: ProductV2) => p.name.includes(this.searchKeyword)
)
}
return result
}
private async fetchProducts(page: number): Promise<ProductV2[]> {
return new Promise<ProductV2[]>((resolve) => {
setTimeout(() => {
let items: ProductV2[] = []
for (let i: number = 0; i < 20; i++) {
let id: number = (page - 1) * 20 + i + 1
items.push(new ProductV2(
id,
`商品${id}`,
Math.floor(Math.random() * 1000) + 10,
"电子"
))
}
resolve(items)
}, 500)
})
}
}
class ProductV2 {
id: number
name: string
price: number
category: string
constructor(id: number, name: string, price: number, category: string) {
this.id = id
this.name = name
this.price = price
this.category = category
}
}
// 2. Store聚合器
class StoreManager {
private static userStore: UserStore = UserStore.getInstance()
private static productStore: ProductStore = ProductStore.getInstance()
static getUser(): UserStore {
return StoreManager.userStore
}
static getProduct(): ProductStore {
return StoreManager.productStore
}
// 全局初始化
static async initialize(context: common.Context): Promise<void> {
// 初始化持久化
await PreferencesManager.initialize(context)
// 恢复登录状态
let auth: Object =
await PreferencesManager.loadObject('auth', {})
let authRecord = auth as Record<string, Object>
if (authRecord['token']) {
StoreManager.userStore.isAuthenticated = true
}
}
// 全局清理(用户登出时)
static async cleanup(): Promise<void> {
StoreManager.userStore.logout()
StoreManager.productStore.products = []
}
}
// 3. 在页面中使用Store
@Entry
@Component
struct MainPage {
@ObjectLinkV2 userStore: UserStore = StoreManager.getUser()
@ObjectLinkV2 productStore: ProductStore = StoreManager.getProduct()
aboutToAppear(): void {
if (this.userStore.isAuthenticated) {
this.productStore.loadProducts()
}
}
build() {
Column() {
// 顶部用户信息栏
Row() {
if (this.userStore.isAuthenticated && this.userStore.currentUser) {
Text(`欢迎, ${this.userStore.currentUser.name}`)
.fontSize(16)
Button("退出")
.fontSize(12)
.onClick(() => { StoreManager.cleanup() })
} else {
Text("未登录")
.fontSize(16)
Button("登录")
.fontSize(12)
.onClick(() => {
this.userStore.login("testuser", "password")
})
}
}
.width('100%')
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
// 搜索和分类
Row() {
TextInput({ placeholder: "搜索商品..." })
.layoutWeight(1)
.onChange((value: string) => {
this.productStore.searchKeyword = value
})
}
.padding({ left: 16, right: 16 })
// 商品列表
List() {
ForEach(this.productStore.filteredProducts,
(product: ProductV2) => {
ListItem() {
Row() {
Text(product.name)
.layoutWeight(1)
Text(`¥${product.price}`)
.fontColor('#FF4444')
}
.padding(16)
}
},
(product: ProductV2) => product.id.toString()
)
}
.layoutWeight(1)
.onReachEnd(() => {
// 滚动到底部加载更多
this.productStore.loadProducts()
})
// 加载指示器
if (this.productStore.isLoading) {
LoadingProgress()
.width(40)
.height(40)
}
}
}
}5.7 本章小结
本章全面介绍了HarmonyOS高级状态管理的核心知识:
状态管理V2:理解了V2相比V1的改进(更精确的观察粒度、更简洁的API、更好的性能),掌握了@ObservedV2、@ObjectLinkV2、@Local、@Param等新装饰器的使用方法和迁移策略。
全局状态管理:掌握了AppStorage(应用级)、LocalStorage(页面级)、PersistentStorage(持久化)和Environment(环境变量)四种全局状态方案的使用场景和最佳实践。
组件间状态共享:深入理解了@State/@Prop/@Link的数据流方向,掌握了@Provide/@Consume跨层级通信和@Watch状态监听的实现方式,了解了状态管理对性能的影响。
响应式数据流:理解了数据劫持(Object.defineProperty和Proxy)的底层原理,掌握了依赖收集和触发更新的机制,学习了计算属性和批处理技术。
状态管理架构:掌握了状态分层策略(UI状态、业务状态、应用状态),了解了状态管理规范和约束,学习了多种持久化方案和大型应用的状态管理实践。
5.8 练习题
5.8.1 一、单选题
1. 在状态管理V2中,用于标记需要被追踪的类属性的装饰器是?
A. @ObservedV2 B. @Trace C. @ObjectLinkV2 D. @Local
答案:B
解析: 在V2状态管理中,@ObservedV2用于标记类使其具备观察能力,而@Trace用于标记类中需要被追踪的具体属性。只有被@Trace标记的属性变化才会触发UI更新。@ObjectLinkV2用于在子组件中接收@ObservedV2对象,@Local用于组件内部本地状态。
2. 以下关于@Provide/@Consume的描述,正确的是?
A. @Provide只能向直接子组件传递数据 B. @Consume可以修改@Provide的值并同步回祖先组件 C. @Provide/@Consume可以跨越多层组件传递状态 D. @Consume必须提供默认值
答案:C
解析: @Provide/@Consume的核心优势是可以跨越多层组件直接传递状态,不需要逐层通过@Prop/@Link传递。@Provide标记的值可以被任意深度的后代组件通过@Consume获取。@Consume获取的值是可以修改的(双向绑定)。
3. AppStorage和LocalStorage的主要区别是?
A. AppStorage用于组件内部,LocalStorage用于全局 B. AppStorage是应用级全局存储,LocalStorage是页面级存储 C. AppStorage支持持久化,LocalStorage不支持 D. 没有区别,只是名称不同
答案:B
解析: AppStorage是应用级别的全局状态存储,所有页面和组件都可以访问;LocalStorage是页面(UIAbility)级别的存储,每个页面有独立的LocalStorage实例。两者都不直接提供持久化能力,持久化需要配合PersistentStorage使用。
5.8.2 二、多选题
4. 以下哪些装饰器属于状态管理V2的新特性?
A. @ObservedV2 B. @ObjectLinkV2 C. @Local D. @Monitor
答案:A、B、C、D
解析: 以上四个装饰器都是V2状态管理引入的新特性。@ObservedV2替代@Observed,@ObjectLinkV2替代@ObjectLink,@Local替代@State(用于组件内部状态),@Monitor替代@Watch(支持监听多个属性)。
5. 以下关于PersistentStorage的描述,正确的有哪些?
A. 数据会在应用重启后保留 B. 通过persistProp方法配置需要持久化的属性 C. 只能存储基本数据类型(string、number、boolean) D. 配合@StorageLink使用可以实现自动持久化
答案:A、B、D
解析: PersistentStorage提供数据持久化能力,数据在应用重启后仍然保留。通过persistProp方法配置哪些属性需要持久化,配合@StorageLink可以实现修改自动持久化。它支持string、number、boolean等基本类型,不直接支持复杂对象的持久化。
5.8.3 三、判断题
6. @Prop装饰器实现的是父子组件之间的双向数据绑定。
答案:错误
解析: @Prop实现的是父到子的单向数据流。子组件可以修改@Prop值的本地副本,但修改不会同步回父组件。如果需要双向绑定,应使用@Link装饰器(V1)或@Event装饰器(V2)。
7. 在同一个事件处理函数中多次修改@State状态,只会触发一次UI重渲染。
答案:正确
解析: HarmonyOS框架会自动对同一事件处理函数(同步代码块)中的多次状态修改进行批处理,合并为一次UI更新。这避免了频繁的状态修改导致的多次重渲染,提升了性能。但异步操作(await之后)的修改会触发新的渲染周期。
8. @ObservedV2标记的类,其所有属性变化都会被自动追踪,无需额外装饰器。
答案:错误
解析: @ObservedV2只标记类具备观察能力,但具体哪些属性需要被追踪,需要使用@Trace装饰器显式标记。只有被@Trace标记的属性变化才会触发依赖该属性的UI组件重渲染。这种设计提供了更精确的控制,避免不必要的更新。