ArkTS 状态管理装饰器详解

265 阅读3分钟

ArkTS 状态管理装饰器详解

在 ArkTS 中,UI 的刷新是由状态数据驱动的。当状态数据发生变化时,框架会识别到变化并自动更新相应的 UI 界面。为了实现这种响应式的 UI 开发,ArkTS 提供了一系列强大的装饰器。本文档将详细介绍 @State@Provide@Consume@Link@Prop 等核心装饰器的作用和使用方法。

目录

  1. 核心概念

  2. @State: 组件内部的状态

  3. @Prop: 从父组件单向同步数据

  4. @Link: 与父组件双向同步数据

  5. @Provide / @Consume: 跨组件层级共享数据

  6. 其他常用装饰器

  7. 总结与对比


核心概念

在深入了解每个装饰器之前,需要理解 ArkTS 状态管理的基本思想:

  • 数据驱动视图:UI 的显示内容完全由状态(State)数据决定。
  • 声明式 UI:开发者只需要声明 UI 的最终状态,而不需要关心如何从旧状态过渡到新状态的繁琐过程。
  • 单一数据源:每个状态数据都应该有其唯一的“所有者”(Source of Truth)。其他地方要么是单向接收,要么是双向同步,以避免数据混乱。

@State: 组件内部的状态

@State 是最基础也是最重要的装饰器,用于定义组件内部私有的、可变的状态。

  • 作用:

    • 标记一个变量为状态变量。
    • 当该变量的值发生改变时,会自动触发 UI 的重新渲染,以更新与该变量相关的视图。
    • @State 变量是其所属组件的“数据所有者”。
  • 特点:

    • 私有: 只能在组件内部访问和修改。
    • 可变: 其值可以被改变。
    • 生命周期与组件同步: 在组件创建时初始化,在组件销毁时销毁。
  • 如何使用:

TypeScript

// @/main/ets/components/MyComponent.ets

@Component
struct MyComponent {
  @State count: number = 0; // 使用 @State 装饰一个变量

  build() {
    Column() {
      Text(`点击次数: ${this.count}`)
        .fontSize(24)

      Button('点击我')
        .onClick(() => {
          // 直接修改 @State 变量的值,UI 会自动更新
          this.count++;
        })
    }
    .width('100%')
    .padding(20)
  }
}

在上面的例子中,countMyComponent 的私有状态。每次点击按钮,count 的值增加,Text 组件显示的内容也会自动更新。

@Prop: 从父组件单向同步数据

@Prop 用于组件接收来自其父组件的单向数据。

  • 作用:

    • 允许父组件将数据传递给子组件。
    • 当父组件中对应的数据源(通常是 @State 变量)发生变化时,@Prop 变量的值会同步更新,并触发子组件的 UI 刷新。
  • 特点:

    • 单向数据流: 数据从父组件流向子组件。子组件不应该在内部直接修改 @Prop 变量的值。
    • 只读性: 虽然技术上可以修改,但这违背了设计原则,可能会导致应用状态不可预测。
  • 如何使用:

TypeScript

// 子组件: Profile.ets
@Component
struct Profile {
  @Prop username: string; // 使用 @Prop 接收父组件的数据

  build() {
    Text(`欢迎, ${this.username}!`)
      .fontSize(20)
  }
}

// 父组件: UserPage.ets
@Component
struct UserPage {
  @State currentUsername: string = "张三";

  build() {
    Column({ space: 10 }) {
      // 将父组件的 @State 变量传递给子组件的 @Prop 变量
      Profile({ username: this.currentUsername })

      Button("切换用户")
        .onClick(() => {
          this.currentUsername = "李四"; // 父组件状态改变
        })
    }
  }
}

当父组件 UserPage 中的 currentUsername 改变时,子组件 Profileusername 会自动更新,UI 也随之刷新。

@Link: 与父组件双向同步数据

@Link 实现了父子组件之间的双向数据同步。

  • 作用:

    • 允许子组件读取并修改从父组件接收的数据。
    • 当子组件修改了 @Link 变量,这个修改会同步回父组件的源数据,反之亦然。
  • 特点:

    • 双向绑定: 父子组件共享数据状态。
    • 需要 $ 符号: 在父组件中传递 @State 变量时,需要在变量名前加上 $ 符号,以传递变量的引用。
  • 如何使用:

TypeScript

// 子组件: TextInput.ets
@Component
struct TextInput {
  @Link textValue: string; // 使用 @Link 创建双向绑定
  label: string = "输入内容";

  build() {
    Column() {
      Text(this.label)
      TextInput({ text: this.textValue })
        .onChange((value: string) => {
          // 子组件修改数据,会同步到父组件
          this.textValue = value;
        })
    }
  }
}

// 父组件: HomePage.ets
@Component
struct HomePage {
  @State message: string = "你好";

  build() {
    Column({ space: 20 }) {
      Text(`父组件显示的消息: ${this.message}`)

      // 使用 `$` 将 @State 变量的引用传递给 @Link
      TextInput({ textValue: $message, label: '编辑消息' })
    }
    .padding(20)
  }
}

HomePage 中,TextInput 子组件对 textValue 的任何修改都会立刻反映在 HomePagemessage 状态上,并更新 Text 的显示。

@Provide / @Consume: 跨组件层级共享数据

当数据需要在多个层级或没有直接父子关系的组件之间共享时,逐层传递 @Prop@Link 会非常繁琐。@Provide@Consume 提供了一种“依赖注入”的解决方案。

  • 作用:

    • @Provide: 在祖先组件中“提供”一个可被其所有后代组件访问的数据。
    • @Consume: 在后代组件中“消费”(即订阅和访问)这个被提供的数据。
  • 特点:

    • 跨层级: 无需手动逐层传递。
    • 别名可选: 可以为 Provide 的数据指定一个别名,Consume 时使用该别名。
    • 双向绑定: @Provide@Consume 之间是双向绑定的。任何一方的修改都会同步给另一方。
  • 如何使用:

TypeScript

// 祖先组件: AppRoot.ets
@Component
struct AppRoot {
  // 在顶层组件提供一个值
  @Provide('appThemeColor') themeColor: string = '#FFFFFF';

  build() {
    Column() {
      // ... 其他组件
      Level1()
    }
    .width('100%')
    .height('100%')
    .backgroundColor(this.themeColor) // 自己也可以使用
  }
}

// 中间层组件: Level1.ets
@Component
struct Level1 {
  build() {
    Level2() // 直接渲染下一层,无需传递 props
  }
}

// 后代组件: ThemedButton.ets
@Component
struct ThemedButton {
  // 通过别名 'appThemeColor' 消费数据
  @Consume('appThemeColor') color: string;

  build() {
    Button('变色按钮')
      .fontColor(this.color === '#FFFFFF' ? '#000000' : '#FFFFFF')
      .backgroundColor(this.color)
      .onClick(() => {
        // 修改 @Consume 变量会同步回 @Provide 的源头
        this.color = this.color === '#FFFFFF' ? '#DDDDDD' : '#FFFFFF';
      })
  }
}

ThemedButton 无论嵌套多深,都可以通过 @Consume 直接获取并修改 AppRoot@ProvidethemeColor

其他常用装饰器

@Observed 与 @ObjectLink

这对组合用于处理对象(class)的响应式。

  • @Observed: 标记一个类,当这个类的属性发生变化时,框架能够监听到。
  • @ObjectLink: 在组件中持有并监听一个 @Observed 标记过的类实例。

TypeScript

// 1. 定义一个可被观察的类
@Observed
class UserProfile {
  public name: string;
  public age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 2. 在组件中使用
@Component
struct ProfileView {
  // @ObjectLink 用于链接到 @Observed 对象
  @ObjectLink user: UserProfile;

  build() {
    Column({ space: 10 }) {
      Text(`姓名: ${this.user.name}`)
      Text(`年龄: ${this.user.age}`)
      Button('年龄+1')
        .onClick(() => {
          this.user.age++; // 直接修改对象的属性,UI 会自动更新
        })
    }
  }
}

// 3. 父组件中创建实例并传递
@Component
struct ParentComponent {
  // 数据源头是 @State
  @State userProfile: UserProfile = new UserProfile("王五", 30);

  build() {
    ProfileView({ user: this.userProfile })
  }
}

@StorageProp 与 @StorageLink

用于将组件状态与应用级的 AppStorageLocalStorage 进行绑定。

  • AppStorage: 应用内存中的全局单例存储,生命周期与应用进程相同。
  • LocalStorage: 设备持久化存储,应用退出后数据依然存在。
  • @StorageProp(key) : 单向绑定。当 Storagekey 对应的值改变时,更新组件状态。
  • @StorageLink(key) : 双向绑定。组件内对变量的修改会同步回 Storage,反之亦然。

TypeScript

// 在应用启动时初始化 AppStorage
AppStorage.SetOrCreate('isUserLoggedIn', false);

@Component
struct AuthStatusView {
  // 单向订阅 AppStorage 中的 'isUserLoggedIn'
  @StorageProp('isUserLoggedIn') isLoggedIn: boolean = false;

  build() {
    Text(this.isLoggedIn ? '用户已登录' : '用户未登录')
  }
}

@Component
struct LoginPage {
  // 双向绑定 'isUserLoggedIn'
  @StorageLink('isUserLoggedIn') loginStatus: boolean;

  build() {
    Button(this.loginStatus ? '退出登录' : '点击登录')
      .onClick(() => {
        // 修改此变量会直接更新 AppStorage,并通知所有订阅者
        this.loginStatus = !this.loginStatus;
      })
  }
}

总结与对比

装饰器数据所有权数据流主要使用场景关键符号
@State组件内部-管理组件自身的、私有的状态。-
@Prop父组件单向 (父 -> 子)子组件接收来自父组件的不可变数据。-
@Link父子共享双向 (父 <-> 子)子组件需要修改父组件的状态。父组件传递时使用 $
@Provide祖先组件双向跨多层级组件共享数据。-
@Consume祖先组件双向订阅和使用祖先组件提供的数据。-
@ObjectLink父组件双向在组件中引用和观察一个 @Observed 的类实例。-
@StorageLinkStorage双向将组件状态与全局或持久化存储双向绑定。key
@StoragePropStorage单向单向订阅全局或持久化存储中的数据。key