鸿蒙装饰器

Posted on By ᵇᵒ

鸿蒙的 ArkUI 是 声明式 UI 框架,使用装饰器改变变量行为。

状态管理

@State

组件内部状态,用于定义组件私有状态,状态变化会自动触发 UI 刷新。
只属于当前组件,子组件不能直接修改。
@State 装饰的属性必须要设置初始值。

@Component
export struct Foo {
  @State count: number = 0

  build() {
    Column() {
      Button("Add")
        .onClick(() => {
          this.count++
        })
      Text(this.count.toString())
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

父子双向绑定,子组件引用父组件状态,并且可以修改父组件状态。

@Component
export struct Parent {
  @State count: number = 0

  build() {
    Column() {
      Button("parent add")
        .onClick(() => {
          this.count++
        })
      Text("parent count:" + this.count)
      Child({ count: this.count })
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

@Component
export struct Child {
  @Link count: number

  build() {
    Column() {
      Button("child Add")
        .onClick(() => {
          this.count++
        })
      Text("child count:" + this.count)
    }
  }
}

@Prop

父组件传入只读数据,子组件修改不影响父组件状态(更加正确的做法是子组件不要去修改,修改 @Prop 数据有点违反设计初衷)。

@Component
export struct Parent {
  @State count: number = 0

  build() {
    Column() {
      Button("parent add")
        .onClick(() => {
          this.count++
        })
      Text("parent count:" + this.count)
      Child({ count: this.count })
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

@Component
export struct Child {
  @Prop count: number

  build() {
    Column() {
      Button("child Add")
        .onClick(() => {
          this.count++
        })
      Text("child count:" + this.count)
    }
  }
}

@Provide @Consume

@Provide 向任意层级的子孙组件提供数据,需要子孙组件搭配 @Consume 来消费 Provide 数据(构造子孙组件不再需要传递该数据)。

@State + @Link 仅用于父子组件双向绑定数据,@Provide + @Consume 可以用于父组件与任意层级子孙组件之间传递数据。

@Component
export struct Parent {
  @Provide count: number = 0

  build() {
    Column() {
      Button("parent add")
        .onClick(() => {
          this.count++
        })
      Text("parent count:" + this.count)
      Child()
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }
}

@Component
export struct Child {
  @Consume count: number

  build() {
    Column() {
      Button("child Add")
        .onClick(() => {
          this.count++
        })
      Text("child count:" + this.count)
    }
  }
}

@Watch

监听状态变化

@Component
export struct Foo {
  // 当 count 改变时,会调用 onCountChange
  @State @Watch("onCountChange") count: number = 0

  build() {
    Column() {
      Button("Add")
        .onClick(() => {
          this.count++
        })
      Text(this.count.toString())
    }
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  onCountChange() {
    console.log("count 变化了", this.count)
  }
}

数据对象

@Observed

让对象内部字段也可观察

@Observed
class User {
  name: string = ""
  age: number = 0
}

添加 @Observed,User 对象内部字段变化 → 自动触发 UI 更新。
不加 @Observed,User 对象内部字段变化 → UI 不会刷新。ArkUI 默认只追踪变量引用变化,不会追踪对象内部字段。

如果是 API 11+,推荐使用 @ObservedV2

用于绑定 @Observed 对象

@Link 不会监听对象内部字段变化,所以 @Link 大多只用于基本类型,对象类型必须用 @ObjectLink 才能保证响应式同步。

UI 构建

@Builder @BuilderParam

@Builder 声明一个可复用的 UI 构建函数。需要注意每个组件(用 @Component struct 定义)都有固定签名的 build 方法,这个 build 方法不是普通函数,它是组件生命周期的一部分,框架内部会自动当作 Builder 来执行,不需要再加 @Builder。

@BuilderParam 把一个 @Builder UI 构建函数作为参数传给组件,它允许父组件决定子组件内部某一块 UI 怎么画。

@Component
export struct Parent {
  build() {
    Column() {
      this.buildItem("haha")
      Child({
        itemBuilder: this.buildItem
      })
    }
    .backgroundColor(Color.White)
    .height("100%")
    .justifyContent(FlexAlign.Center)
    .alignItems(HorizontalAlign.Center)
  }

  @Builder
  buildItem(text: string) {
    Text(text)
  }
}

@Component
export struct Child {
  @BuilderParam itemBuilder: (text: string) => void

  build() {
    Column() {
      this.itemBuilder("Hello")
    }
  }
}

组件相关

@Component

声明一个结构体为 UI 组件,可以被其他组件调用。

@Entry

声明应用 UI 入口组件,应用启动后:

Application start
      ↓
加载 EntryAbility
      ↓
渲染 @Entry 标记的组件

@Reusable

可复用组件,告诉框架:这个组件可以被复用,而不是每次都重新创建。主要目的是减少组件创建销毁,提高列表等场景的性能。常用于列表滚动的 item 组件

@Builder
List() {
  LazyForEach(this.data, (item: string) => {
    ListItem() {
      ItemView({ text: item })
    }
  })
}

@Component
@Reusable
struct ItemView {
  @Prop text: string

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

@Preview

在 DevEco Studio Preview 面板中显示预览

LocalStorage

LocalStorage 是 ArkTS 为构建页面级别状态变量提供存储的内存内“数据库”。在页面中创建实例,组件中使用 @LocalStorageLink 和 @LocalStorageProp 装饰器修饰对应的状态变量,绑定对应的组件使用比状态属性更灵活。

  • 应用程序可以创建多个 LocalStorage 实例,LocalStorage 实例可以在页面内共享,也可以通过 GetShared 接口,实现跨页面、UIAbility 实例内共享。
  • 组件树的根节点,即被 @Entry 装饰的 @Component,可以被分配一个 LocalStorage 实例,此组件的所有子组件实例将自动获得对该 LocalStorage 实例的访问权限。
  • 被 @Component 装饰的组件最多可以访问一个 LocalStorage 实例和 AppStorage,未被 @Entry 装饰的组件不可被独立分配 LocalStorage 实例,只能接受父组件通过 @Entry 传递来的 LocalStorage 实例。一个 LocalStorage 实例在组件树上可以被分配给多个组件。
  • LocalStorage 中的所有属性都是可变的。

应用程序决定 LocalStorage 对象的生命周期。当应用释放最后一个指向 LocalStorage 的引用时,比如销毁最后一个自定义组件,LocalStorage 将被 JS Engine 垃圾回收。

用来把变量和 LocalStorage 双向绑定。常用于轻量级、跨页面或组件间共享的状态,例如主题色、用户设置。key 必须唯一。

  • 变量改变时自动写入本地存储
  • 页面初始化时自动从本地存储读取值
  • 简化了手动调用 LocalStorage 的过程
@Component
export struct ThemeSwitcher {
  // 绑定到 LocalStorage 的 theme 字段,默认是 "light"
  @LocalStorageLink("theme") theme: string = "light"

  build() {
    Column() {
      Text("当前主题:" + this.theme)
      Button("切换主题")
        .onClick(() => {
          this.theme = this.theme === "light" ? "dark" : "light"
        })
    }
  }
}

@LocalStorageProp

@LocalStorageProp 装饰的变量和与 LocalStorage 中给定属性建立单向同步关系。

AppStorage

AppStorage 是在应用启动的时候会被创建的单例。它的目的是为了提供应用状态数据的中心存储,这些状态数据在应用级别都是可访问的。AppStorage 将在应用运行过程保留其属性。属性通过唯一的键字符串值访问。
AppStorage 可以和 UI 组件同步,且可以在应用业务逻辑中被访问。
AppStorage 支持应用的主线程内多个 UIAbility 实例间的状态共享。

在各个页面组件中使用 @StorageProp 和 @StorageLink 装饰器修饰对应的状态变量。

@StorageProp

PersistentStorage

PersistentStorage 将选定的 AppStorage 属性保留在设备磁盘上。应用程序通过 API,以决定哪些 AppStorage 属性应借助 PersistentStorage 持久化。UI 和业务逻辑不直接访问 PersistentStorage 中的属性,所有属性访问都是对 AppStorage 的访问,AppStorage 中的更改会自动同步到 PersistentStorage。

PersistentStorage 和 AppStorage 中的属性建立双向同步。应用开发通常通过 AppStorage 访问 PersistentStorage,另外还有一些接口可以用于管理持久化属性,但是业务逻辑始终是通过 AppStorage 获取和设置属性的。