(三)鸿蒙HarmonyOS主力开发语言ArkTS-状态管理

516 阅读10分钟

系列文章目录

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


@TOC


前言

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


(一)状态管理概述

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

上面的示例中,用户与应用程序的交互触发了文本状态变更,状态变更引起了UI渲染,UI从“Hello World”变更为“Hello ArkUI”。

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

自定义组件拥有变量,变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。 下图展示了State和View(UI)之间的关系。

View(UI):UI渲染,指将build方法内的UI描述和@Builder装饰的方法内的UI描述映射到界面。 State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,引起UI的重新渲染。

基本概念

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

初始化状态

从父组件初始化

父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖。示例:

@Component
struct MyComponent {
  @State count: number = 0;
  private increaseBy: number = 1;

  build() {
    // ...
  }
}

@Component
struct Parent {
  build() {
    Column() {
      // 从父组件初始化,覆盖本地定义的默认值
      MyComponent({ count: 1, increaseBy: 2 })
    }
  }
}

初始化子节点

父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量。示例同上。

本地初始化

在变量声明的时候赋值,作为变量的默认值。示例:@State count: number = 0。

装饰器总览

ArkUI提供了多种装饰器,通过这些装饰器,状态变量不仅可以观察在组件内的改变,还可以在不同组件层级间传递,比如父子组件、跨组件层级,也可以观察全局范围内的变化。根据状态变量的影响范围,将所有的装饰器可以大致分为:

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

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

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

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

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

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

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

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

管理组件拥有的状态

@State

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

@Prop

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

@Link

@Link 装饰的变量和父组件构建双向同步关系的状态变量。父组件会接受来自 @Link 装饰的变量的修改的同步,父组件的更新也会同步给 @Link 装饰的变量。

@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用于监听状态变量的变化。

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

在这里插入图片描述

装饰目的

目标

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

使用场景

应用范围

  • API版本9及以后的ArkTS卡片中可用

观察变化与行为表现

  • 当状态变量变化时(包括双向绑定的AppStorage和LocalStorage中的对应键发生变化),对应的@Watch回调方法会被触发
  • 自定义组件的属性变更后,@Watch方法会同步执行
  • 如在@Watch方法内改变其他状态变量,也会引发状态变更并执行@Watch
  • 在首次初始化时,@Watch装饰的方法不会被调用,只在后续状态改变时才调用@Watch回调方法

注意事项

限制条件

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

示例演示

组件更新与@Watch处理步骤

  • count在CountModifier中由@State装饰,在TotalView中由@Prop装饰

子组件中观察@Link变量的步骤

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

内部状态具体指什么取决于组件。例如,[Refresh](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-container-refresh-0000001478181429-V2)组件的refreshing参数。 ## 使用规则 + 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量。 + 当前$$仅支持[Refresh](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-container-refresh-0000001478181429-V2)组件的refreshing参数。 + $$绑定的变量变化时,会触发UI的同步刷新。 ## 使用示例 以[Refresh](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V2/ts-container-refresh-0000001478181429-V2)组件的refreshing参数为例: 当使用了$$符号绑定isRefreshing状态变量时,页面进行下拉操作,isRefreshing会变成true。 同时,Text中的isRefreshing状态也会同步改变为true,如果不使用$$符号绑定,则不会同步改变。 ```typescript // 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) }) } } } ``` ![](https://img-blog.csdnimg.cn/img_convert/6548a74bbb47a5d07d4a3d42d7477607.gif) # 总结 主要介绍了在声明式UI编程框架中,如何通过引入“状态”的概念来构建动态、有交互的界面。它详细解释了状态管理机制的原理,包括状态变量、常规变量、数据源/同步源以及命名参数机制等基本概念。同时,文章还介绍了ArkUI框架中提供的多种装饰器,用于管理组件和应用的状态,包括组件级别的状态管理(如@State、@Prop、@Link等)和应用级别的状态管理(如@StorageProp、@StorageLink等)。此外,文章还提到了AppStorage和LocalStorage这两个应用级别的状态管理机制,以及@Watch和$$运算符等其他状态管理功能。