ArkTS @State用法详解(附带实例)
在 ArkTS 中,状态管理是构建响应式用户界面的关键。通过一系列装饰器和指令,ArkTS 提供了一种简捷而有效的方式来处理组件的状态和数据流。
@State 装饰器用于定义状态变量,它赋予变量状态属性,使其与自定义组件的渲染紧密绑定。当状态发生改变时,UI 会自动同步更新。这类变量是私有的,仅能在组件内部访问。在声明状态变量时,需要指定其类型和初始值,也可以通过命名参数由父组件进行初始化。
其特性包括:
@State 变量传递/访问规则如下:
初始化规则如下图所示:

图 1 初始化规则
注意,并非所有状态变量的变更都会导致 UI 更新,只有框架能观察到的修改才会触发 UI 更新。接下来将阐述哪些修改会被框架观察到,以及框架在观察到变化后的行为。
1) 当 @State 装饰的数据类型为 boolean、string、number 时,可以观察到数值的变化。也就是说,当数据类型为 boolean、string、number 时,如果数据发生改变,那么页面也会同步更新。示例如下:

图 2 @State 装饰器1

图 3 @State装饰器2
2) 当 @State 装饰的数据类型为 class 或者 Object 时,可以观察到自身赋值的变化和其属性赋值的变化,即 Object.keys(observedObject) 返回的所有属性。
示例如下:
接下来使用定义的类型来观察数据的变化。首先使用 @State 装饰 Model,然后进行数据修改,以观察是否可以修改数据。示例代码如下:

图 4 @State装饰器3

图 5 @State装饰器4
说明当 @State 装饰的是 class 类型时,数据的变化是可以被观察到的。
如果只修改 @State 装饰的某一个属性呢?示例代码如下:
3) 当装饰的对象是 array 时,可以观察到数组本身的赋值和添加、删除、更新数组的变化。
数组的操作在开发的过程中是比较常见的,通过下面的代码示例将会更好地理解关于数组的操作方案。
案例代码主要是实现当单击按钮时,将自定义数组对象的内容进行替换,效果如图 6 和图 7 所示。

图 6 数组的赋值操作1

图 7 数组的赋值操作2
修改数组中的某一项数据,代码如下:

图 8 数组的替换操作1

图 9 数组的替换操作2
删除数组中的数据,代码如下:

图 10 数组的删除操作1

图 11 数组的删除操作2
为数组新增数据,代码如下:

图 12 数组的新增操作1

图 13 数组的新增操作2
我们通过 .value 来获取数组中对应的数据,然后直接进行赋值操作,是否可以呢?直接对数组中的属性进行赋值,代码如下:

图 14 对数组中的属性进行赋值
@State 装饰器用于定义状态变量,它赋予变量状态属性,使其与自定义组件的渲染紧密绑定。当状态发生改变时,UI 会自动同步更新。这类变量是私有的,仅能在组件内部访问。在声明状态变量时,需要指定其类型和初始值,也可以通过命名参数由父组件进行初始化。
其特性包括:
- 装饰器参数:无须提供;
- 同步类型:不与父组件中的任何变量同步;
- 允许装饰的变量类型:包括 Object、class、string、number、boolean、enum,以及这些类型的数组。支持 Date 类型。API 11 及以上版本支持 Map、Set 类型。同时也支持 undefined 和 null 类型;
- 类型支持场景:请参考相关文档观察变化;
- API 11 及以上版本支持联合类型,例如 string | number,string | undefined 或 ClassA | null;
- 使用 undefined 和 null 时,建议显式指定类型,以遵循 TypeScript 的类型校验。推荐写法:@State a: string | undefined = undefined。不推荐写法:@State a: string = undefined;
- 支持 ArkUI 框架定义的联合类型 Length、ResourceStr、ResourceColor;
- 类型必须指定,不支持使用 any 类型;
- 被装饰变量的初始值:必须本地初始化。
@State 变量传递/访问规则如下:
- 初始化:可为空,从父组件或本地初始化。若从父组件初始化,将覆盖本地初始化。
- 支持类型:可以从父组件传递常规变量(仅数值初始化,不触发 UI 刷新),以及由 @State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink 和 @LocalStorageProp 装饰的变量来初始化子组件的 @State。
- 初始化子组件:由 @State 装饰的变量可以用来初始化子组件的常规变量、@State、@Link、@Prop、@Provide。
- 访问限制:不支持组件外访问,变量只能在组件内部访问。
初始化规则如下图所示:

图 1 初始化规则
注意,并非所有状态变量的变更都会导致 UI 更新,只有框架能观察到的修改才会触发 UI 更新。接下来将阐述哪些修改会被框架观察到,以及框架在观察到变化后的行为。
1) 当 @State 装饰的数据类型为 boolean、string、number 时,可以观察到数值的变化。也就是说,当数据类型为 boolean、string、number 时,如果数据发生改变,那么页面也会同步更新。示例如下:
@Entry @Component struct State_demo { @State bool: boolean = false; @State message: string = '你好'; @State num: number = 1; build() { Row() { Column() { Button('修改数据') .onClick(() => { this.bool = !this.bool; this.message = '数据被修改了'; this.num++; }); // 修改string类型 Text(this.message) .fontSize(20); // 修改number类型 Text(this.num.toString()) .fontSize(20); // 修改boolean类型 Text(this.bool.toString()) .fontSize(20); } .width('100%') .height('100%'); } } }在上述示例代码中,定义了 3 种数据类型,通过单击 Button 按钮来进行数据修改。需要注意的是,在 ArkTS 中所有需要渲染出来的内容必须是 string 格式,因此将 number 和 boolean 类型的数据通过 toString() 方法转换成字符串。效果如图 2 和图 3 所示。

图 2 @State 装饰器1

图 3 @State装饰器2
2) 当 @State 装饰的数据类型为 class 或者 Object 时,可以观察到自身赋值的变化和其属性赋值的变化,即 Object.keys(observedObject) 返回的所有属性。
示例如下:
// 声明一个 Testclass 类,包含一个字符串类型的 value 属性 class Testclass { value: string; // 构造函数,接收一个字符串参数并将其赋值给 value 属性 constructor(value: string) { this.value = value; } } // 声明一个 Model 类,包含两个公共属性:一个字符串类型的 value 和一个 Testclass 类型的 name class Model { public value: string; public name: Testclass; // 构造函数,接收一个字符串参数和一个 Testclass 实例,并分别赋值给 value 和 name 属性 constructor(value: string, a: Testclass) { this.value = value; this.name = a; } }
接下来使用定义的类型来观察数据的变化。首先使用 @State 装饰 Model,然后进行数据修改,以观察是否可以修改数据。示例代码如下:
// 定义一个名为 message 的 @State 变量,类型为 Model,初始值为一个新的 Model 实例,包含字符串 'Hello' 和一个 Testclass 实例,其参数为 'World' @State message: Model = new Model('Hello', new Testclass('World')); build() { // 创建一个 Row 组件,宽度为 100% Row() { // 创建一个 Column 组件 Column() { // 创建一个按钮,单击该按钮时修改 message 的值 Button('修改数据') .onClick(() => { // 将 message 的值更新为一个新的 Model 实例,包含字符串 'HI' 和一个 Testclass 实例,其参数为 '数据被修改了!' this.message = new Model('HI', new Testclass('数据被修改了')); }); // 创建一个文本组件,用于显示 message 的 value 属性值,字体大小为 20 Text(this.message.value) .fontSize(20); // 创建一个文本组件,用于显示 message 的 name 属性的 value 属性值,字体大小为 20 Text(this.message.name.value) .fontSize(20); } .width('100%') // 设置 Column 组件的宽度为 100% .height('100%'); // 设置 Row 组件的高度为 100% } }界面效果如图 4 和图 5 所示。

图 4 @State装饰器3

图 5 @State装饰器4
说明当 @State 装饰的是 class 类型时,数据的变化是可以被观察到的。
如果只修改 @State 装饰的某一个属性呢?示例代码如下:
// 创建一个名为 message 的 Model 实例,包含两个属性:value 和 name @State message: Model = new Model('Hello', new Testclass('World')); build() { // 创建一个 Row 布局 Row() { // 创建一个 Column 布局 Column() { // 创建一个按钮,单击该按钮后修改 message 的值和 name 的值 Button('修改数据') .onClick(() => { this.message.value = 'HI'; this.message.name.value = '数据被修改了'; }); // 显示 message 的 value 属性值,字体大小为 20 Text(this.message.value) .fontSize(20); // 显示 message 的 name 属性值,字体大小为 20 Text(this.message.name.value) .fontSize(20); } // 设置 Column 布局的宽度为 100% .width('100%') // 设置 Row 布局的高度为 100% .height('100%'); } }在示例代码中,我们通过以下代码来修改 @State 装饰的数据:
this.message.value = 'HI' this.message.name.value = '数据被修改了'效果图见图 4 和图 5,发现数据被修改后依旧可以被观察到。
3) 当装饰的对象是 array 时,可以观察到数组本身的赋值和添加、删除、更新数组的变化。
数组的操作在开发的过程中是比较常见的,通过下面的代码示例将会更好地理解关于数组的操作方案。
// 第一步:声明一个 Arrayclass 类,包含一个数值类型的属性 value class Arrayclass { value: number; // 构造函数,用于初始化 Arrayclass 实例的 value 属性 constructor(value: number) { this.value = value; } } // 第二步:使用 @State 装饰器创建一个 Arrayclass 类型的数组 cousomArray,并初始化为包含 4 个元素的数组 @State cousomArray: Arrayclass[] = [ new Arrayclass(1), new Arrayclass(2), new Arrayclass(3), new Arrayclass(4) ]; // 第三步:对数组进行操作 build() { Column() { // 赋值操作:单击按钮后,将 cousomArray 数组重新赋值为只包含一个元素(值为 11)的新数组 Button('赋值') .onClick(() => { this.cousomArray = [new Arrayclass(11)]; }); // 遍历 cousomArray 数组,显示每个元素的 value 值 ForEach(this.cousomArray, (item: Arrayclass, index: number) => { Text(item.value.toString()) .fontSize(20); }); } .width('100%'); }注意,代码中用了一个新的属性 ForEach ,这个属性的作用是遍历数组,将数组的每一项内容进行展示。
案例代码主要是实现当单击按钮时,将自定义数组对象的内容进行替换,效果如图 6 和图 7 所示。

图 6 数组的赋值操作1

图 7 数组的赋值操作2
修改数组中的某一项数据,代码如下:
Button('修改第一项数据').onClick(() => { this.cousomArray[0] = new ArrayClass(11) })单击按钮直接对数组中的第一项数据进行修改,效果如图 8 和图 9 所示。

图 8 数组的替换操作1

图 9 数组的替换操作2
删除数组中的数据,代码如下:
Button('删除数据').onClick(()=>{ this.cousomArray.pop() })有 JavaScript 基础的读者应该很清楚,案例中我们使用了 pop() 方法对数组进行数据的删除操作,效果如图 10 和图 11 所示。

图 10 数组的删除操作1

图 11 数组的删除操作2
为数组新增数据,代码如下:
Button('新增数据').onClick(()=>{ this.cousomArray.push(new ArrayClass(20)) })采用数组的操作方法 push 为 cousomArray 这个数组项新增了一个数据,效果如图 12 和图 13 所示。

图 12 数组的新增操作1

图 13 数组的新增操作2
我们通过 .value 来获取数组中对应的数据,然后直接进行赋值操作,是否可以呢?直接对数组中的属性进行赋值,代码如下:
Button('直接赋值').onClick(()=>{ this.cousomArray[0].value = 10 })效果如下图所示。可以发现这种赋值方式是不可以的。

图 14 对数组中的属性进行赋值