除了前面章节提到的组件状态管理和应用状态管理,ArkTS还提供了@Watch、$$运算符和@Track来为开发者提供更多功能:
-
@Watch:用于监听状态变量的变化。
-
$$运算符:给内置组件提供TS变量的引用,使得TS变量和内置组件的内部状态保持同步。
-
@Track:应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。
-
自定义组件冻结:当自定义组件处于非激活状态时,状态变量将不响应更新。
@Watch应用于对状态变量的监听。如果开发者需要关注某个状态变量的值是否改变,可以使用@Watch为状态变量设置回调函数。
说明
从API version 9开始,该装饰器支持在ArkTS卡片中使用。
从API version 11开始,该装饰器支持在元服务中使用。
概述
@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。
装饰器说明
@Watch补充变量装饰器 | 说明 |
---|---|
装饰器参数 | 必填。常量字符串,字符串需要有引号。是(string) => void自定义成员函数的方法的引用。 |
可装饰的自定义组件变量 | 可监听所有装饰器装饰的状态变量。不允许监听常规变量。 |
装饰器的顺序 | 建议@State、@Prop、@Link等装饰器在@Watch装饰器之前。 |
语法说明
类型 | 说明 |
---|---|
(changedPropertyName? : string) => void | 该函数是自定义组件的成员函数,changedPropertyName是被watch的属性名。 在多个状态变量绑定同一个@Watch的回调方法的时候,可以通过changedPropertyName进行不同的逻辑处理 将属性名作为字符串输入参数,不返回任何内容。 |
观察变化和行为表现
-
当观察到状态变量的变化(包括双向绑定的AppStorage和LocalStorage中对应的key发生的变化)的时候,对应的@Watch的回调方法将被触发;
-
@Watch方法在自定义组件的属性变更之后同步执行;
-
如果在@Watch的方法里改变了其他的状态变量,也会引起状态变更和@Watch的执行;
-
在第一次初始化的时候,@Watch装饰的方法不会被调用,即认为初始化不是状态变量的改变。只有在后续状态改变时,才会调用@Watch回调方法。
限制条件
-
建议开发者避免无限循环。循环可能是因为在@Watch的回调方法里直接或者间接地修改了同一个状态变量引起的。为了避免循环的产生,建议不要在@Watch的回调方法里修改当前装饰的状态变量;
-
开发者应关注性能,属性值更新函数会延迟组件的重新渲染(具体请见上面的行为表现),因此,回调函数应仅执行快速运算;
-
不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题。
使用场景
@Watch和自定义组件更新
以下示例展示组件更新和@Watch的处理步骤。count在CountModifier中由@State装饰,在TotalView中由@Prop装饰。
@Component
struct TotalView {
@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 })
}
}
}
处理步骤:
-
CountModifier自定义组件的Button.onClick点击事件自增count。
-
由于@State count变量更改,子组件TotalView中的@Prop被更新,其@Watch('onCountUpdated')方法被调用,更新了子组件TotalView 中的total变量。
-
子组件TotalView中的Text重新渲染。
@Watch与@Link组合使用
以下示例说明了如何在子组件中观察@Link变量。
class PurchaseItem {
static NextId: number = 0;
public id: number;
public price: number;
constructor(price: number) {
this.id = PurchaseItem.NextId++;
this.price = price;
}
}
@Component
struct BasketViewer {
@Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[];
@State totalPurchase: number = 0;
updateTotal(): number {
let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0);
// 超过100欧元可享受折扣
if (total >= 100) {
total = 0.9 * total;
}
return total;
}
// @Watch 回调
onBasketUpdated(propName: string): void {
this.totalPurchase = this.updateTotal();
}
build() {
Column() {
ForEach(this.shopBasket,
(item: PurchaseItem) => {
Text(`Price: ${item.price.toFixed(2)} €`)
},
(item: PurchaseItem) => item.id.toString()
)
Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
}
}
}
@Entry
@Component
struct BasketModifier {
@State shopBasket: PurchaseItem[] = [];
build() {
Column() {
Button('Add to basket')
.onClick(() => {
this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
})
BasketViewer({ shopBasket: $shopBasket })
}
}
}
处理步骤如下:
-
BasketModifier组件的Button.onClick向BasketModifier shopBasket中添加条目;
-
@Link装饰的BasketViewer shopBasket值发生变化;
-
状态管理框架调用@Watch函数BasketViewer onBasketUpdated 更新BasketViewer TotalPurchase的值;
-
@Link shopBasket的改变,新增了数组项,ForEach组件会执行item Builder,渲染构建新的Item项;@State totalPurchase改变,对应的Text组件也重新渲染;重新渲染是异步发生的。
$$语法:内置组件双向同步
$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。
内部状态具体指什么取决于组件。例如,TextInput组件的text参数。
说明
$$还用于@Builder装饰器的按引用传递参数,开发者需要注意两种用法的区别。
使用规则
-
当前$$支持的组件:
组件 支持的参数/属性 起始API版本 Checkbox select 10 CheckboxGroup selectAll 10 DatePicker selected 10 TimePicker selected 10 MenuItem selected 10 Panel mode 10 Radio checked 10 Rating rating 10 Search value 10 SideBarContainer showSideBar 10 Slider value 10 Stepper index 10 Swiper index 10 Tabs index 10 TextArea text 10 TextInput text 10 TextPicker selected、value 10 Toggle isOn 10 AlphabetIndexer selected 10 Select selected、value 10 BindSheet isShow 10 BindContentCover isShow 10 Refresh refreshing 8 GridItem selected 10 ListItem selected 10 -
$$绑定的变量变化时,会触发UI的同步刷新。
使用示例
以TextInput方法的text参数为例:
// 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)
}
}
@Track装饰器:class对象属性级更新
@Track应用于class对象的属性级更新。@Track装饰的属性变化时,只会触发该属性关联的UI更新。
说明
从API version 11开始,该装饰器支持在ArkTS卡片中使用。
概述
@Track是class对象的属性装饰器。当一个class对象是状态变量时,@Track装饰的属性发生变化,只会触发该属性关联的UI更新;而未被标记的属性不能在UI中使用。
装饰器说明
@Track变量装饰器 | 说明 |
---|---|
装饰器参数 | 无 |
可装饰的变量 | class对象的非静态成员属性。 |
观察变化和行为表现
当一个class对象是状态变量时,@Track装饰的属性发生变化,该属性关联的UI触发更新。
说明
当class对象中没有一个属性被标记@Track,行为与原先保持不变。@Track没有深度观测的功能。
使用@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 rendered Text 4 is rendered
限制条件
-
不能在UI中使用非@Track装饰的属性,包括不能绑定在组件上、不能用于初始化子组件,错误的使用将导致JSCrash;可以在非UI中使用非@Track装饰的属性,如事件回调函数中、生命周期函数中等。
-
建议开发者不要混用包含@Track的class对象和不包含@Track的class对象,如联合类型中、类继承中等。
使用场景
@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%')
}
}
处理步骤:
-
AddLog自定义组件的Text.onClick点击事件自增字符串' info.'。
-
由于@State log变量的@Track属性logInfo更改,Text重新渲染。