前言
本文代碼案例基于Api13。
隨著官方的迭代,在新的Api中,對于新的應用開發,官方已經建議直接使用V2所屬的裝飾器進行開發了,所以,能上手V2的盡量上手V2吧,畢竟,V2是V1的增強版本,為開發者提供更多功能和靈活性,由V1升成V2,肯定是大勢所趨;但是,畢竟V1有著大量的應用基礎,使用的也非常廣泛,如果V1版本的功能和性能已能滿足需求,其實也不用切換,總之就一句話:新的應用盡量使用V2,老的應用,如果V1滿足可以不切換V2,如果功能受限,建議循序漸進的進行切換。
本篇文章主要概述下V2版本裝飾器中的@Monitor裝飾器,它對標的是V1中的@Watch裝飾器,但是使用上和功能上均有所不同。
記得之前在寫刷新組件的時候,有一個功能,需要監聽當前刷新或加載的關閉狀態,然后去執行關閉動畫等邏輯,使用的就是@Watch裝飾器,簡單的邏輯如下:
class RefreshController {closeRefresh: boolean = falsecloseLoadMore: boolean = false
}@Entry
@Component
struct Index {@State @Watch("listenerController") refreshController: RefreshController = new RefreshController()listenerController() {console.log("==當前刷新狀態:" + this.refreshController.closeRefresh)console.log("==當前加載狀態:" + this.refreshController.closeLoadMore)}build() {Column() {Button("關閉刷新").onClick(() => {this.refreshController.closeRefresh = true})Button("關閉加載").margin({ top: 20 }).onClick(() => {this.refreshController.closeLoadMore = true})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
運行后,我們點擊按鈕進行打印日志:
雖然執行的狀態,我們通過@Watch裝飾器監聽到了,也能實現我們的邏輯,但是存在一個問題,本身我們只想監聽到某一個狀態的改變,比如只監聽刷新狀態,或者只監聽加載狀態,但是@Watch裝飾器是,你無論監聽哪一個,都統統給你返回,顯然會影響我們做針對性的邏輯判斷。
除此之外,還存在一個問題,變量更改前的值是什么,在這里無法獲取,在業務邏輯復雜的場景下,我們是無法準確知道是哪一個屬性或元素發生了改變從而觸發了@Watch事件,這非常不便于我們對變量的更改進行準確監聽。
針對以上的弊端,V2版本中的@Monitor裝飾器,則彌補了這一缺陷,實現對對象、數組中某一單個屬性或數組項變化的監聽,并且能夠獲取到變化之前的值。
更改為@Monitor裝飾器后,針對屬性單獨監聽。
@ObservedV2
class RefreshController {@Trace closeRefresh: boolean = false@Trace closeLoadMore: boolean = false
}@Entry
@ComponentV2
struct Index {@Local refreshController: RefreshController = new RefreshController()@Monitor("refreshController.closeRefresh")closeRefreshChange() {console.log("==當前刷新狀態:" + this.refreshController.closeRefresh)}@Monitor("refreshController.closeLoadMore")closeLoadMoreChange() {console.log("==當前加載狀態:" + this.refreshController.closeLoadMore)}build() {Column() {Button("關閉刷新").onClick(() => {this.refreshController.closeRefresh = true})Button("關閉加載").margin({ top: 20 }).onClick(() => {this.refreshController.closeLoadMore = true})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
使用方式
首先需要注意,@Monitor監聽的變量,一定是被@Local、@Param、@Provider、@Consumer、@Computed裝飾,否則是無法監聽的,這一點,務必須知。
第一步,使用@Local、@Param、@Provider、@Consumer、@Computed等其中之一,修飾你的變量,例如下代碼:
@Local testContent: string = "測試數據一"
第二步,使用@Monitor裝飾器進行監聽,方法名自己定義,要求,@Monitor("變量名"),其中變量名一定要和第一步的變量名字保持一致,例如下代碼:
@Monitor("testContent")testContentChange() {console.log("==屬性testContent發生了改變:" + this.testContent)}
動態改變屬性testContent,就會觸發@Monitor裝飾器修飾的函數;@Monitor裝飾器支持監聽多個狀態變量,直接逗號分隔即可,多個屬性中,任意一個屬性發生了改變都會進行回調。
@Monitor("testContent","testNumber")
testChange() {console.log("==testContent屬性:" + this.testContent + ",testNumber屬性:" + this.testNumber)}
獲取改變之前的值
如果你想拿到當前屬性改變之前的值,那么就需要在函數中傳遞IMonitor類型的參數。
IMonitor類型的變量用作@Monitor裝飾方法的參數。
屬性 | 類型 | 參數 | 返回值 | 說明 |
dirty | Array<string> | 無 | 無 | 保存發生變化的屬性名。 |
value<T> | function | path?: string | IMonitorValue<T> | 獲得指定屬性(path)的變化信息。當不填path時返回@Monitor監聽順序中第一個改變的屬性的變化信息。 |
MonitorValue<T>類型
IMonitorValue<T>類型保存了屬性變化的信息,包括屬性名、變化前值、當前值。
屬性 | 類型 | 說明 |
before | T | 監聽屬性變化之前的值。 |
now | T | 監聽屬性變化之后的當前值。 |
path | string | 監聽的屬性名。 |
@Monitor("testContent")testChange(monitor: IMonitor) {monitor.dirty.forEach((path: string) => {console.log("==屬性值改變之前:" + monitor.value(path)?.before + ",屬性值改變之后:" + monitor.value(path)?.now)})}
如果只想監聽改變之后的值,IMonitor參數可以省略。
對象監聽
在前言中,我們可以看到,監聽對象中的屬性變化時,需要使用@Trace裝飾,如果未被裝飾,則是無法進行監聽的,所以在實際的開發中,如果需要針對對象的單一屬性進行監聽時,@Trace裝飾務必使用。
如果不裝飾,那么就需要重新創建對象,雖然這種方式也能正常的監聽到,但是并不是唯一屬性的監聽,在實際的開發中是不推薦的。
以下案例未使用@Trace裝飾,不建議使用。
class RefreshController {closeRefresh: boolean = falsecloseLoadMore: boolean = false
}@Entry
@ComponentV2
struct Index {@Local refreshController: RefreshController = new RefreshController()@Monitor("refreshController.closeRefresh")closeRefreshChange() {console.log("==當前刷新狀態:" + this.refreshController.closeRefresh)}@Monitor("refreshController.closeLoadMore")closeLoadMoreChange() {console.log("==當前加載狀態:" + this.refreshController.closeLoadMore)}build() {Column() {Button("關閉刷新").onClick(() => {this.refreshController = new RefreshController()this.refreshController.closeRefresh = true})Button("關閉加載").margin({ top: 20 }).onClick(() => {this.refreshController = new RefreshController()this.refreshController.closeLoadMore = true})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
如果你想監聽整個對象的屬性變化,@Trace裝飾可以不使用。
class RefreshController {closeRefresh: boolean = falsecloseLoadMore: boolean = false
}@Entry
@ComponentV2
struct Index {@Local refreshController: RefreshController = new RefreshController()@Monitor("refreshController")statusChange() {console.log("==當前刷新狀態:" + this.refreshController.closeRefresh)console.log("==當前加載狀態:" + this.refreshController.closeLoadMore)}build() {Column() {Button("關閉刷新").onClick(() => {this.refreshController = new RefreshController()this.refreshController.closeRefresh = true})Button("關閉加載").margin({ top: 20 }).onClick(() => {this.refreshController = new RefreshController()this.refreshController.closeLoadMore = true})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
除了在UI組件可以進行監聽,在自身對象中也是可以進行監聽的,方便在對象中做一些邏輯,同樣也支持在繼承類場景下,同一個屬性進行多次監聽。
@ObservedV2
class RefreshController {@Trace closeRefresh: boolean = false@Trace closeLoadMore: boolean = false@Monitor("closeRefresh")closeRefreshChange() {console.log("==監聽對象中的當前刷新狀態:" + this.closeRefresh)}
}
通用監聽能力
@Monitor裝飾器,除了正常的數據監聽之外,還支持對數組中的元素進行監聽,包括多維數組,對象數組,雖然可以正常監聽其值的變化,但是無法監聽內置類型(Array、Map、Date、Set)的API調用引起的變化。
還有一點需要注意,當@Monitor監聽數組整體變化時,只能通過監聽數組的長度變化來判斷數組是否有插入、刪除等變化,以下是一個簡單的二位數組數據改變案例:
@Entry
@ComponentV2
struct Index {@Local numberArray: number[][] = [[1, 1, 1], [2, 2, 2], [3, 3, 3]];@Monitor("numberArray.0.0", "numberArray.1.1")statusChange() {console.log("==數據改變:" + this.numberArray)}build() {Column() {Button("改變").onClick(() => {this.numberArray[0][0]++})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
可以發現以上的案例,只要數據發生了變化,就會執行數據監聽方法。
監聽對象整體改變時,如果當前屬性未發生改變時,則不會觸發@Monitor回調。
如下案例,依次點擊按鈕,會發生,按鈕一不會執行任何方法,因為屬性的值一樣,未發生變化,按鈕二和按鈕三則可以正常執行。
@ObservedV2
class RefreshController {@Trace closeRefresh: boolean = false@Trace closeLoadMore: boolean = falseconstructor(closeRefresh: boolean, closeLoadMore: boolean) {this.closeRefresh = closeRefresh;this.closeLoadMore = closeLoadMore;}
}@Entry
@ComponentV2
struct Index {@Local refreshController: RefreshController = new RefreshController(false, false)@Monitor("refreshController.closeRefresh")closeRefreshChange() {console.log("==當前刷新狀態:" + this.refreshController.closeRefresh)}@Monitor("refreshController.closeLoadMore")closeLoadMoreChange() {console.log("==當前加載狀態:" + this.refreshController.closeLoadMore)}build() {Column() {Button("不會走").onClick(() => {this.refreshController = new RefreshController(false, false)})Button("關閉刷新").margin({ top: 20 }).onClick(() => {this.refreshController = new RefreshController(true, false)})Button("關閉加載").margin({ top: 20 }).onClick(() => {this.refreshController = new RefreshController(false, true)})}.height('100%').width('100%').justifyContent(FlexAlign.Center)}
}
相關總結
如果要實現@Monitor監聽,其變量一定要被@Local、@Param、@Provider、@Consumer、@Computed裝飾,未被修飾則無法被監聽,還有,如果監聽對象的變化,則不建議在一個類中對同一個屬性進行多次@Monitor的監聽,多次監聽,只有最后一個定義的監聽方法才會有效。