系列文章目录

(一)鸿蒙HarmonyOS开发基础
(二)鸿蒙HarmonyOS主力开发语言ArkTS-基本语法


文章目录


前言

每当学习一种新技巧,脑神经就会产生新的连线;学越多,线连越多,脑筋就越灵活。


(一)状态管理概述

在前文的描述中,我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。

基本概念

  • 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新。示例:@State num: number = 1,其中,@State是状态装饰器,num是状态变量。
  • 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。以下示例中increaseBy变量为常规变量。
  • 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。以下示例中数据源为count: 1。
  • 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。
  • 本地初始化:在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。

状态管理(V2)

@Local

@Local表示组件内部的状态,使得自定义组件内部的变量具有观测变化的能力:

被@Local装饰的变量无法从外部初始化,因此必须在组件内部进行初始化。

当被@Local装饰的变量变化时,会刷新使用该变量的组件。

@Local支持观测number、boolean、string、Object、class等基本类型以及Array、Set、Map、Date等内嵌类型。

@Local的观测能力仅限于被装饰的变量本身。当装饰简单类型时,能够观测到对变量的赋值;当装饰对象类型时,仅能观测到对对象整体的赋值;当装饰数组类型时,能观测到数组整体以及数组元素项的变化;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化。详见观察变化。

@Local支持null、undefined以及联合类型。

@Local装饰器只能在@ComponentV2装饰的自定义组件中使用。
@Local装饰的变量表示组件内部状态,不允许从外部传入初始化。

@State @Local
参数 无。 无。
从父组件初始化 可选。 不允许外部初始化。
观察能力 能观测变量本身以及一层的成员属性,无法深度观测。 能观测变量本身,深度观测依赖@Trace装饰器。
数据传递 可以作为数据源和子组件中状态变量同步。 可以作为数据源和子组件中状态变量同步。

@ObservedV2装饰器与@Trace装饰器需要配合使用

未被@Trace装饰的属性用在UI中无法感知到变化,也无法触发UI刷新。

@Param表示组件从外部传入的状态

  • @Param装饰的变量支持本地初始化,但是不允许在组件内部直接修改变量本身。

  • 对于复杂类型如类对象,@Param会接受数据源的引用。在组件内可以修改类对象中的属性,该修改会同步到数据源。

  • @Param的观测能力仅限于被装饰的变量本身。当装饰简单类型时,对变量的整体改变能够观测到;当装饰对象类型时,仅能观测对象整体的改变;当装饰数组类型时,能观测到数组整体以及数组元素项的改变;当装饰Array、Set、Map、Date等内嵌类型时,可以观测到通过API调用带来的变化。详见观察变化

  • 当装饰的变量类型为类对象时,仅可以观察到对类对象整体赋值的变化,无法直接观察到对类成员属性赋值的变化,对类成员属性的观察依赖@ObservedV2和@Trace装饰器。

@Once

  • @Once必须搭配@Param使用,单独使用或搭配其他装饰器使用都是不允许的。
  • @Once装饰器仅在变量初始化时接受外部传入值进行初始化,当后续数据源更改时,不会将修改同步给子组件:

@Event主要配合@Param实现数据的双向同步

@Provider和@Consumer

可接受可选参数aliasName,如果开发者没有配置参数,则使用属性名作为默认的aliasName。

  • @Provider和@Consumer建立双向绑定
  • @Provider和@Consumer未建立双向绑定
    由于aliasName值不同,无法建立双向同步关系。
  • @Provider和@Consumer装饰回调事件,用于组件之间完成行为抽象
  • @Provider和@Consumer装饰复杂类型,配合@Trace一起使用
    @Provider和@Consumer只能观察到数据本身的变化。如果当其装饰复杂数据类型,需要观察属性的变化时,需要配合@Trace一起使用。
    装饰内置类型:Array、Map、Set、Date时,可以观察到某些API的变化,观察能力同@Trace。

@Monitor装饰器对状态变量进行监听

@Watch @Monitor
参数 回调方法名。 监听状态变量名、属性名。
监听目标数 只能监听单个状态变量。 能同时监听多个状态变量。
监听能力 跟随状态变量观察能力(一层)。 跟随状态变量观察能力(深层)。
能否获取变化前的值 不能获取变化前的值。 能获取变化前的值。
监听条件 监听对象为状态变量。 监听对象为状态变量或为@Trace装饰的类成员属性。
使用限制 仅能在@Component装饰的自定义组件中使用。 能在@ComponentV2装饰的自定义组件中使用,也能在@ObservedV2装饰的类中使用。

@Computed装饰器

计算属性,在被计算的值变化的时候,只会计算一次。主要应用于解决UI多次重用该属性从而重复计算导致的性能问题。
@Computed为方法装饰器,仅能装饰getter方法。

状态管理V1现状以及V2优点

状态管理V1使用代理观察数据,当创建一个状态变量时,同时也创建了一个数据代理观察者。该观察者可感知代理变化,但无法感知实际数据变化,因此在使用上有如下限制:

  • 状态变量不能独立于UI存在,同一个数据被多个视图代理时,在其中一个视图的更改不会通知其他视图更新。
  • 只能感知对象属性第一层的变化,无法做到深度观测和深度监听。
  • 在更改对象中属性以及更改数组中元素的场景下存在冗余更新的问题。
  • 装饰器间配合使用限制多,不易用。组件中没有明确状态变量的输入与输出,不利于组件化。

状态管理V2将观察能力增强到数据本身,数据本身就是可观察的,更改数据会触发相应的视图的更新。相较于状态管理V1,状态管理V2有如下优点:

  • 状态变量独立于UI,更改数据会触发相应视图的更新。

  • 支持对象的深度观测和深度监听,且深度观测机制不影响观测性能。

  • 支持对象中属性级精准更新及数组中元素的最小化更新。

  • 装饰器易用性高、拓展性强,在组件中明确输入与输出,有利于组件化。

状态管理V1与V2能力对比

V1能力 V2能力 说明
@Observed @ObservedV2 表明当前对象为可观察对象。但两者能力并不相同。@Observed可观察第一层的属性,需要搭配@ObjectLink使用才能生效。@ObservedV2本身无观察能力,仅代表当前class可被观察,如果要观察其属性,需要搭配@Trace使用。
@Track @Trace V1装饰器@Track为精确观察,不使用则无法做到类属性的精准观察。V2@Trace装饰的属性可以被精确跟踪观察。
@Component @ComponentV2 @Component为搭配V1状态变量使用的自定义组件装饰器。@ComponentV2为搭配V2状态变量使用的自定义组件装饰器。
@State 无外部初始化:@Local 外部初始化一次:@Param@Once @State和@Local类似都是数据源的概念,区别是@State可以外部传入初始化,而@Local无法外部传入初始化。
@Prop @Param @Prop和@Param类似都是自定义组件参数的概念。当输入参数为复杂类型时,@Prop为深拷贝,@Param为引用。
@Link @Param@Event @Link是框架自己封装实现的双向同步,对于V2开发者可以通过@Param@Event自己实现双向同步。
@ObjectLink @Param 直接兼容,@ObjectLink需要被@Observed装饰的class的实例初始化,@Param没有此限制。
@Provide @Provider 兼容。
@Consume @Consumer 兼容。
@Watch @Monitor @Watch用于监听V1状态变量的变化,具有监听状态变量本身和其第一层属性变化的能力。状态变量可观察到的变化会触发其@Watch监听事件。@Monitor用于监听V2状态变量的变化,搭配@Trace使用,可有深层监听的能力。状态变量在一次事件中多次变化时,仅会以最终的结果判断是否触发@Monitor监听事件。
LocalStorage 全局@ObservedV2@Trace 兼容。
AppStorage AppStorageV2 兼容。
Environment 调用Ability接口获取系统环境变量 Environment获取环境变量能力和AppStorage耦合。在V2中可直接调用Ability接口获取系统环境变量。
PersistentStorage PersistenceV2 PersistentStorage持久化能力和AppStorage耦合,PersistenceV2持久化能力可独立使用。
自定义组件生命周期 自定义组件生命周期 均支持。aboutToAppearonDidBuildaboutToDisappear
页面生命周期 页面生命周期 均支持。onPageShowonPageHideonBackPress
@Reusable 暂未提供 组件复用。包括:aboutToReuseaboutToRecycle
$$ !! 双向绑定。V2建议使用!!实现双向绑定。
@CustomDialog openCustomDialog接口 自定义弹窗。V2建议使用openCustomDialog实现自定义弹窗功能。
withTheme 暂未提供 主题。用于设置应用局部页面自定义主题风格。包括:onWillApplyTheme
高级组件 暂未提供 高级组件。例如:DownloadFileButtonProgressButtonSegmentButton
animateTo 部分场景不支持 当前某些场景下,在状态管理V2中使用animateTo动画,会产生异常效果,详见:在状态管理V2中使用animateTo动画效果异常

@Component、@ComponentV2

无法同时使用@ComponentV2与@Component装饰同一个struct结构。

状态管理(V1)

管理组件拥有状态的装饰器

组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。

管理应用拥有状态的装饰器

应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。

从数据的传递形式和同步类型层面看,装饰器也可分为:

  • 只读的单向传递;
  • 可变更的双向传递。

图示如下,具体装饰器的介绍,可详见管理组件拥有的状态和管理应用拥有的状态。开发者可以灵活地利用这些能力来实现数据和UI的联动。

上图中,Components部分的装饰器为组件级别的状态管理,Application部分为应用的状态管理。开发者可以通过@StorageLink/@LocalStorageLink实现应用和组件状态的双向同步,通过@StorageProp/@LocalStorageProp实现应用和组件状态的单向同步。

管理组件拥有的状态

@State

@State 装饰的变量拥有其所属组件的状态,可以作为其子组件单向和双向同步的数据源。当其数值改变时,会引起相关组件的渲染刷新。

@State @Local
参数 无。 无。
从父组件初始化 可选。 不允许外部初始化。
观察能力 能观测变量本身以及一层的成员属性,无法深度观测。 能观测变量本身,深度观测依赖@Trace装饰器。
数据传递 可以作为数据源和子组件中状态变量同步。 可以作为数据源和子组件中状态变量同步。

两者在观察能力和初始化规则上存在明显差异。针对不同的使用场景,迁移策略如下:

  • 简单类型:对于简单类型的变量,可以直接将@State替换为@Local。
  • 复杂类型:V1中的@State可以观察复杂对象的第一层属性变化,而V2中的@Local只能观察对象自身的变化。如果需要追踪对象内部的属性变化,可以结合使用@ObservedV2和@Trace。
  • 外部初始化:V1中,@State支持从外部传递初始值,但在V2中,@Local禁止外部初始化。若需要从外部传递初始值,可以使用@Param和@Once装饰器来实现类似的效果。

@Prop

@Prop 装饰的变量可以和父组件建立单向同步关系。@Prop 装饰的变量是可变的,但修改不会同步回父组件。

  • 简单类型:对于简单类型的参数,可以直接将@Prop替换@Param。
  • 复杂类型:如果传递的是复杂对象且需要严格的单向数据绑定,可以对对象进行深拷贝,防止子组件修改父组件的数据。
  • 子组件修改变量:如果子组件需要修改传入的参数,可以使用@Once来允许子组件对在本地修改该变量。但需要注意,如果使用了@Once,则代表当前子组件只会被初始化一次,后续并没有父组件到子组件的同步能力。

@Link

@Link 装饰的变量和父组件构建双向同步关系的状态变量。父组件会接受来自 @Link 装饰的变量的修改的同步,父组件的更新也会同步给 @Link 装饰的变量。
迁移到V2时,可以用@Param和@Event模拟双向同步。@Param实现父到子的单向传递,子组件再通过@Event回调函数触发父组件的状态更新。

@Provide / @Consume

@Provide / @Consume 装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过 alias(别名)或者属性名绑定。

@Observed

@Observed 装饰 class,需要观察多层嵌套场景的 class 需要被 @Observed 装饰。单独使用 @Observed 没有任何作用,需要和 @ObjectLink@Prop 连用。

@ObjectLink

@ObjectLink 装饰的变量接收 @Observed 装饰的 class 的实例,应用于观察多层嵌套场景,和父组件的数据源构建双向同步。

应用级别的状态管理

AppStorage

AppStorage 是应用程序中的一个特殊的单例 LocalStorage 对象,是应用级的数据库,和进程绑定。通过 @StorageProp@StorageLink 装饰器可以和组件联动。AppStorage 是应用状态的“中枢”,将需要与组件(UI)交互的数据存入 AppStorage,比如持久化数据 PersistentStorage 和环境变量 Environment。UI 再通过 AppStorage 提供的装饰器或者 API 接口,访问这些数据。

LocalStorage

框架还提供了 LocalStorage,AppStorage 是 LocalStorage 特殊的单例。LocalStorage 是应用程序声明的应用状态的内存“数据库”,通常用于页面级的状态共享。通过 @LocalStorageProp@LocalStorageLink 装饰器可以和 UI 联动。

其他状态管理功能

@Watch

@Watch 用于监听状态变量的变化。

$$ 运算符

$$ 运算符用于给内置组件提供 TS 变量的引用,使得 TS 变量和内置组件的内部状态保持同步。

(二)管理组件拥有的状态

@State装饰器:组件内状态

在这里插入图片描述

说明

本章节将详细介绍ArkUI中的@State装饰器,它允许开发者创建具有状态属性的变量。这些状态变量与组件的渲染绑定,当状态改变时,UI会发生相应的渲染改变。此外,还将讨论如何使用@State装饰器,并解释其观察变化和框架行为表现。

概述

@State装饰器用于创建具有状态属性的变量,这些变量只能从组件内部访问,并在声明时必须指定其类型和本地初始化。初始化也可以选择使用命名参数机制从父组件完成。

@State装饰的变量特点

  1. 与子组件中的@Prop装饰变量建立单向数据同步,与@Link@ObjectLink装饰变量建立双向数据同步。
  2. 生命周期与其所属自定义组件的生命周期相同。

装饰器使用规则说明

@State变量装饰器
装饰器参数
同步类型 不与父组件中任何类型的变量同步。
允许装饰的变量类型 Object、class、string、number、boolean、enum类型,以及这些类型的数组。嵌套类型的场景请参考观察变化。类型必须被指定。不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。
说明 建议不要装饰Date类型,应用可能会产生异常行为。不支持Length、ResourceStr、ResourceColor类型,Length、ResourceStr、ResourceColor为简单类型和复杂类型的联合类型。
被装饰变量的初始值 必须本地初始化。

变量的传递/访问规则说明

传递/访问 说明
从父组件初始化 可选,从父组件初始化或者本地初始化。如果从父组件初始化将会覆盖本地初始化。支持父组件中常规变量、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp装饰的变量,初始化子组件的@State。
用于初始化子组件 @State装饰的变量支持初始化子组件的常规变量、@State、@Link、@Prop、@Provide。
是否支持组件外访问 不支持,只能在组件内访问。

图1 初始化规则图示

观察变化和行为表现

并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。本小节将介绍什么样的修改才能被观察到,以及观察到变化后,框架的是怎么引起UI刷新的,即框架的行为表现是什么。

观察变化

当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。

@State count: number = 0;
// value changing can be observed
this.count = 1;

当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。

class Model {
  public value: string;
  public name: ClassA;
  constructor(value: string, a: ClassA) {
    this.value = value;
    this.name = a;
  }
}

@State title: Model = new Model('Hello', new ClassA('World'));

// class类型赋值
this.title = new Model('Hi', new ClassA('ArkUI'));

// class属性的赋值
this.title.value = 'Hi';

当装饰的对象是array时,可以观察到数组本身的赋值和添加、删除、更新数组的变化。

class Model {
  public value: number;
  constructor(value: number) {
    this.value = value;
  }
}

@State title: Model[] = [new Model(11), new Model(1)];

// 数组自身的赋值可以观察到
this.title = [new Model(2)];

// 数组项的赋值可以观察到
this.title[0] = new Model(2);

// 删除数组项可以观察到
this.title.pop();

// 新增数组项可以观察到
this.title.push(new Model(12));
框架行为

当状态变量被改变时,框架会查询依赖该状态变量的组件,并执行这些组件的更新方法,实现按需更新。与状态变量不相关的组件或UI描述不会发生重新渲染。

使用场景

装饰简单类型的变量

以下示例展示了使用@State装饰简单类型的变量,其中count是一个状态变量。当count改变时,会触发Button组件的刷新。

@Entry
@Component
struct MyComponent {
  @State count: number = 0;

  build() {
    Button(`click times: ${this.count}`)
      .onClick(() => {
        this.count += 1;
      })
  }
}
装饰class对象类型的变量

自定义组件MyComponent定义了被@State装饰的状态变量counttitle,其中title的类型为自定义类Model。如果counttitle的值发生变化,则查询MyComponent中使用该状态变量的UI组件,并进行重新渲染。

EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent

class Model {
  public value: string;

  constructor(value: string) {
    this.value = value;
  }
}

@Entry
@Component
struct EntryComponent {
  build() {
    Column() {
      // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化
      MyComponent({ count: 1, increaseBy: 2 })
        .width(300)
      MyComponent({ title: new Model('Hello World 2'), count: 7 })
    }
  }
}

@Component
struct MyComponent {
  @State title: Model = new Model('Hello World');
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
    Column() {
      Text(`${this.title.value}`)
        .margin(10)
      Button(`Click to change title`)
        .onClick(() => {
          // @State变量的更新将触发上面的Text组件内容更新
          this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
        })
        .width(300)
        .margin(10)

      Button(`Click to increase count = ${this.count}`)
        .onClick(() => {
          // @State变量的更新将触发该Button组件的内容更新
          this.count += this.increaseBy;
        })
        .width(300)
        .margin(10)
    }
  }
}


从该示例中,我们可以了解到@State变量首次渲染的初始化流程:

使用默认的本地初始化:

@State title: Model = new Model('Hello World');
@State count: number = 0;

对于@State来说,命名参数机制传递的值并不是必选的,如果没有命名参数传值,则使用本地初始化的默认值:

MyComponent({ count: 1, increaseBy: 2 })

@Prop装饰器:父子单向同步

在这里插入图片描述

装饰器特性

变量同步性
  • 单向同步:子组件@Prop变量的变化不会同步到父组件
  • 父组件状态变量的变化会同步给子组件@Prop变量
允许的变量类型
  • string、number、boolean、enum类型
  • 不支持any,不允许使用undefined和null
  • 必须指定类型
初始化规则
  • 允许本地初始化
  • 如果本地已有初始化,则初始化是可选的
  • 没有本地初始化的情况下,初始化是必需的

使用场景

父子组件间的数据同步
简单数据类型同步
  • 父组件@State到子组件@Prop的简单数据类型同步
  • 父组件状态变量的变化会影响子组件相关@Prop变量的值
数组项目同步
  • 父组件@State装饰的数组项可以初始化子组件的@Prop变量
  • 子组件的局部变量值可以在组件内部进行更改
对象属性同步
  • 当父组件状态变量为Object或class时,其属性类型需与子组件的@Prop变量相匹配
组件复用和支持
  • 支持本地初始化,使@Prop是否与父组件建立同步关系可选
  • 当且仅当@Prop有本地初始化时,从父组件向子组件传递@Prop的数据源才是可选的

@Link装饰器:父子双向同步

在这里插入图片描述

装饰器功能

特点
  • 子组件中被@Link装饰的变量与父组件中对应的数据源建立双向数据绑定。
  • 从API版本9开始,支持在ArkTS卡片中使用。
  • 被@Link装饰的变量与其父组件中的数据源共享相同的值。
  • 不能在@Entry装饰的自定义组件中使用。
变量装饰器说明
  • 装饰器参数:无
  • 同步类型:双向同步
  • 允许装饰的变量类型:Object、class、string、number、boolean、enum类型,以及这些类型的数组。
  • 必须指定类型,并且和双向绑定状态变量的类型相同。
  • 不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。
变量初始化
  • 初始值:无,禁止本地初始化。
  • 从父组件初始化和更新:必选。
  • 是否支持组件外访问:私有,只能在所属组件内访问。

观察变化和行为表现

生命周期共享
  • @Link装饰的变量和其所属的自定义组件共享生命周期。
初始化和更新机制
  • 通过理解父组件和拥有@Link变量的子组件的关系,以及初始渲染和双向更新的流程(以父组件为@State为例),可以更好地理解@Link变量的初始化和更新机制。

使用场景

示例
  • 点击父组件ShufflingContainer中的“Parent View: Set yellowButton”和“Parent View: Set GreenButton”,可以从父组件将变化同步给子组件。
  • 点击子组件GreenButton和YellowButton中的Button,子组件会发生相应变化,将变化同步给父组件。
  • 当点击父组件ShufflingContainer中的Button时,@State变化,也会同步给@Link,子组件也会发生对应的刷新。
class GreenButtonState {
  width: number = 0;

  constructor(width: number) {
    this.width = width;
  }
}

@Component
struct GreenButton {
  @Link greenButtonState: GreenButtonState;

  build() {
    Button('Green Button')
      .width(this.greenButtonState.width)
      .height(40)
      .backgroundColor('#64bb5c')
      .fontColor('#FFFFFF,90%')
      .onClick(() => {
        if (this.greenButtonState.width < 700) {
          // 更新class的属性,变化可以被观察到同步回父组件
          this.greenButtonState.width += 60;
        } else {
          // 更新class,变化可以被观察到同步回父组件
          this.greenButtonState = new GreenButtonState(180);
        }
      })
  }
}

@Component
struct YellowButton {
  @Link yellowButtonState: number;

  build() {
    Button('Yellow Button')
      .width(this.yellowButtonState)
      .height(40)
      .backgroundColor('#f7ce00')
      .fontColor('#FFFFFF,90%')
      .onClick(() => {
        // 子组件的简单类型可以同步回父组件
        this.yellowButtonState += 40.0;
      })
  }
}

@Entry
@Component
struct ShufflingContainer {
  @State greenButtonState: GreenButtonState = new GreenButtonState(180);
  @State yellowButtonProp: number = 180;

  build() {
    Column() {
      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
        // 简单类型从父组件@State向子组件@Link数据同步
        Button('Parent View: Set yellowButton')
          .width(312)
          .height(40)
          .margin(12)
          .fontColor('#FFFFFF,90%')
          .onClick(() => {
            this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
          })
        // class类型从父组件@State向子组件@Link数据同步
        Button('Parent View: Set GreenButton')
          .width(312)
          .height(40)
          .margin(12)
          .fontColor('#FFFFFF,90%')
          .onClick(() => {
            this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
          })
        // class类型初始化@Link
        GreenButton({ greenButtonState: $greenButtonState }).margin(12)
        // 简单类型初始化@Link
        YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12)
      }
    }
  }
}

注意事项

@Component
struct Child {
  @Link items: number[];

  build() {
    Column() {
      Button(`Button1: push`)
        .margin(12)
        .width(312)
        .height(40)
        .fontColor('#FFFFFF,90%')
        .onClick(() => {
          this.items.push(this.items.length + 1);
        })
      Button(`Button2: replace whole item`)
        .margin(12)
        .width(312)
        .height(40)
        .fontColor('#FFFFFF,90%')
        .onClick(() => {
          this.items = [100, 200, 300];
        })
    }
  }
}

@Entry
@Component
struct Parent {
  @State arr: number[] = [1, 2, 3];

  build() {
    Column() {
      Child({ items: $arr })
        .margin(12)
      ForEach(this.arr,
        (item: number) => {
          Button(`${item}`)
            .margin(12)
            .width(312)
            .height(40)
            .backgroundColor('#11a2a2a2')
            .fontColor('#e6000000')
        },
        (item: ForEachInterface) => item.toString()
      )
    }
  }
}

  • ArkUI框架可以观察到数组元素的添加,删除和替换。
  • 在此示例中,@State和@Link的类型是相同的number[],不允许将@Link定义成number类型(@Link item : number),并在父组件中用@State数组中每个数据项创建子组件。如果要使用这个场景,可以参考@Prop和@Observed。

(三)管理应用拥有的状态

上一个章节中介绍的装饰器仅能在页面内,即一个组件树上共享状态变量。如果开发者要实现应用级的,或者多个页面的状态数据共享,就需要用到应用级别的状态管理的概念。ArkTS根据不同特性,提供了多种应用状态管理的能力:

  • LocalStorage:页面级UI状态存储,通常用于UIAbility内、页面间的状态共享。
  • AppStorage:特殊的单例LocalStorage对象,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储;
  • PersistentStorage:持久化存储UI状态,通常和AppStorage配合使用,选择AppStorage存储的数据写入磁盘,以确保这些属性在应用程序重新启动时的值与应用程序关闭时的值相同;
  • Environment:应用程序运行的设备的环境参数,环境参数会同步到AppStorage中,可以和AppStorage搭配使用。

LocalStorage:页面级UI状态存储

在这里插入图片描述

特点

  • 支持在页面内或UIAbility实例内共享状态
  • 内置于ArkTS,用于构建页面级别的状态变量
  • 生命周期由应用程序管理
  • 提供两种装饰器:@LocalStorageProp 和 @LocalStorageLink

使用场景

  • 在UI组件内部获取LocalStorage实例中存储的状态变量
  • 建立与自定义组件的联系
  • 实现单向或双向数据同步

装饰器介绍

@LocalStorageProp
  • 单向数据同步:从LocalStorage的对应属性到组件的状态变量
  • 初始化子节点支持
  • 不支持从父节点初始化和更新
  • 被装饰变量的初始值必须指定
@LocalStorageLink
  • 双向数据同步:从LocalStorage的对应属性到自定义组件,从自定义组件到LocalStorage对应属性
  • 初始化子节点支持
  • 不支持从父节点初始化和更新
  • 被装饰变量的初始值必须指定

示例

  • CompA组件和Child组件分别在本地创建了与storage的’PropA’对应属性的单向同步的数据
  • 展示了@LocalStorageLink装饰的数据和LocalStorage双向同步的场景
  • 通过@LocalStorageLink双向同步兄弟节点之间的状态

共享机制

  • LocalStorage.GetShared只在模拟器或者实机上才有效,不能在Preview预览器中使用
  • 建议在创建LocalStorage实例的时候就写入默认值,以便作为运行异常的备份或页面的单元测试

AppStorage:应用全局的UI状态存储

在这里插入图片描述

关键概念

AppStorage与LocalStorage的区别
  • AppStorage:应用级别的全局状态共享,类似应用的"中枢"。
  • LocalStorage:页面级别的数据共享。
AppStorage的特性
  • 单例,与应用进程绑定。
  • 在应用启动时创建,为应用程序UI状态属性提供中央存储。
  • 可以与UI组件同步,可在应用业务逻辑中访问。
  • 属性可以双向同步,支持本地或远程设备上的数据,具有数据持久化等功能。

使用场景

装饰器:@StorageProp和@StorageLink
  • @StorageProp:用于建立单向数据同步,允许本地改变,但不会同步回AppStorage。
  • @StorageLink:用于建立双向数据同步,允许本地改变,同步回AppStorage。
初始化规则
  • @StorageProp只支持初始化自定义组件,不支持从父节点初始化。
  • @StorageLink支持初始化子节点,可用于初始化常规变量、@State、@Link、@Prop、@Provide等。

注意事项

  • 不建议使用@StorageLink实现事件通知,因其会导致UI刷新,成本较大。
  • 开发者应考虑使用更高效的方式,如emit订阅事件并接收事件回调,以提高代码可读性和降低资源消耗。
合作与配合
  • AppStorage与其他数据存储(如PersistentStorage, Environment)共同协作,需注意各组件间的协调工作。

PersistentStorage:持久化存储UI状态

在这里插入图片描述

LocalStorage和AppStorage

特点
  • 运行时内存,重启后仍保留选择结果
应用场景
  • 在应用开发中常见

PersistentStorage

定义
  • 应用程序中的可选单例对象
功能
  • 持久化存储选定的AppStorage属性
  • 确保属性在应用程序重新启动时的值与应用程序关闭时的值相同
数据存储位置
  • 保存在设备磁盘上
访问方式
  • 通过API决定哪些AppStorage属性应借助PersistentStorage持久化
  • UI和业务逻辑不直接访问PersistentStorage中的属性
  • AppStorage中的更改会自动同步到PersistentStorage

PersistentStorage的使用限制

允许的类型和值
  • 视具体需求而定
不允许的类型和值
  • 视具体需求而定
避免的情况
  • 大量数据持久化
  • 影响UI渲染性能
  • 不应在非UI页面内使用

PersistentStorage接口

PersistProp(key: string, defaultValue: T): void
  • 将AppStorage中key对应的属性持久化到文件中
  • 调用通常在访问AppStorage之前
DeleteProp(key: string): void
  • 从PersistentStorage删除指定属性
PersistProps(properties: {key: string, defaultValue: any;}[]): void
  • 一次性持久化多个数据,适合在应用启动时初始化
Keys(): Array
  • 返回所有持久化属性的key的数组

使用场景

  • 在组件内部定义
  • 根据实际需求调整参数和方法顺序

设备环境查询 - Environment

在这里插入图片描述

Environment概述

创建时机
  • 应用程序启动时由ArkUI框架创建
特点
  • 单例对象
  • 提供一系列描述应用程序运行状态的属性
  • 所有属性都是不可变的(应用不可写入)
  • 所有属性都是简单类型

Environment内置参数

参数详情
accessibilityEnabled (boolean)
  • 获取无障碍屏幕读取是否启用
colorMode (ColorMode)
  • 色彩模型类型:浅色(ColorMode.LIGHT)或深色(ColorMode.DARK)
fontScale (number)
  • 字体大小比例,范围:[0.85, 1.45]
fontWeightScale (number)
  • 字体粗细程度,范围:[0.6, 1.6]
layoutDirection (LayoutDirection)
  • 布局方向类型:从左到右(LayoutDirection.LTR)或从右到左(LayoutDirection.RTL)
languageCode (string)
  • 当前系统语言值,取值必须为小写字母,如“zh”

使用场景

更新链路
  • 设备环境到Component的更新链:Environment --> AppStorage --> Component
注意事项
  • @StorageProp关联的环境参数可以本地更改,但不能同步回AppStorage中,因为应用对环境变量参数是不可写的,只能在Environment中查询

(四)其他状态管理

除了前面章节提到的组件状态管理和应用状态管理,ArkTS还提供了@Watch和$$来为开发者提供更多功能:

  • @Watch用于监听状态变量的变化。
  • $$运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。

@Watch装饰器:状态变量更改通知

在这里插入图片描述

装饰目的

目标
  • 针对状态变量的值变动提供通知机制

使用场景

应用范围
  • API版本9及以后的ArkTS卡片中可用
观察变化与行为表现
  • 当状态变量变化时(包括双向绑定的AppStorage和LocalStorage中的对应键发生变化),对应的@Watch回调方法会被触发
  • 自定义组件的属性变更后,@Watch方法会同步执行
  • 如在@Watch方法内改变其他状态变量,也会引发状态变更并执行@Watch
  • 在首次初始化时,@Watch装饰的方法不会被调用,只在后续状态改变时才调用@Watch回调方法

注意事项

限制条件
  • 开发者应避免无限循环,避免在@Watch回调方法中直接或间接修改同一状态变量
  • 关注性能,属性值更新函数会延迟组件的重新渲染,回调函数应仅执行快速运算
  • 不建议在@Watch函数中调用async await,因为这可能导致重新渲染速度的性能问题

示例演示

组件更新与@Watch处理步骤
  • count在CountModifier中由@State装饰,在TotalView中由@Prop装饰
子组件中观察@Link变量的步骤

$$语法:内置组件双向同步

$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。

内部状态具体指什么取决于组件。例如,Refresh组件的refreshing参数。

使用规则

  • 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量。
  • 当前$$仅支持Refresh组件的refreshing参数。
  • $$绑定的变量变化时,会触发UI的同步刷新。

使用示例

Refresh组件的refreshing参数为例:

当使用了$$符号绑定isRefreshing状态变量时,页面进行下拉操作,isRefreshing会变成true。

同时,Text中的isRefreshing状态也会同步改变为true,如果不使用$$符号绑定,则不会同步改变。

// xxx.ets
@Entry
@Component
struct RefreshExample {
  @State isRefreshing: boolean = false
  @State counter: number = 0

  build() {
    Column() {
      Text('Pull Down and isRefreshing: ' + this.isRefreshing)
        .fontSize(30)
        .margin(10)

      Refresh({ refreshing: $$this.isRefreshing, offset: 120, friction: 100 }) {
        Text('Pull Down and refresh: ' + this.counter)
          .fontSize(30)
          .margin(10)
      }
      .onStateChange((refreshStatus: RefreshStatus) => {
        console.info('Refresh onStateChange state is ' + refreshStatus)
      })
    }
  }
}

总结

主要介绍了在声明式UI编程框架中,如何通过引入“状态”的概念来构建动态、有交互的界面。它详细解释了状态管理机制的原理,包括状态变量、常规变量、数据源/同步源以及命名参数机制等基本概念。同时,文章还介绍了ArkUI框架中提供的多种装饰器,用于管理组件和应用的状态,包括组件级别的状态管理(如@State、@Prop、@Link等)和应用级别的状态管理(如@StorageProp、@StorageLink等)。此外,文章还提到了AppStorage和LocalStorage这两个应用级别的状态管理机制,以及@Watch和$$运算符等其他状态管理功能。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐