Skip to content

状态管理

状态管理是 ArkUI 的核心概念。当状态变量变化时,UI 会自动刷新。ArkUI 提供了丰富的状态管理装饰器,帮助开发者在不同场景下高效管理组件状态和全局状态。

本文档系统介绍 ArkUI 状态管理的完整知识体系,包括 V1 状态管理、V2 状态管理(API 12+)、全局状态管理以及最佳实践。

V1 状态管理

V1 状态管理是 ArkUI 早期版本提供的状态管理体系,适用于 API 9~API 11 的项目,在 API 12+ 中仍然兼容可用。

@State(组件内部状态)

@State 是组件内部的状态装饰器,用于声明组件的私有状态。被 @State 装饰的变量必须初始化,修改后会自动触发 UI 刷新。

基本用法

typescript
@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 支持以下类型的状态变量:

类型说明示例
简单类型numberstringbooleanenum@State count: number = 0
对象类型自定义类实例(需配合 @Observed@State person: Person = new Person()
数组类型数组整体赋值可观测@State list: number[] = [1, 2, 3]
Date 类型Date 对象@State date: Date = new Date()

使用限制

  1. 必须初始化@State 装饰的变量必须在声明时或构造函数中初始化
  2. 仅当前组件可用:状态变量仅在当前组件内有效,子组件无法直接访问
  3. 不支持从外部传入@State 变量不能通过组件参数从父组件传入
  4. 数组元素修改限制:直接修改数组元素(如 this.arr[0] = 1)不会触发刷新,需要使用数组 API(pushsplice 等)或整体赋值
typescript
@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 用于父组件向子组件单向传递数据。父组件状态变化会同步到子组件,但子组件修改该变量不会影响父组件(值拷贝机制)。

基本用法

typescript
// 子组件
@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:父传子基础用法

typescript
@Component
struct UserCard {
  @Prop name: string
  @Prop age: number

  build() {
    Column() {
      Text(`姓名: ${this.name}`)
      Text(`年龄: ${this.age}`)
    }
  }
}

// 使用
UserCard({ name: '张三', age: 25 })

场景 2:传递对象属性

typescript
class UserInfo {
  name: string = ''
  avatar: string = ''
}

@Component
struct UserProfile {
  @Prop user: UserInfo  // 传递对象

  build() {
    Column() {
      Text(this.user.name)
      Image(this.user.avatar)
    }
  }
}

场景 3:配合 $ 语法传递(数组元素)

typescript
@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)
  }
}

使用限制

  1. 深拷贝机制@Prop 采用深拷贝,频繁传递大对象会影响性能
  2. 同步方向:只能从父到子单向同步,子组件修改不会回传
  3. 必须声明类型@Prop 变量必须显式声明类型
  4. 不可与 @State 混用同一变量:父组件用 @State,子组件用 @Prop 接收

@Link 用于父子组件之间的双向数据绑定。父子组件共享同一个数据源,任意一方修改都会同步到另一方。

基本用法

typescript
// 子组件
@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:父子组件共享计数器

typescript
@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:表单双向绑定

typescript
@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:数组元素双向绑定

typescript
@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] })  // 数组元素双向绑定
        }
      })
    }
  }
}

使用限制

  1. 必须使用 $ 传递:父组件传递 @Link 变量时必须使用 $variable 语法
  2. 不能独立初始化@Link 变量不能设置默认值,必须从父组件传入
  3. 类型必须匹配:父子组件的变量类型必须一致
  4. 不能用于深层嵌套对象@Link 主要用于简单类型和数组,复杂对象建议使用 @ObjectLink

@Provide / @Consume(跨层级共享)

@Provide@Consume 用于跨越多层组件共享状态,无需逐层传递。祖先组件通过 @Provide 提供状态,后代组件通过 @Consume 消费状态。

基本用法

typescript
// 祖先组件提供状态
@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:主题系统

typescript
// 主题配置类
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:用户信息全局共享

typescript
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:多层级导航状态

typescript
@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
    })
  }
}

使用限制

  1. 别名匹配@Provide('alias')@Consume('alias') 必须通过相同的别名匹配
  2. 双向同步@Consume 修改数据会同步到 @Provide 和所有其他 @Consume
  3. 作用域限制@Consume 只能在其组件树的祖先中找到匹配的 @Provide
  4. 不支持跨页面@Provide/@Consume 仅在单个页面内有效,跨页面请使用 AppStorage

对于嵌套对象和数组,需要使用 @Observed@ObjectLink 实现深度响应式。@Observed 装饰类,使类的实例具有响应式能力;@ObjectLink 装饰变量,用于在子组件中引用被 @Observed 装饰的类的实例。

基本用法

typescript
// 定义数据模型
@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:嵌套对象响应式

typescript
@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:对象数组深度响应

typescript
@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:继承类场景

typescript
@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}`)
      }
    }
  }
}

使用限制

  1. 必须配合使用@ObjectLink 必须配合 @Observed 使用
  2. 不能独立初始化@ObjectLink 变量不能设置默认值,必须从父组件传入被 @Observed 装饰的实例
  3. 仅用于子组件@ObjectLink 只能用于自定义组件内部,不能用于 @Entry 组件
  4. 不支持简单类型@ObjectLink 只能装饰 @Observed 装饰的类的实例
  5. 数组整体赋值@State 装饰的数组整体赋值会触发刷新,但数组内对象属性修改需要通过 @ObjectLink

@Watch(状态监听)

@Watch 用于监听状态变量的变化,当状态变量改变时触发指定的回调方法。

基本用法

typescript
@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:表单验证

typescript
@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:联动效果

typescript
@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:数据持久化

typescript
@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')
    }
  }
}

使用限制

  1. 回调方法名@Watch('methodName') 中的方法名必须是当前组件中定义的方法
  2. 不监听初始化@Watch 不会在变量初始化时触发,只在值变化时触发
  3. 同步执行:回调方法同步执行,避免在回调中执行耗时操作
  4. 不能修改自身:在 @Watch 回调中修改被监听的变量可能导致无限循环
  5. 可监听多个变量:一个方法可以被多个 @Watch 装饰器引用

$$ 语法(双向绑定语法糖)

$$ 语法是 ArkUI 提供的双向绑定语法糖,用于简化表单组件的双向绑定。它适用于 TextInputTextAreaSliderToggleCheckbox 等组件。

基本用法

typescript
@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)
  }
}

支持的组件

组件绑定属性说明
TextInputtext输入框文本
TextAreatext多行输入框文本
Slidervalue滑块值
ToggleisOn开关状态
Checkboxselect选中状态
Radiochecked选中状态
Ratingrating评分值
DatePickerselected选中日期
TimePickerselected选中时间

使用限制

  1. 仅支持 @State@Link$$ 语法只能绑定 @State@Link 装饰的变量
  2. 类型必须匹配:组件属性类型必须与变量类型一致
  3. 不适用于自定义组件$$ 语法仅适用于系统组件

@StorageLink@StorageProp 用于将组件状态与 AppStorage 中的应用级存储进行绑定,实现跨页面状态共享。

typescript
// 在 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(单向绑定)

typescript
@Entry
@Component
struct ReadOnlyPage {
  // 单向绑定:只能读取,不能通过此变量修改 AppStorage
  @StorageProp('appVersion') version: string = '1.0.0'

  build() {
    Column() {
      Text(`应用版本: ${this.version}`)
      // 修改不会同步到 AppStorage
      // this.version = '2.0.0'  // ❌ 不推荐
    }
  }
}

使用限制

  1. 必须初始化 AppStorage:使用 @StorageLink/@StorageProp 前必须在 AppStorage 中创建对应的属性
  2. 默认值仅在未初始化时生效:如果 AppStorage 中已有该属性,组件中的默认值会被忽略
  3. 类型一致性:组件中声明的类型必须与 AppStorage 中的类型一致
  4. 对象类型限制@StorageLink 绑定对象类型时,对象的属性修改不会触发刷新(需要整体赋值)

@LocalStorageLink@LocalStorageProp 用于页面级的状态存储,作用范围限定在当前页面及其子组件。

基本用法

typescript
// 创建 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:页面内多组件共享状态

typescript
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))
      })
  }
}

使用限制

  1. 页面级作用域LocalStorage 仅在当前页面有效,页面销毁后数据丢失
  2. 需要注入:必须在 @Entry 装饰器中注入 LocalStorage 实例
  3. 子组件自动继承:子组件无需额外注入,可直接使用 @LocalStorageLink/@LocalStorageProp

V2 状态管理(API 12+)

V2 状态管理是 ArkUI 在 API 12 推出的新一代状态管理体系,提供了更统一、更简洁、更高效的开发体验。新项目强烈推荐使用 V2。

注意

V1 和 V2 装饰器不能混用。使用 @ComponentV2 的组件内部只能使用 V2 装饰器。

@ObservedV2 和 @Trace

@ObservedV2@Trace 是 V2 中实现对象深度观测的核心装饰器。@ObservedV2 装饰类,使类具有响应式能力;@Trace 装饰类的属性,使属性变化时触发 UI 刷新。

基本用法

typescript
@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:嵌套类深度观测

typescript
@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:数组元素观测

typescript
@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:继承类观测

typescript
@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

类型可观测操作
numberstringboolean赋值操作
Arraypushpopshiftunshiftsplicesortreverse、索引赋值
Mapsetdeleteclear
Setadddeleteclear
DatesetXxx 系列方法
嵌套 @ObservedV2属性赋值

使用限制

  1. 必须配合使用@Trace 只能在 @ObservedV2 装饰的类中使用
  2. 不支持 JSON 序列化@ObservedV2 的类实例目前不支持 JSON.stringify
  3. 类型限制@Trace 不支持 undefinednull 和联合类型
  4. 方法装饰限制@Trace 只能装饰属性,不能装饰方法

@ComponentV2

@ComponentV2 是 V2 中定义自定义组件的装饰器,替代 V1 中的 @Component。使用 @ComponentV2 的组件内部只能使用 V2 状态管理装饰器。

基本用法

typescript
@ComponentV2
struct MyComponent {
  @Local count: number = 0

  build() {
    Column() {
      Text(`${this.count}`)
      Button('增加')
        .onClick(() => this.count++)
    }
  }
}

@Entry
@ComponentV2
struct Page {
  build() {
    MyComponent()
  }
}

特性说明

  1. 冻结功能@ComponentV2 支持 freezeWhenInactive 属性,组件不可见时自动冻结,优化性能
  2. 类型安全:V2 装饰器必须显式声明类型
  3. 不支持组件复用@ComponentV2 暂不支持 @Reusable 组件复用
typescript
@ComponentV2({ freezeWhenInactive: true })
struct OptimizedComponent {
  @Local data: string = ''

  build() {
    Text(this.data)
  }
}

@Local(替代 @State)

@Local 是 V2 中用于声明组件内部状态的装饰器,功能类似于 V1 的 @State

基本用法

typescript
@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
类型声明推荐显式声明必须显式声明

使用限制

  1. 必须初始化@Local 变量必须在声明时初始化
  2. 无法从外部传入@Local 变量不能通过组件参数传入
  3. 必须声明类型:API 12+ 要求 V2 装饰器必须显式声明类型

@Param(替代 @Prop)

@Param 是 V2 中用于父组件向子组件传递参数的装饰器,功能类似于 V1 的 @Prop

基本用法

typescript
@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:基础参数传递

typescript
@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:对象参数传递

typescript
@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:数组参数传递

typescript
@ComponentV2
struct TagList {
  @Param tags: string[] = []

  build() {
    Row() {
      ForEach(this.tags, (tag: string) => {
        Text(tag)
          .padding(8)
          .backgroundColor('#F0F0F0')
          .borderRadius(4)
      })
    }
  }
}

使用限制

  1. 单向同步@Param 只能从父到子单向同步,子组件修改不会影响父组件
  2. 本地修改限制@Param 变量在子组件中不能直接修改(需配合 @Event
  3. 必须声明类型:必须显式声明类型
  4. 支持本地初始化:可以设置默认值,但外部传入时会覆盖

@Event(事件回调)

@Event 是 V2 中用于子组件向父组件传递事件的装饰器,替代 V1 中通过回调函数传递的方式。

基本用法

typescript
@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:基础事件传递

typescript
@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:带参数的事件

typescript
@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:多事件传递

typescript
@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')
    }
  }
}

使用限制

  1. 只能装饰函数类型@Event 只能装饰函数类型的变量
  2. 必须初始化:需要设置默认空函数
  3. 箭头函数保持 this:使用箭头函数确保 this 指向正确
  4. 不能用于返回值@Event 不适用于需要返回值的场景

@Once(单次同步)

@Once 用于装饰仅在初始化时同步一次的参数。与 @Param 配合使用,父组件后续修改不会同步到子组件。

基本用法

typescript
@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 = '李四'
        })
    }
  }
}

使用限制

  1. 必须配合 @Param@Once 只能与 @Param 一起使用
  2. 初始化后不同步:父组件后续修改不会同步到子组件
  3. 子组件可本地修改@Once @Param 变量可以在子组件中本地修改

@Provider / @Consumer(替代 @Provide/@Consume)

@Provider@Consumer 是 V2 中用于跨组件层级共享状态的装饰器,替代 V1 的 @Provide/@Consume

基本用法

typescript
@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:主题系统

typescript
@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:用户状态共享

typescript
@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 = '张三'
        }
      })
  }
}

使用限制

  1. 别名匹配@Provider('alias')@Consumer('alias') 通过别名匹配
  2. 默认值@Consumer 可以设置默认值,当找不到对应的 @Provider 时使用默认值
  3. 双向同步@Consumer 修改数据会同步到 @Provider 和所有其他 @Consumer
  4. 不能修饰 class 属性@Provider/@Consumer 只能修饰组件内的属性

@Monitor(状态监听替代 @Watch)

@Monitor 是 V2 中用于监听状态变量变化的装饰器,替代 V1 的 @Watch。支持深度监听和监听多个属性。

基本用法

typescript
@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:监听多个属性

typescript
@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:深度监听嵌套对象

typescript
@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 类中使用

typescript
@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 接口说明

typescript
interface IMonitor {
  dirty: string[]                    // 发生变化的属性名数组
  value(path?: string): IMonitorValue | undefined  // 获取指定路径的变化值
}

interface IMonitorValue<T = Object> {
  before: T   // 变化前的值
  now: T      // 变化后的值
  path: string // 监听的属性路径
}

使用限制

  1. 路径格式:监听嵌套属性时使用点号路径,如 'person.address.city'
  2. 单次事件合并:多个属性在一次事件中同时变化时,只会触发一次回调
  3. 继承场景:父子组件可以对同一属性分别定义 @Monitor,都会触发
  4. 未装饰属性无法监听:未使用 @ObservedV2@Trace 的属性变化无法被监听

@Computed(计算属性)

@Computed 是 V2 中用于声明计算属性的装饰器。计算属性会根据依赖的状态自动重新计算,并缓存结果,避免重复计算。

基本用法

typescript
@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:派生数据计算

typescript
@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:过滤和排序

typescript
@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:格式化显示

typescript
@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())
    }
  }
}

使用限制

  1. 只能用于 getter@Computed 只能装饰 getter 方法
  2. 依赖追踪:自动追踪 getter 中访问的状态变量,依赖变化时自动重新计算
  3. 缓存机制:依赖未变化时直接返回缓存结果,避免重复计算
  4. 不能有副作用@Computed getter 中不应有副作用(如修改其他状态)

全局状态管理

全局状态管理用于在应用级别或页面级别共享状态,实现跨组件、跨页面的数据同步。

AppStorage(应用级存储)

AppStorage 是应用级别的全局状态存储,所有页面共享同一个 AppStorage 实例。应用启动时创建,应用销毁时释放。

基本用法

typescript
// 在 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')
  }
}

在组件中使用

typescript
@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 方法

typescript
// 设置或创建属性
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)
})

使用限制

  1. 进程级共享AppStorage 仅在当前应用进程内共享
  2. 不持久化:应用退出后数据丢失,需要配合 PersistentStorage 实现持久化
  3. 类型安全:建议统一封装 AppStorage 的读写操作,避免类型错误

LocalStorage(页面级存储)

LocalStorage 是页面级的状态存储,用于在页面内部及其组件之间共享状态。可以在 UIAbility 内页面间共享状态。

基本用法

typescript
// 创建 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 中共享

typescript
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)
  }
}

使用限制

  1. 页面级作用域LocalStorage 仅在当前页面及其子组件中有效
  2. 需要注入:必须在 @EntrywindowStage.loadContent 中注入
  3. 生命周期:页面销毁时 LocalStorage 数据丢失

PersistentStorage(持久化存储)

PersistentStorage 用于将选定的 AppStorage 属性持久化到设备磁盘上,应用关闭后仍然保持。

基本用法

typescript
// 在 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')
  }
}

在组件中使用

typescript
@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'
        })
    }
  }
}

使用限制

  1. 仅支持简单类型PersistentStorage 仅支持 numberstringboolean 等简单类型
  2. 对象类型限制:对象类型需要先序列化为字符串再存储
  3. 异步写入:数据写入磁盘是异步的,极端情况下可能丢失最后一次修改
  4. 容量限制:持久化数据有容量限制,不宜存储大量数据
typescript
// 对象类型的持久化处理
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 刷新。

基本用法

typescript
@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'}`)
    }
  }
}

支持的环境变量

属性名类型说明
languageCodestring系统语言代码,如 'zh'、'en'
colorModeColorMode颜色模式,ColorMode.LIGHTColorMode.DARK
fontScalenumber字体缩放比例
layoutDirectionLayoutDirection布局方向,LayoutDirection.LTRLayoutDirection.RTL
screenDensitynumber屏幕密度
fontWeightScalenumber字重缩放比例

使用限制

  1. 只读属性Environment 变量是只读的,不能通过组件修改
  2. 系统触发更新:环境变量变化时由系统自动触发 UI 刷新
  3. 使用 @StorageProp:环境变量通过 @StorageProp 绑定(单向)

状态管理最佳实践

状态提升模式

当多个兄弟组件需要共享状态时,将状态提升到它们的共同父组件中管理。

typescript
// 反例:状态分散在子组件中,无法同步
@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++)
  }
}

状态下沉模式

将状态尽量靠近使用它的组件,避免不必要的组件刷新。

typescript
// 反例:所有状态都在父组件,导致整个页面刷新
@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)
  }
}

单一数据源原则

每个状态应该有且只有一个数据源,避免多个状态表示相同的数据。

typescript
// 反例:多个状态表示相同数据
@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. 避免在循环中访问状态变量

typescript
// 反例:循环中频繁读取状态变量
@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}`)
    })
  }
}
typescript
// 反例:使用 @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

typescript
// 反例:频繁变化的属性使用 @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

typescript
// 反例:不必要的 @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 变量未初始化

typescript
// ❌ 错误
@Entry
@Component
struct BadExample {
  @State count: number  // 未初始化
}

// ✅ 正确
@Entry
@Component
struct GoodExample {
  @State count: number = 0  // 必须初始化
}
typescript
// ❌ 错误
@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 期望同步到父组件

typescript
// ❌ 错误理解
@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 组件

typescript
// ❌ 错误
@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 混用

typescript
// ❌ 错误
@ComponentV2
struct BadExample {
  @State count: number = 0  // V1 装饰器不能在 @ComponentV2 中使用
}

// ✅ 正确
@ComponentV2
struct GoodExample {
  @Local count: number = 0  // 使用 V2 装饰器
}

错误 6:@ObservedV2 类未使用 @Trace

typescript
// ❌ 错误
@ObservedV2
class BadModel {
  name: string = ''  // 未使用 @Trace,属性变化不会触发刷新
}

// ✅ 正确
@ObservedV2
class GoodModel {
  @Trace name: string = ''  // 使用 @Trace,属性变化触发刷新
}

错误 7:PersistentStorage 存储对象类型

typescript
// ❌ 错误
PersistentStorage.persistProp('user', { name: '张三' })  // 对象类型不支持

// ✅ 正确
PersistentStorage.persistProp('user', JSON.stringify({ name: '张三' }))

// 读取时
const userJson = AppStorage.get('user') as string
const user = JSON.parse(userJson)

错误 8:在 @Watch 中修改被监听的变量

typescript
// ❌ 错误:可能导致无限循环
@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+ 推荐,更统一、更简洁、性能更好
  • 全局状态管理AppStorageLocalStoragePersistentStorageEnvironment 覆盖不同层级的状态共享需求
  • 最佳实践:状态提升、状态下沉、单一数据源、性能优化等模式帮助构建可维护的应用

建议新项目优先使用 V2 状态管理,老项目可以逐步迁移。无论使用哪个版本,遵循最佳实践都能帮助你构建高性能、可维护的 ArkUI 应用。