1. 状态管理
1.1 基本概念
用户构建一个UI界面,会依赖数据进行渲染,所依赖的这些数据可以称为状态。状态的变化会让UI重新渲染,这在ArkUI中统称为状态管理机制。
在了解状态管理机制之前,首先需要先了解下一些基本的概念:
- 状态变量:被状态装饰器装饰的变量,状态变量值的改变会引起UI的渲染更新;
- 常规变量:没有被状态装饰器装饰的变量,通常应用于辅助计算。它的改变永远不会引起UI的刷新;
- 数据源/同步源:状态变量的原始来源,可以同步给不同的状态数据。通常意义为父组件传给子组件的数据;
- 命名参数机制:父组件通过指定参数传递给子组件的状态变量,为父子传递同步参数的主要手段;
- 从父组件初始化:父组件使用命名参数机制,将指定参数传递给子组件。子组件初始化的默认值在有父组件传值的情况下,会被覆盖;
- 初始化子组件:父组件中状态变量可以传递给子组件,初始化子组件对应的状态变量;
- 本地初始化:在变量声明的时候赋值,作为变量的默认值;
@Component
struct MyComponent {
// number用@State修饰,是状态变量;
@State count: number = 0;
// increaseBy是常规变量,并且进行了本地初始化;
private increaseBy: number = 1;
build() {
}
}
@Component
struct Parent {
build() {
Column() {
// 从父组件初始化,覆盖本地定义的默认值;
// 初始化子组件,并且将父组件中的状态变量传递给了子组件;
// count: 1,为数据源;
MyComponent({ count: 1, increaseBy: 2 }) // 命名参数机制,父子组件传递同步参数;
}
}
}
在ArkUI中,自定义组件中可以拥有变量,这些变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。如果不使用状态变量,UI只能在初始化时渲染,后续将不会再刷新。
- View(UI):UI渲染,指将build()方法和@Builder装饰的方法内的UI描述映射到界面;
- State:状态,指驱动UI更新的数据。用户通过触发组件的事件方法,改变状态数据。状态数据的改变,会引起UI的重新渲染;
简单来说,这就是View和State的双向绑定,当State(可以理解为数据)变化的时候,View会自动的跟着变化,重新渲染,这就省去了我们拿到数据之后,再手动更新View展示的逻辑。对比于iOS原生框架,这真的是太方便了。
2. 场景概览
这里是关于状态管理的一个总结,会列出ArkTS支持的几种方式以及特点,先有个大概印象。
所有的装饰器可以大致分为:
- 管理组件拥有状态的装饰器:组件级别的状态管理,可以观察组件内变化,和不同组件层级的变化,但需要唯一观察同一个组件树上,即同一个页面内。
装饰器 | 描述 | 使用场景 |
---|---|---|
@State | 装饰的变量是组件自己内部的状态数据,这些状态数据的改变会调用组件的build方法进行UI刷新 | 组件内部自己使用,可以与其他装饰器配合使用 |
@State @Prop | 和@State类似,但@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,组件内部可以修改@Prop变量,但不会通知父组件,是单向绑定的 | 父传子,单向传递 |
@Provide @Consume | @Provide/@Consume装饰的变量用于跨组件层级(多层组件)同步状态变量,可以不需要通过参数命名机制传递,通过alias(别名)或者属性名绑定 | 父传子孙,跨层级双向传递 |
- 管理应用拥有状态的装饰器:应用级别的状态管理,可以观察不同页面,甚至不同UIAbility的状态变化,是应用内全局的状态管理。
装饰器 | 描述 | 使用场景 |
---|---|---|
LocalStorage | 多个页面之间的UI状态存储,可以跨页面共享数据; | 想要在多个页面共享数据,或者在逻辑中操作数据触发UI更新 |
AppStorage | App全局共享的UI状态存储 | 在App全局内跨模块使用 |
PersistentStorage | 可以将AppStorage数据持久化 | UI的状态如果想持久化选这个 |
Environment | 用于管理设备的环境变量 | 多语言、多主题会使用 |
3. 管理组件状态的装饰器
3.1 @State 组件内状态
使用@State装饰的变量,称为状态变量,有以下特点:
- 是私有的,只能从组件内部访问;
- 声明时必须指定类型和在本地初始化;
- 状态变量的改变会让依赖该状态变量的UI重新渲染;
举个例子:
@Entry
@Component
struct Index {
// 声明一个状态变量
@State name: string = "hello"
build() {
Column() {
// 默认展示的是 hello
Text(this.name)
Button("Change")
.onClick(()=>{
// 点击按钮修改状态变量,Text展示的内容也会随之变化
this.name = "hello world"
})
}
.margin({top: 20})
}
}
可以在DevEco上运行下上面的例子,这只是最基本的使用。另外,并不是状态变量的所有改变都会引起UI的刷新,这取决于装饰的数据类型:
- 对于boolean string number类型,可以观察到数值的变化;
- 对于class或者Object类型,只能观察到自身和自身属性的变化(如果b对象的是a对象的一个属性,a.b.xxx的改变是观察不到的);
- 对于array对象,可以观察到数组本身的赋值和添加、删除、更新数组的变化(数组中item的属性更改是观察不到的);
- 对于Date对戏那个,可以观察到Date整体的赋值;
- 对于Map,可以观察到Map整体的赋值,可以通过set clear delete 更新Map的值;
- 对于Set,可以观察到Set整体的赋值,可以通过add clear delete更新Set的值;
状态改变只会引起和该状态变量相关组件的重新渲染。当状态变量改变时,会查询依赖该状态变量的组件,并且执行该组件的更新方法,组件进行更新渲染。
3.2 @Prop 父子单向同步
我们会有这样的需求:父组件的数据会传递给子组件使用,当父组件的数据更新时,希望也会触发子组件的UI更新,此时就需要用到@Prop了。
简单来说就是:
- 对于父组件,还是使用@State声明一个状态变量;
- 对于子组件,使用@Prop声明一个变量,但是不能初始化;
这样@Prop装饰的变量就会和父组件建立单向的同步关系,@Prop变量可以在子组件内修改,但是修改之后不会同步给父组件。
来看个简单示例:
class TestModel {
name: string = 'zhangsan'
}
@Entry
@Component
struct Parent {
// 父组件的状态变量
@State model: TestModel = new TestModel()
build() {
Column() {
Text(this.model.name)
Button('点击修改名字')
.onClick(() => {
// 父组件中修改状态变量,会影响到子组件
this.model.name = "李四"
})
Child({name: this.model.name})
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
// 子组件这里使用@Prop
@Prop name: string
build() {
Column() {
Text(this.name)
Button('点击修改名字')
.onClick(() => {
// 子组件中修改状态变量,只会影响到当前组件
this.name = "王五"
})
}
}
}
使用@Prop修饰变量有以下限制:
- @Prop装饰变量时会进行深拷贝,在拷贝过程中,除了基本类型、Map、Set、Date、Array外,都会丢失类型。
- @Prop装饰器不能在@Entry装饰的自定义组件中使用;
@Prop装饰的数据可以观察到以下变化:
- 装饰类型是 Object class string number boolean enum类型时,都可以观察到赋值的变化;
- 装饰类型是Object或者class复杂类型时,只会观察第一层的属性的变化;
- 对于嵌套场景,如果class是被@Observed装饰,可以观察到class属性的变化;
- 装饰类型是Array,可以观察到数组本身的赋值、添加、删除和更新;
- 使用父组件中的@State变量的值初始化子组件中的@Prop变量,@State变量变化时,该变量值也会同步给@Prop变量;
- @Prop装饰的变量的修改不会影响其数据源@State装饰变量的值;
- 除了@State,数据源也可以用@Link或@Prop装饰,对@Prop的同步机制是相同的;
- 可以观察到Date、Map、Set类型的变量的整体赋值,通过其方法可以更新对象的值;
3.3 @Link 父子双向同步
子组件中被@Link装饰的变量和其父组件中对应的数据源建立双向数据绑定,@Link装饰的变量和其父组件中的数据源共享相同的值。也就是说,子组件中修改@Link装饰的变量,会引起父组件中状态变量的变化。
对于不同数据类型的观察,和上面@State @Props类似,这里就不一一列举了。
直接看个小例子吧:
class TestModel {
name: string = '张三'
}
@Entry
@Component
struct Index {
@State model: TestModel = new TestModel()
build() {
Column() {
Text(this.model.name)
Button('点击修改名字')
.onClick(() => {
this.model.name = "李四"
})
Child({model: this.model})
}
.width('100%')
.height('100%')
}
}
@Component
struct Child {
// 使用@Link装饰的变量不能被本地初始化
// @Link修饰的变量和父组件需要同类型
@Link model: TestModel
build() {
Column(){
Text(this.model.name)
Button('点击修改名字')
.onClick(() => {
// 同时会修改父组件中的状态
this.model.name = "王五"
})
}
}
}
4 管理应用拥有的状态
如果要实现应用级的,或者想要在多个页面进行状态数据共享,就需要用到应用级别的状态管理。
4.1 LocalStorage 页面级UI状态存储
4.1.1 LocalStorage介绍
- 组件树的根节点(被@Entry装饰的@Component),可以被分配一个LocalStorage实例,其所有的子组件将自动获得对该LocalStorage实例的访问权限;
- LocalStorage支持UIAbility实例内多个页面间状态共享;
- 应用程序可以创建多个LocalStorage实例,LocalStorage实例可以在页面内共享,也可以通过GetShared接口,实现跨页面、UIAbility实例内共享;
- 被@Component装饰的组件最多可以访问一个LocalStorage实例和AppStorage,未被@Entry装饰的组件不可被独立分配LocalStorage实例,只能接受父组件通过@Entry传递来的LocalStorage实例;
- LocalStorage中的所有属性都是可变的;
LocalStorage根据与@Component装饰的组件的同步类型不同,提供了两个装饰器:
- @LocalStorageProp:@LocalStorageProp装饰的变量和与LocalStorage中给定属性建立单向同步关系。
- @LocalStorageLink:@LocalStorageLink装饰的变量和在@Component中创建与LocalStorage中给定属性建立双向同步关系。
4.1.2 @LocalStorageProp和LocalStorage单向同步的简单场景
// 创建新实例并使用给定对象初始化
let para: Record<string, number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 使LocalStorage可从@Component组件访问
@Entry(storage)
@Component
struct CompA {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storageProp1: number = 1;
build() {
Column({ space: 15 }) {
// 点击后从47开始加1,只改变当前组件显示的storageProp1,不会同步到LocalStorage中
Button(`Parent from LocalStorage ${this.storageProp1}`)
.onClick(() => {
this.storageProp1 += 1
})
Child()
}
}
}
@Component
struct Child {
// @LocalStorageProp变量装饰器与LocalStorage中的'PropA'属性建立单向绑定
@LocalStorageProp('PropA') storageProp2: number = 2;
build() {
Column({ space: 15 }) {
// 当CompA改变时,当前storageProp2不会改变,显示47
Text(`Parent from LocalStorage ${this.storageProp2}`)
}
}
}
在上面的示例中,CompA组件和Child组件分别在本地创建了storage的PropA对应属性的单向同步的数据,我们可以看到:
- 在CompA中点击按钮对this.storProp1的修改,只会在CompA中生效,并不会同步回storage;
- 在Child组件中,Text绑定的storProp2依旧显示47;
Record和Map类似,Map和Record的区别:
- Map是JavaScript中的原生数据结构,而Record是TypeScript中的一个工具类型;
- Map中的键可以是任意类型,而Record中的键是通过类型参数指定的;
- Map中相同的键只能存在一个,后面的会覆盖前面的;而Record中指定的键是固定的,值的类型也是固定的;
4.1.3 @LocalStorageLink和LocalStorage双向同步的简单场景
// 构造LocalStorage实例
let para: Record<string, number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para);
// 调用link(api9以上)接口构造'PropA'的双向同步数据,linkToPropA 是全局变量
let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink('PropA')在CompA自定义组件中创建'PropA'的双向同步数据,初始值为47,因为在构造LocalStorage已经给“PropA”设置47
@LocalStorageLink('PropA') storageLink: number = 1;
build() {
Column() {
Text(`incr @LocalStorageLink variable`)
// 点击“incr @LocalStorageLink variable”,this.storageLink加1,改变同步回storage,全局变量linkToPropA也会同步改变
.onClick(() => {
this.storageLink += 1
})
// 并不建议在组件内使用全局变量linkToPropA.get(),因为可能会有生命周期不同引起的错误。
Text(`@LocalStorageLink: ${this.storageLink} - linkToPropA: ${linkToPropA.get()}`)
}
}
}
上面构建了一个简单的双向同步的场景,当点击”incr @LocalStorageLink variable“修改this.storageLink状态变量的值时,会同步修改LocalStorage里面的数据。
4.1.4 应用逻辑中使用LocalStorage
上面双向同步的示例中可以看到点击事件也会修改linkToPropA的值:
let linkToPropA: SubscribedAbstractProperty<object> = storage.link('PropA');
其实我们在应用逻辑内使用LocalStorage也可以实现类似前面UI的单向、双向绑定:
let para: Record<string,number> = { 'PropA': 47 };
let storage: LocalStorage = new LocalStorage(para); // 创建新实例并使用给定对象初始化
let propA: number | undefined = storage.get('PropA') // propA == 47
let link1: SubscribedAbstractProperty<number> = storage.link('PropA'); // link1.get() == 47
let link2: SubscribedAbstractProperty<number> = storage.link('PropA'); // link2.get() == 47
let prop: SubscribedAbstractProperty<number> = storage.prop('PropA'); // prop.get() == 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
4.1.5 多个视图、页面中共享LocalStorage实例
如果想要在多个页面中共享Storage实例,可以在所属的UIAbility中创建LocalStorage实例,并调用windowStage.loadContent,如下:
// EntryAbility.ets
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
para:Record<string, number> = { 'PropA': 47 };
storage: LocalStorage = new LocalStorage(this.para);
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}
然后就可以在页面中通过getShared()方法获取到共享的LocalStorage实例,可以看下面这个例子,Index页面使用了propA的值,当跳转到Page页面,修改propA的值,返回Index页面之后,页面中的propA的值也会同步修改:
// index.ets
import router from '@ohos.router';
// 通过getShared接口获取stage共享的LocalStorage实例
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct Index {
// can access LocalStorage instance using
// @LocalStorageLink/Prop decorated variables
@LocalStorageLink('PropA') propA: number = 1;
build() {
Row() {
Column() {
Text(`${this.propA}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("To Page")
.onClick(() => {
router.pushUrl({
url: 'pages/Page'
})
})
}
.width('100%')
}
.height('100%')
}
}
// Page.ets
import router from '@ohos.router';
let storage = LocalStorage.getShared()
@Entry(storage)
@Component
struct Page {
@LocalStorageLink('PropA') propA: number = 2;
build() {
Row() {
Column() {
Text(`${this.propA}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button("Change propA")
.onClick(() => {
this.propA = 100;
})
Button("Back Index")
.onClick(() => {
router.back()
})
}
.width('100%')
}
}
}
目前遇到了一个问题,想要在单独的TS文件中实现某个方法,操作更新主页面的状态,前几天一直没有找到合适的解决方案,目前尝试使用Storage可以解决这个问题:
// 单独创建一个文件提供修改状态的方法
// util.ets
export function changeCount() {
let storage = LocalStorage.getShared()
storage.set("PropA", 100);
}
// 引入changeCount方法
import {changeCount} from '../common/utils/util';
// 在页面中使用Storage
let storage: LocalStorage = LocalStorage.getShared();
@Entry(storage)
@Component
struct Parent {
@LocalStorageLink('PropA') count: number = 1;
build() {
Column() {
Text(`count: ${this.count}`);
Button("Click")
.onClick(()=>{
changeCount();
})
}
}
}
4.2 AppStorage:应用全局的UI状态存储
AppStorage是应用全局的UI状态存储,是和应用的进程绑定的,由UI框架在应用程序启动时创建的单例,为应用程序UI状态属性提供中央存储。持久化数据PersistentStorage和环境变量Environment都是通过AppStorage中转,才可以和UI交互。
AppStorage支持应用的主线程内多个UIAbility实例间的状态共享。并且可以被双向同步,数据可以是存在本地或远程设备上,并具有不同的功能。如果希望这些数据在UI中使用,需要使用@StorageProp和@StorageLink。
从官方文档中来看AppStorage和LocalStorage的使用方法是很类似的。
4.2.1 在应用逻辑中使用AppStorage和LocalStorage
// AppStorage是单例,它的所有API都是静态的
AppStorage.setOrCreate('PropA', 47);
let storage: LocalStorage = new LocalStorage();
storage.setOrCreate('PropA',17);
// propA in AppStorage == 47, propA in LocalStorage == 17
let propA: number | undefined = AppStorage.get('PropA')
// link1.get() == 47
let link1: SubscribedAbstractProperty<number> = AppStorage.link('PropA');
// link2.get() == 47
let link2: SubscribedAbstractProperty<number> = AppStorage.link('PropA');
// prop.get() == 47
let prop: SubscribedAbstractProperty<number> = AppStorage.prop('PropA');
// two-way sync: link1.get() == link2.get() == prop.get() == 48
link1.set(48);
// one-way sync: prop.get() == 1; but link1.get() == link2.get() == 48
prop.set(1);
// two-way sync: link1.get() == link2.get() == prop.get() == 49
link1.set(49);
storage.get<number>('PropA') // == 17
storage.set('PropA', 101);
storage.get<number>('PropA') // == 101
AppStorage.get<number>('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
4.2.2 在UI内部使用AppStorage和LocalStorage
AppStorage.setOrCreate('PropA', 47);
let storage = new LocalStorage();
storage.setOrCreate('PropA', 48);
@Entry(storage)
@Component
struct CompA {
// @StorageLink变量装饰器和AppStorage配合使用,可以建立和AppStorage中的属性的双向同步
@StorageLink('PropA') storageLink: number = 1;
@LocalStorageLink('PropA') localStorageLink: number = 1;
build() {
Column({ space: 20 }) {
Text(`From AppStorage ${this.storageLink}`)
.onClick(() => {
this.storageLink += 1
})
Text(`From LocalStorage ${this.localStorageLink}`)
.onClick(() => {
this.localStorageLink += 1
})
}
}
}
4.3 PersistentStorage 持久化存储UI状态
4.3.1 介绍
LocalStorage和AppStorage都是运行时的内存,如果想要在应用重新启动后数据依然保留,就需要用到PersistentStorage了。
PersistentStorage是应用程序中的可选单例对象,它的作用就是持久化存储选定的AppStorage属性,确保这些属性在应用程序重新启动时的值与应用程序关闭的值相同。
应用程序通过API,决定哪些AppStorage属性可以借助PersistentStorage持久化,UI和业务逻辑不会直接访问PersistentStorage中的属性,所有属性访问都是对AppStorage的访问,AppStorage中的更改会自动同步到PersistentStorage中。
PersistentStorage和AppStorage中的属性建立双向同步,通常通过AppStorage访问PersistentStorage。
4.3.2 限制条件
PersistantStorage存储类型的限制:
- 支持number string boolean enum等简单类型
- 支持可以被JSON.stringfy()和JSON.parse()重构的对象
- 不支持嵌套对象
- 不支持undefined和null
PersistantStorage使用的限制:
- 避免持久化大型数据
- 避免持久化经常变化的变量
- 最好小于2kb,写入操作时同步的,影响UI线程
4.3.3 从AppStorage中访问PersistantStorage初始化的属性
// 初始化PersistentStorage
PersistentStorage.persistProp('aProp', 47);
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
// 使用StorageLink
@StorageLink('aProp') aProp: number = 48
build() {
Row() {
Column() {
Text(this.message)
// 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
Text(`${this.aProp}`)
.onClick(() => {
this.aProp += 1;
})
}
}
}
}
PersistProp初始化流程如下:
4.3.4 在PersistentStorage之前访问AppStorage中的属性
在调用PersistantStorage.persistProp或者persistProps之前使用接口访问AppStorage中的属性是错误的,因为这样的调用顺序会丢失上一次应用程序运行中的属性值:
let aProp = AppStorage.setOrCreate('aProp', 47);
PersistentStorage.persistProp('aProp', 48);
应用在非首次运行时,先执行AppStorage.setOrCreate(‘aProp’, 47):属性aProp在AppStorage中创建,其类型为number,值为47,aProp是持久化的属性,所以会被写回到PersistentStorage磁盘中,上次存储的值会丢失;
PersistentStorage.persistProp(‘aProp’, 48):在PersistentStorage中查找到“aProp”,值为刚刚使用AppStorage接口写入的47;
4.3.5 在PersistentStorage之后访问AppStorage中的属性
开发者可以先判断是否需要覆盖上一次保存在PersistantStorage中的值,如果需要覆盖,再调用AppStorage的接口进行修改:
// 在读取PersistentStorage储存的数据后判断“aProp”的值是否大于50,如果大于50的话使用AppStorage的接口设置为47。
PersistentStorage.persistProp('aProp', 48);
if (AppStorage.get('aProp') > 50) {
// 如果PersistentStorage存储的值超过50,设置为47
AppStorage.setOrCreate('aProp',47);
}
4.4 Environment: 设备环境查询
如果需要应用程序运行的设备的环境参数,做出不同的场景判断,比如多语言,暗黑模式等,需要使用到Environment设备环境查询了。
Environment是ArkUI框架在应用程序启动时创建的单例对象,所有属性都是不可变的(应用不可写入),所有的属性都是简单类型。
4.4.1 内置参数
4.4.2 从UI中访问Environment参数:
- 使用Environment.envProp将设备运行的环境变量存入AppStorage中:
// 将设备的语言code存入AppStorage,默认值为en
Environment.envProp('languageCode', 'en');
- 可以使用@StorageProp链接到Component中:
@StorageProp('languageCode') lang : string = 'en';
设备环境到Component的更新链:Environment --> AppStorage -->Component。
@StorageProp关联的环境参数可以在本地更改,但不能同步回AppStorage中,因为应用对环境变量参数是不可写的,只能在Environment中查询。
4.4.3 应用逻辑使用Environment
// 使用Environment.EnvProp将设备运行languageCode存入AppStorage中;
Environment.envProp('languageCode', 'en');
// 从AppStorage获取单向绑定的languageCode的变量
const lang: SubscribedAbstractProperty<string> = AppStorage.prop('languageCode');
if (lang.get() === 'zh') {
console.info('你好');
} else {
console.info('Hello!');
}
4.4.4 限制条件
Environment和UIContext相关联,需要在UIContext明确的时候才可以调用。可以通过在runScopedTask里明确上下文。如果没有在UIContext明确的地方调用,将导致无法查询到设备环境数据。
5. 其它状态管理
除了组件状态管理和应用状态管理,ArkTS还提供了@Watch和$$来为开发者提供更多功能;
- @Watch用于监听状态变量的变化;
$$
运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步;
5.1 @Watch装饰器:状态变量更改通知
如果我们想要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数;@Watch在ArkUI框架内部判断数值有无更新用的是严格相等(===)。
观察变化和行为表现:
- 当观察到状态变量变化时,对应的@Watch的回调方法将被处罚;
- @Watch方法在自定义组件的属性变更之后同步执行;
- 如果在@Watch的方法里改变了其它的状态变量,也会引起状态变更和@Watch的执行;
- 在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变,只有在后续状态改变时,才会调用@Watch回调方法;
5.1.1 @Watch和自定义组件更新
@Component
struct TotalView {
// 使用@Watch装饰器观察count的变化
@Prop @Watch('onCountUpdated') count: number = 0;
@State total: number = 0;
// @Watch绑定的回调
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 })
}
}
}
5.2 $$语法:内置组件双向同步
$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。内部状态具体指什么取决于组件,例如 TextInput组件的text参数。
类似于Vue中的v-model???
简单的使用示例:
// xxx.ets
@Entry
@Component
struct TextInputExample {
@State text: string = ''
controller: TextInputController = new TextInputController()
build() {
Column({ space: 20 }) {
Text(this.text)
TextInput({ text: $$this.text, placeholder: 'input your word...', controller: this.controller })
.placeholderColor(Color.Grey)
.placeholderFont({ size: 14, weight: 400 })
.caretColor(Color.Blue)
.width(300)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}
当用户在输入时,Text展示的内容也会同步进行刷新。
当前$$支持的组件有:
5.3 @Track装饰器 class对象属性级更新
@Track应用于class对象的属性级更新,@Track装饰的属性变化时,只会触发该属性关联的UI更新。
@Track是class对象的属性装饰器,当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新,而未被标记的属性不能在UI中使用。
5.3.1 简单示例
使用@Track装饰器可以避免冗余刷新:
class LogTrack {
@Track str1: string;
@Track str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = 'World';
}
}
class LogNotTrack {
str1: string;
str2: string;
constructor(str1: string) {
this.str1 = str1;
this.str2 = '世界';
}
}
@Entry
@Component
struct AddLog {
@State logTrack: LogTrack = new LogTrack('Hello');
@State logNotTrack: LogNotTrack = new LogNotTrack('你好');
isRender(index: number) {
console.log(`Text ${index} is rendered`);
return 50;
}
build() {
Row() {
Column() {
Text(this.logTrack.str1) // UINode1
.fontSize(this.isRender(1))
.fontWeight(FontWeight.Bold)
Text(this.logTrack.str2) // UINode2
.fontSize(this.isRender(2))
.fontWeight(FontWeight.Bold)
Button('change logTrack.str1')
.onClick(() => {
this.logTrack.str1 = 'Bye';
})
Text(this.logNotTrack.str1) // UINode3
.fontSize(this.isRender(3))
.fontWeight(FontWeight.Bold)
Text(this.logNotTrack.str2) // UINode4
.fontSize(this.isRender(4))
.fontWeight(FontWeight.Bold)
Button('change logNotTrack.str1')
.onClick(() => {
this.logNotTrack.str1 = '再见';
})
}
.width('100%')
}
.height('100%')
}
}
上面这个示例中:
-
类LogTrack中的属性均被@Track装饰器装饰,点击按钮"change logTrack.str1",此时UINode1刷新,UINode2不刷新,只有一条日志输出,避免了冗余刷新。
Text 1 is rendered
-
类logNotTrack中的属性均未被@Track装饰器装饰,点击按钮"change logNotTrack.str1",此时UINode3、UINode4均会刷新,有两条日志输出,存在冗余刷新。
Text 3 is renderedText 4 is rendered
如果一个类中的属性很多,但是我们只依赖其中某一个属性,就可以将这个属性用@Track装饰,避免其他属性也触发刷新。
5.3.2 @Track和自定义组件刷新
下面这个示例展示组件更新和@Track的处理步骤,对象log是@State装饰的状态变量,logInfo是@Track的成员属性,其余成员属性都是非@Track装饰的,而且也不准备在UI中更新:
class Log {
@Track logInfo: string;
owner: string;
id: number;
time: Date;
location: string;
reason: string;
constructor(logInfo: string) {
this.logInfo = logInfo;
this.owner = 'OH';
this.id = 0;
this.time = new Date();
this.location = 'CN';
this.reason = 'NULL';
}
}
@Entry
@Component
struct AddLog {
@State log: Log = new Log('origin info.');
build() {
Row() {
Column() {
Text(this.log.logInfo)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
// The properties without @Track can be used in the event handler.
console.log('owner: ' + this.log.owner +
' id: ' + this.log.id +
' time: ' + this.log.time +
' location: ' + this.log.location +
' reason: ' + this.log.reason);
this.log.time = new Date();
this.log.id++;
this.log.logInfo += ' info.';
})
}
.width('100%')
}
.height('100%')
}
}
怎样学习鸿蒙?
首先必学的是开发语言 ArkTS,这是重中之重,然后就是ArkUI声明式UI开发、Stage模型、网络/数据库管理、分布式应用开发、进程间通信与线程间通信技术、OpenHarmony多媒体技术……。中间还有许多的知识点,都整理成思维导图来分享给大家~
此外,小编精心准备了一份联合鸿蒙官方发布笔记整理收纳的《鸿蒙开发学习笔记》,内容包含ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
==【有需要的小伙伴,可以扫描下方二维码免费领取!!!】 ==
![](https://img-blog.csdnimg.cn/img_convert/4b3b9e1511d699d1958009ce1af5d8e8.jpeg)
快速入门
- 开发准备
- 构建第一个ArkTS应用(Stage模型)
- 构建第一个ArkTS应用(FA模型)
- 构建第一个JS应用(FA模型)
开发基础知识
- 应用程序包基础知识
- 应用配置文件(Stage模型)
- 应用配置文件概述(FA模型)
资源分类与访问
- 资源分类与访问
- 创建资源目录和资源文件
- 资源访问
学习ArkTs语言
- 初识ArkTS语言
- 基本语法
- 状态管理
- 其他状态管理
- 渲染控制
基于ArkTS声明式开发范式
- UI开发(ArkTS声明式开发范式)概述
- 开发布局
- 添加组件
- 显示图片
- 使用动画
- 支持交互事件
- 性能提升的推荐方法
兼容JS的类Web开发范式
- 概述
- 框架说明
- 构建用户界面
- 常见组件开发指导
- 动效开发指导
- 自定义组件
Web组件
- 概述
- 设置基本属性和事件
- 并发
- 窗口管理
- WebGL
- 媒体
- 安全
- 网络与连接
- 电话服务
- 数据管理
- …
应用模型
- 概述
- Stage模型开发指导
- FA模型开发指导
2024完整鸿蒙学习资料领取方式:扫描下方二维码即可
![](https://img-blog.csdnimg.cn/img_convert/4b3b9e1511d699d1958009ce1af5d8e8.jpeg)