赞
踩
[写在前面: 文章多处用到gif动图,如未自动播放,请点击图片]
衔接上一篇:鸿蒙开发-ArkTS 语言-基础语法
变量必须被装饰器装饰才能成为状态变量,状态变量的改变才能导致 UI 界面重新渲染
概念 | 描述 |
---|---|
状态变量 | 被状态装饰器装饰的变量,改变会引起UI的渲染更新。 |
常规变量 | 没有状态的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新。 |
数据源/同步源 | 状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据。 |
命名参数机制 | 父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段。示例:CompA: ({ aProp: this.aProp })。 |
从父组件初始化 | 父组件使用命名参数机制,将指定参数传递给子组件。本地初始化的默认值在有父组件传值的情况下,会被覆盖。 |
Components部分的装饰器为组件级别的状态管理,Application部分为应用的状态管理。可以通@StorageLink/@LocalStorageLink和@StorageProp/@LocalStorageProp实现应用和组件状态的双向和单向同步。图中箭头方向为数据同步方向,单箭头为单向同步,双箭头为双向同步。
图片来源:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-state-management-overview-0000001524537145-V2
状态管理分三种:
组件级别,即components级别:
应用级别,即 Application 级别:
其他状态管理功能
@Watch用于监听状态变量的变化。
$$ 运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。
@State装饰的变量,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。
示例如下:
@Entry @Component struct Index { build() { Column() { Parent() } } } @Component struct MyComponent { @State count: number = 0; private increaseBy: number = 1; build() { Column(){ Row() { Text(`${this.count}`) .fontSize(108) } Row() { Button('click me hh').onClick(() => { this.count += this.increaseBy }) } } } } @Component struct Parent { build() { Column() { // 从父组件初始化,覆盖本地定义的默认值 MyComponent({ count: 2, increaseBy: 2 }) } } }
效果如下:
3.1.2 能观察的数据类型
@State count: number = 0;
this.count = 1; // 这种修改可以被观察到
class ClassA { public value: string; constructor(value: string) { this.value = value; } } 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'; // 嵌套属性的赋值观察不到 this.title.name.value = 'ArkUI';
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));
可以使用 @Prop 定义要从父级接受的变量。注意以下两点:
@Prop变量可以在子组件内修改,但修改后的变化不会同步回父组件中。
但当父组件中的数据源更改时,与之相关的@Prop装饰的变量都会自动更新。如果子组件已经在本地修改了@Prop装饰的相关变量值,而在父组件中对应的@State装饰的变量被修改后,子组件本地修改的@Prop装饰的相关变量值将被覆盖。
允许装饰的变量的类型:string、number、boolean、enum类型。
不允许的类型:any,undefined和null。
@Prop 初始化规则:
图示:
图片来源:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-prop-0000001473537702-V2
代码示例:
@Component struct CountDownComponent { @Prop count: number; costOfOneAttempt: number = 1; build() { Column() { if (this.count > 0) { Text(`儿子还有 ${this.count} 个萝卜头`) } else { Text('吃完了,要挨打了') } // @Prop装饰的变量不会同步给父组件 Button(`儿子偷吃了${this.costOfOneAttempt}个萝卜头`).onClick(() => { this.count -= this.costOfOneAttempt; }) } } } @Entry @Component struct ParentComponent { @State countDownStartValue: number = 10; build() { Column() { Text(`老爸还有 ${this.countDownStartValue} 个萝卜头`) // 父组件的数据源的修改会同步给子组件 Button(`一起买了1个萝卜头`).onClick(() => { this.countDownStartValue += 1; }) // 父组件的修改会同步给子组件 Button(`一起吃了1个萝卜头`).onClick(() => { this.countDownStartValue -= 1; }) CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) } } }
效果如下:
可以使用 @Link 装饰器进行数据双向同步:
@Link初识化规则:
图示:
图片来源:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-link-0000001524297305-V2
代码示例:
class GreenButtonState { width: number = 0; constructor(width: number) { this.width = width; } } // class类型的子组件GreenButton,通过@Link装饰变量greenButtonState同步到父组件Index中的@State变量greenButtonState @Component struct GreenButton { @Link greenButtonState: GreenButtonState; build() { Button('绿色按钮') .width(this.greenButtonState.width) .height(150.0) .fontWeight(FontWeight.Bold) .backgroundColor('#ff81c123') .onClick(() => { if (this.greenButtonState.width < 700) { // 更新class的属性,变化可以被观察到同步回父组件 this.greenButtonState.width += 125; } else { // 更新class,变化可以被观察到同步回父组件 this.greenButtonState = new GreenButtonState(100); } }) } } // 简单类型的子组件YellowButton,通过@Link装饰变量yellowButtonState同步到父组件Index中的@State变量yellowButtonProp @Component struct YellowButton { @Link yellowButtonState: number; build() { Button('黄色按钮') .width(this.yellowButtonState) .height(150.0) .fontWeight(FontWeight.Bold) .backgroundColor('#ffdb944d') .onClick(() => { // 子组件的简单类型可以同步回父组件 this.yellowButtonState += 50.0; }) } } // 父组件Index,初始化并管理子组件的状态变量 @Entry @Component struct Index { @State greenButtonState: GreenButtonState = new GreenButtonState(300); @State yellowButtonProp: number = 100; build() { Column() { // 按钮:父组件修改greenButtonState,同步到GreenButton子组件 Button('父视图: 设置 绿色按钮') .onClick(() => { this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100; }) // 按钮:父组件修改yellowButtonProp,同步到YellowButton子组件 Button('父视图: 设置 黄色按钮') .onClick(() => { this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 100 : 100; }) // 子组件:GreenButton,通过@Link同步更新父组件@State的greenButtonState GreenButton({ greenButtonState: $greenButtonState }) // 子组件:YellowButton,通过@Link同步更新父组件@State的yellowButtonProp YellowButton({ yellowButtonState: $yellowButtonProp }) } } }
图示:
为了避免在组件中多次传递变量,推出了一种使某些变量能被所有后代组件使用的装饰器 @Provide,后代组件可以使用 @Consume去获取 @Provide 的值,而@State和@Link 只能在父子组件中传递。
// 通过相同的变量名绑定
@Provide a: number = 0;
@Consume a: number;
// 通过相同的变量别名绑定
@Provide('a') b: number = 0;
@Consume('a') c: number;
@Provide 和 @Consume 是双向同步的。
代码示例:
@Component struct CompD { // @Consume装饰的变量通过相同的属性名绑定其祖先组件CompA内的@Provide装饰的变量 @Consume tickets: number; build() { Column() { Text(`评审投票数(${this.tickets})`) Button(`评审投票数(${this.tickets}),+1`) .onClick(() => this.tickets += 1) } .width('50%') } } @Component struct CompC { build() { Row({ space: 5 }) { CompD() CompD() } } } @Component struct CompB { build() { CompC() } } @Entry @Component struct Index { // @Provide装饰的变量reviewVotes由入口组件CompA提供其后代组件 @Provide tickets: number = 0; build() { Column() { Button(`评审投票数(${this.tickets}),+1`) .onClick(() => this.tickets += 1) CompB() } } }
图示:
对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。
@ObjectLink和@Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步:
初始化图示:
图片来源:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V2/arkts-observed-and-objectlink-0000001473697338-V2#section2976114355019
以二维数组监听为例:
@Observed class StringArray extends Array<String> { } @Component struct ItemPage { @ObjectLink itemArr: StringArray; build() { Row() { Text('ItemPage') .width(100).height(100) ForEach(this.itemArr, item => { Text(item) .width(100).height(100) }, item => item ) } } } @Entry @Component struct Index { @State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()]; build() { Column() { ItemPage({ itemArr: this.arr[0] }) ItemPage({ itemArr: this.arr[1] }) ItemPage({ itemArr: this.arr[2] }) Divider() ForEach(this.arr, itemArr => { ItemPage({ itemArr: itemArr }) }, itemArr => itemArr[0] ) Divider() Button('update') .onClick(() => { console.error('Update all items in arr'); if (this.arr[0][0] !== undefined) { // 正常情况下需要有一个真实的ID来与ForEach一起使用,但此处没有 // 因此需要确保推送的字符串是唯一的。 this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`); this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`); this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`); } else { this.arr[0].push('Hello'); this.arr[1].push('World'); this.arr[2].push('!'); } }) } } }
效果如下:
ArkTS可以实现多种应用状态管理的能力,具体表现在以下几点:
UIAblility
内,也可以用于页面间状态共享有关 LocalStorage 介绍:
LocalStorage
实例,这些实例可以在页面内共享,也可以通过 GetShared
接口在 UIAbility
中创建的实例实现跨页面和 UIAbility
内的共享。@Entry
装饰的 @Component
实例可以被分配一个 LocalStorage
实例,而该组件的所有子组件实例将自动获得对该 LocalStorage
实例的访问权限。@Component
装饰的组件最多可以访问一个 LocalStorage
实例和 AppStorage
,而未被 @Entry
装饰的组件无法独立分配 LocalStorage
实例,只能接受父组件通过 @Entry
传递来的 LocalStorage
实例。LocalStorage
实例在组件树上可以被分配给多个组件,而其中的所有属性都是可变的。装饰器:
@LocalStorageProp(key)
是一个装饰器,用于在 ArkUI 组件框架中建立自定义组件的属性与 LocalStorage
中特定键对应属性之间的单向数据同步。
初始化与绑定: 在自定义组件初始化时,被 @LocalStorageProp(key)
装饰的变量通过给定的 key
绑定到相应的 LocalStorage
属性上,完成初始化。本地初始化是必要的,因为不能保证在组件初始化之前 LocalStorage
中是否存在给定的 key
。
本地修改和同步: 对于使用 @LocalStorageProp(key)
装饰的变量,本地的修改是允许的。但是需要注意,本地的修改永远不会同步回 LocalStorage
中。相反,如果 LocalStorage
中给定 key
的属性发生改变,这个改变会被同步给被 @LocalStorageProp(key)
装饰的变量,并覆盖本地的修改。
@LocalStorageLink
是一个装饰器,用于在 ArkUI 组件框架中建立自定义组件的状态变量与 LocalStorage
中特定键对应属性之间的双向数据同步。以下是相关信息的概述:
@LocalStorageLink(key)
与 LocalStorage
中给定 key
对应的属性建立双向数据同步。这包括本地的修改会同步回 LocalStorage
中,以及 LocalStorage
中的修改会被同步到所有绑定了相同 key
的属性上。@LocalStorageLink
不支持从父节点初始化,只能从 LocalStorage
中的 key
对应的属性初始化。如果没有对应的 key
,则使用本地默认值初始化。可用于初始化 @State
、@Link
、@Prop
、@Provide
,但不支持组件外访问语法示例:
let storage = new LocalStorage({ 'KeyA': 47 }); // 创建新实例并使用给定对象初始化
let keyA = storage.get('KeyA'); // keyA == 47
let link1 = storage.link('KeyA'); // link1.get() == 47
let link2 = storage.link('KeyA'); // link2.get() == 47
let prop = storage.prop('KeyA'); // prop.get() == 47
link1.set(48); // 双向绑定 link1.get() == link2.get() == prop.get() == 48
prop.set(1); // 单向绑定 prop.get() == 1;但是 link1.get() == link2.get() == 48
link1.set(49); // 双向绑定 link1.get() == link2.get() == prop.get() == 49
案例理解:
// 创建新实例并使用给定对象初始化 let localStorageInstance = new LocalStorage({ 'PropertyA': 47 }); @Component struct ChildComponent { // 与LocalStorage中的'PropertyA'属性建立双向绑定 @LocalStorageLink('PropertyA') linkedProperty2: number = 1; // 与LocalStorage中的'PropertyA'属性建立单向绑定 @LocalStorageProp('PropertyA') linkedProperty3: number = 1; build() { Row() { Button(`子组件的值 ${this.linkedProperty2}`) // 更改将同步至LocalStorage中的'PropertyA'以及ParentComponent.linkedProperty1 .onClick(() => this.linkedProperty2 += 1) // Local 中值变化,linkedProperty3 也会变,反之不会 Button(`子组件的值-单向 Prop ${this.linkedProperty3}`) .onClick(() => this.linkedProperty3 += 1) } } } // 使LocalStorage可被@Component组件访问 @Entry(localStorageInstance) @Component struct ParentComponent { // @LocalStorageLink变量装饰器与LocalStorage中的'PropertyA'属性建立双向绑定 @LocalStorageLink('PropertyA') linkedProperty1: number = 1; build() { Column({ space: 15 }) { Button(`父组件的值 ${this.linkedProperty1}`) // 初始化值从LocalStorage中获取,因为'PropertyA'已经初始化为47 .onClick(() => this.linkedProperty1 += 1) // @Component子组件自动获得对ParentComponent LocalStorage实例的访问权限。 ChildComponent() } } }
效果如下:
概述
AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建,为应用程序UI状态属性提供中央存储。AppStorage是应用级的全局状态共享,相当于整个应用的“中枢”,持久化数据PersistentStorage和 环境变量Environment都是通过AppStorage的中转,才可以和UI交互。AppStorage中的属性可以被双向同步,数据可以是存在于本地或远程设备上,并具有不同的功能,比如数据持久化。
基本用法:
// 在AppStorage中设置或创建 'PropA' 属性,初始值为 47 AppStorage.SetOrCreate('PropA', 47); let localStorageInstance: LocalStorage = new LocalStorage({ 'PropA': 17 }); // 从AppStorage中获取 'PropA' 的值,此时 propA 在 AppStorage 中为 47,在 LocalStorage 中为 17 let propAFromAppStorage: number = AppStorage.Get('PropA'); // 使用AppStorage的Link方法创建两个链接(link1和link2),以同步 'PropA' 的值 let link1: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); let link2: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // 使用AppStorage的Prop方法创建一个属性(prop),以单向同步 'PropA' 的值 let prop: SubscribedAbstractProperty<number> = AppStorage.Prop('PropA'); // 修改 link1,演示双向绑定的效果,所有其他绑定到相同键的变量都同步更新 link1.set(48); // link1.get() == link2.get() == prop.get() == 48 // 修改属性 prop,演示单向绑定的效果,只有属性本身更新,其他变量不受影响 prop.set(1); // prop.get() == 1;但是 link1.get() == link2.get() == 48 // 再次修改 link1,验证双向绑定,所有绑定到相同键的变量都同步更新 link1.set(49); // link1.get() == link2.get() == prop.get() == 49 // 使用 'PropA' 的值从 LocalStorage 中获取,此时为 17 let valueFromLocalStorage: number = localStorageInstance.get('PropA'); // 设置 'PropA' 的值为 101 localStorageInstance.set('PropA', 101); // 从 LocalStorage 中获取 'PropA' 的值,此时为 101 let valueFromLocalStorageAfterSet: number = localStorageInstance.get('PropA'); // 从 AppStorage 中获取 'PropA' 的值,此时为 49 let valueFromAppStorage: number = AppStorage.Get('PropA'); // 获取 link1 的值,此时为 49 let valueFromLink1: number = link1.get(); // 获取 link2 的值,此时为 49 let valueFromLink2: number = link2.get(); // 获取 prop 的值,此时为 49 let valueFromProp: number = prop.get();
LocalStorage和AppStorage都是运行时的内存,但是在应用退出再次启动后,依然能保存选定的结果,是应用开发中十分常见的现象,这就需要用到PersistentStorage。
注意:
代码示例:
PersistentStorage.PersistProp('userScore', 100); @Entry @Component struct Game { @State message: string = 'Welcome to the Game' @StorageLink('userScore') userScore: number = 50 build() { Row() { Column() { Text(this.message) Text(`你的得分: ${this.userScore}`) .onClick(() => { this.userScore += 10; }) } } } }
Environment设备环境查询用于查询设备运行环境参数,是ArkUI框架在应用程序启动时创建的单例对象,它为AppStorage提供了一系列描述应用程序运行状态的属性。Environment的所有属性都是不可变的(即应用不可写入),所有的属性都是简单类型。
使用场景
Environment.EnvProp
将设备的语言设置为英语 'zh'
。然后,@StorageProp
将设备语言与AppStorage
中的 deviceLanguage
建立了单向同步。
// 将设备的语言code存入AppStorage Environment.EnvProp('deviceLanguage', 'zh'); @Entry @Component struct Main { // 使用@StorageProp链接到Component中 @StorageProp('deviceLanguage') selectedLanguage: string = 'en'; build() { Row() { Column() { Text('Device Language:') Text(this.selectedLanguage) } } } } // 应用逻辑使用Environment // 从AppStorage获取单向绑定的languageCode的变量 const lang: SubscribedAbstractProperty<string> = AppStorage.Prop('deviceLanguage'); if (lang.get() === 'zh') { console.info('你好'); } else { console.info('Hello!'); }
@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。
注意:
示例:
@Component struct TotalView { @Prop @Watch('onCountUpdated') count: number; @State total: number = 0; // @Watch cb onCountUpdated(propName: string): void { this.total += this.count; } build() { Text(`Total: ${this.total}`) } } @Entry @Component struct CountModifier { @State count: number = 0; build() { Column() { Button('add to basket') .onClick(() => { this.count++ }) TotalView({ count: this.count }) } } }
$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。
内部状态具体指什么取决于组件。
@Entry @Component struct bindPopupPage { @State customPopup: boolean = false; build() { Column() { Button('Popup') .margin(20) .onClick(() => { this.customPopup = !this.customPopup }) .bindPopup($$this.customPopup, { message: 'showPopup' }) } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。