????????除了前面章节提到的组件状态管理和应用状态管理,ArkTS还提供了@Watch和$$来为开发者提供更多功能:
????????@Watch用于监听状态变量的变化,当状态变量变化时,@Watch的回调方法将被调用。@Watch在ArkUI框架内部判断数值有无更新使用的是严格相等(===),遵循严格相等规范。当在严格相等为false的情况下,就会触发@Watch的回调。
- 可监听所有装饰器装饰的状态变量。不允许监听常规变量。
- 建议@State、@Prop、@Link等装饰器在@Watch装饰器之前。
- 必须有装饰器参数且为常量字符串,字符串需要有引号。是(string) => void自定义成员函数的方法的引用。
- 建议开发者避免无限循环。循环可能是因为在@Watch的回调方法里直接或者间接地修改了同一个状态变量引起的。为了避免循环的产生,建议不要在@Watch的回调方法里修改当前装饰的状态变量;
- 开发者应关注性能,属性值更新函数会延迟组件的重新渲染(具体请见上面的行为表现),因此,回调函数应仅执行快速运算;
- 不建议在@Watch函数中调用async await,因为@Watch设计的用途是为了快速的计算,异步行为可能会导致重新渲染速度的性能问题。
@Component
struct TotalView {
@Prop @Watch('onCountUpdated') count: number;
@State total: number = 0;
// @Watch 回调
onCountUpdated(propName: string): void {
this.total += this.count;
}
build() {
Text(`Total: ${this.total}`)
}
}
@Entry
@Component
struct OtherStateChangeDemo1Page {
@State count: number = 0;
build() {
Column() {
Button('add to basket')
.onClick(() => {
this.count++
})
TotalView({ count: this.count })
}
}
}
? ? ? ? 运行结果如下:
?????????示例展示组件更新和@Watch的处理步骤。count在CountModifier中由@State装饰,在TotalView中由@Prop装饰。流程分析如下:
? ? ? ? 以下案例展示了@Watch与@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) => {
Text(`Price: ${item.price.toFixed(2)} €`)
},
item => item.id.toString()
)
Text(`Total: ${this.totalPurchase.toFixed(2)} €`)
}
}
}
@Entry
@Component
struct OtherStateChangeDemo2Page {
@State shopBasket: PurchaseItem[] = [];
build() {
Column() {
Button('Add to basket')
.onClick(() => {
this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random())))
})
BasketViewer({ shopBasket: $shopBasket })
}
}
}
? ? ? ? 运行结果如下:
处理步骤如下:
????????$$运算符为系统内置组件提供TS变量的引用,使得TS变量和系统内置组件的内部状态保持同步。内部状态具体指什么取决于组件。例如,Refresh组件的refreshing参数。
- 当前$$支持基础类型变量,以及@State、@Link和@Prop装饰的变量。
- 当前$$仅支持Refresh组件的refreshing参数。
- $$绑定的变量变化时,会触发UI的同步刷新。
????????以Refresh组件的refreshing参数为例:
????????当使用了$$符号绑定isRefreshing状态变量时,页面进行下拉操作,isRefreshing会变成true。同时,Text中的isRefreshing状态也会同步改变为true,如果不使用$$符号绑定,则不会同步改变。
@Entry
@Component
struct RefreshDemo1Page {
@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 onStatueChange state is ' + refreshStatus)
}).onRefreshing(() => {
this.onRefresh()
})
}
}
onRefresh() {
setTimeout(() => {
this.counter++
this.isRefreshing = false
}, 2000)
}
}
? ? ? ? 运行结果如下:
? ? ? ? 刷新开始之后?isRefreshing的状态会变为true,触发UI更新,刷新的过程中会触发自定义函数onRefresh,该函数中会改版isRefreshing为false,同时更新刷新次数counter,这些状态更新又会触发UI的更新,从而使得整个刷新过程能顺畅的进行。