【中秋國慶不斷更】OpenHarmony巢狀類物件屬性變化
上文所述的裝飾器僅能觀察到第一層的變化,但是在實際應用開發中,應用會根據開發需要,封裝自己的資料模型。對於多層巢狀的情況,比如二維陣列,或者陣列項class,或者class的屬性是class,他們的第二層的屬性變化是無法觀察到的。這就引出了@Observed/@ObjectLink裝飾器。
說明:
從API version 9開始,這兩個裝飾器支援在ArkTS卡片中使用。
概述
@ObjectLink和@Observed類裝飾器用於在涉及巢狀物件或陣列的場景中進行雙向資料同步:
● 被@Observed裝飾的類,可以被觀察到屬性的變化;
● 子元件中@ObjectLink裝飾器裝飾的狀態變數用於接收@Observed裝飾的類的例項,和父元件中對應的狀態變數建立雙向資料繫結。這個例項可以是陣列中的被@Observed裝飾的項,或者是class object中的屬性,這個屬性同樣也需要被@Observed裝飾。
● 單獨使用@Observed是沒有任何作用的,需要搭配@ObjectLink或者 @Prop 使用。
限制條件
使用@Observed裝飾class會改變class原始的原型鏈,@Observed和其他類裝飾器裝飾同一個class可能會帶來問題。
裝飾器說明
@Observed類裝飾器 |
說明 |
裝飾器引數 |
無 |
類裝飾器 |
裝飾class。需要放在class的定義前,使用new建立類物件。 |
@ObjectLink變數裝飾器 |
說明 |
裝飾器引數 |
無 |
同步型別 |
不與父元件中的任何型別同步變數。 |
允許裝飾的變數型別 |
必須為被@Observed裝飾的class例項,必須指定型別。 不支援簡單型別,可以使用 @Prop 。 支援繼承Date或者Array的class例項,示例見 觀察變化 。 @ObjectLink的屬性是可以改變的,但是變數的分配是不允許的,也就是說這個裝飾器裝飾變數是隻讀的,不能被改變。 |
被裝飾變數的初始值 |
不允許。 |
@ObjectLink裝飾的資料為可讀示例。
// 允許@ObjectLink裝飾的資料屬性賦值 this.objLink.a= ... // 不允許@ObjectLink裝飾的資料自身賦值 this.objLink= ...
說明:
@ObjectLink裝飾的變數不能被賦值,如果要使用賦值操作,請使用 @Prop 。
● @Prop裝飾的變數和資料來源的關係是是單向同步,@Prop裝飾的變數在本地複製了資料來源,所以它允許本地更改,如果父元件中的資料來源有更新,@Prop裝飾的變數本地的修改將被覆蓋;
● @ObjectLink裝飾的變數和資料來源的關係是雙向同步,@ObjectLink裝飾的變數相當於指向資料來源的指標。禁止對@ObjectLink裝飾的變數賦值,如果一旦發生@ObjectLink裝飾的變數的賦值,則同步鏈將被打斷。因為@ObjectLink修飾的變數透過資料來源(Object)引用來初始化。對於實現雙向資料同步的@ObjectLink,賦值相當於更新父元件中的陣列項或者class的屬性,TypeScript/JavaScript不能實現,會發生執行時報錯。
變數的傳遞/訪問規則說明
@ObjectLink傳遞/訪問 |
說明 |
從父元件初始化 |
必須指定。 初始化@ObjectLink裝飾的變數必須同時滿足以下場景: - 型別必須是@Observed裝飾的class。 - 初始化的數值需要是陣列項,或者class的屬性。 - 同步源的class或者陣列必須是@State,@Link,@Provide,@Consume或者@ObjectLink裝飾的資料。 |
與源物件同步 |
雙向。 |
可以初始化子元件 |
允許,可用於初始化常規變數、@State、@Link、@Prop、@Provide |
圖1 初始化規則圖示
觀察變化和行為表現
觀察變化
@Observed裝飾的類,如果其屬性為非簡單型別,比如class、Object或者陣列,也需要被@Observed裝飾,否則將觀察不到其屬性的變化。
class ClassA { public c: number; constructor(c: number) { this.c = c; } } @Observed class ClassB { public a: ClassA; public b: number; constructor(a: ClassA, b: number) { this.a = a; this.b = b; } }
以上示例中,ClassB被@Observed裝飾,其成員變數的賦值的變化是可以被觀察到的,但對於ClassA,沒有被@Observed裝飾,其屬性的修改不能被觀察到。
@ObjectLink b: ClassB // 賦值變化可以被觀察到 this.b.a = new ClassA(5) this.b.b = 5 // ClassA沒有被@Observed裝飾,其屬性的變化觀察不到 this.b.a.c = 5
@ObjectLink:@ObjectLink只能接收被@Observed裝飾class的例項,可以觀察到:
● 其屬性的數值的變化,其中屬性是指Object.keys(observedObject)返回的所有屬性,示例請參考 巢狀物件 。
● 如果資料來源是陣列,則可以觀察到陣列item的替換,如果資料來源是class,可觀察到class的屬性的變化,示例請參考 物件陣列 。
繼承Date的class時,可以觀察到Date整體的賦值,同時可透過呼叫Date的介面 setFullYear , setMonth , setDate , setHours , setMinutes , setSeconds , setMilliseconds , setTime , setUTCFullYear , setUTCMonth , setUTCDate , setUTCHours , setUTCMinutes , setUTCSeconds , setUTCMilliseconds 更新Date的屬性。
@Observed class DateClass extends Date { constructor(args: number | string) { super(args) } } @Observed class ClassB { public a: DateClass; constructor(a: DateClass) { this.a = a; } } @Component struct ViewA { label: string = 'date'; @ObjectLink a: DateClass; build() { Column() { Button(`child increase the day by 1`) .onClick(() => { this.a.setDate(this.a.getDate() + 1); }) DatePicker({ start: new Date('1970-1-1'), end: new Date('2100-1-1'), selected: this.a }) } } } @Entry @Component struct ViewB { @State b: ClassB = new ClassB(new DateClass('2023-1-1')); build() { Column() { ViewA({ label: 'date', a: this.b.a }) Button(`parent update the new date`) .onClick(() => { this.b.a = new DateClass('2023-07-07'); }) Button(`ViewB: this.b = new ClassB(new DateClass('2023-08-20'))`) .onClick(() => { this.b = new ClassB(new DateClass('2023-08-20')); }) } } }
框架行為
1. 初始渲染:
a. @Observed裝飾的class的例項會被不透明的代理物件包裝,代理了class上的屬性的setter和getter方法
b. 子元件中@ObjectLink裝飾的從父元件初始化,接收被@Observed裝飾的class的例項,@ObjectLink的包裝類會將自己註冊給@Observed class。
2. 屬性更新:當@Observed裝飾的class屬性改變時,會走到代理的setter和getter,然後遍歷依賴它的@ObjectLink包裝類,通知資料更新。
使用場景
巢狀物件
以下是巢狀類物件的資料結構。
// objectLinkNestedObjects.ets let NextID: number = 1; @Observed class ClassA { public id: number; public c: number; constructor(c: number) { this.id = NextID++; this.c = c; } } @Observed class ClassB { public a: ClassA; constructor(a: ClassA) { this.a = a; } } @Observed class ClassD { public c: ClassC; constructor(c: ClassC) { this.c = c; } } @Observed class ClassC extends ClassA { public k: number; constructor(k: number) { // 呼叫父類方法對k進行處理 super(k); this.k = k; } }
以下元件層次結構呈現的是巢狀類物件的資料結構。
@Component struct ViewC { label: string = 'ViewC1'; @ObjectLink c: ClassC; build() { Row() { Column() { Text(`ViewC [${this.label}] this.a.c = ${this.c.c}`) .fontColor('#ffffffff') .backgroundColor('#ff3fc4c4') .height(50) .borderRadius(25) Button(`ViewC: this.c.c add 1`) .backgroundColor('#ff7fcf58') .onClick(() => { this.c.c += 1; console.log('this.c.c:' + this.c.c) }) } .width(300) } } } @Entry @Component struct ViewB { @State b: ClassB = new ClassB(new ClassA(0)); @State child : ClassD = new ClassD(new ClassC(0)); build() { Column() { ViewC({ label: 'ViewC #3', c: this.child.c}) Button(`ViewC: this.child.c.c add 10`) .backgroundColor('#ff7fcf58') .onClick(() => { this.child.c.c += 10 console.log('this.child.c.c:' + this.child.c.c) }) } } }
被@Observed裝飾的ClassC類,可以觀測到繼承基類的屬性的變化。
ViewB中的事件控制程式碼:
● this.child.c = new ClassA(0) 和this.b = new ClassB(new ClassA(0)): 對@State裝飾的變數b和其屬性的修改。
● this.child.c.c = ... :該變化屬於第二層的變化, @State 無法觀察到第二層的變化,但是ClassA被@Observed裝飾,ClassA的屬性c的變化可以被@ObjectLink觀察到。
ViewC中的事件控制程式碼:
● this.c.c += 1:對@ObjectLink變數a的修改,將觸發Button元件的重新整理。@ObjectLink和@Prop不同,@ObjectLink不複製來自父元件的資料來源,而是在本地構建了指向其資料來源的引用。
● @ObjectLink變數是隻讀的,this.a = new ClassA(...)是不允許的,因為一旦賦值操作發生,指向資料來源的引用將被重置,同步將被打斷。
物件陣列
物件陣列是一種常用的資料結構。以下示例展示了陣列物件的用法。
@Component struct ViewA { // 子元件ViewA的@ObjectLink的型別是ClassA @ObjectLink a: ClassA; label: string = 'ViewA1'; build() { Row() { Button(`ViewA [${this.label}] this.a.c = ${this.a.c} +1`) .onClick(() => { this.a.c += 1; }) } } } @Entry @Component struct ViewB { // ViewB中有@State裝飾的ClassA[] @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)]; build() { Column() { ForEach(this.arrA, (item: ClassA) => { ViewA({ label: `#${item.id}`, a: item }) }, (item: ClassA): string => item.id.toString() ) // 使用@State裝飾的陣列的陣列項初始化@ObjectLink,其中陣列項是被@Observed裝飾的ClassA的例項 ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] }) ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] }) Button(`ViewB: reset array`) .onClick(() => { this.arrA = [new ClassA(0), new ClassA(0)]; }) Button(`ViewB: push`) .onClick(() => { this.arrA.push(new ClassA(0)) }) Button(`ViewB: shift`) .onClick(() => { this.arrA.shift() }) Button(`ViewB: chg item property in middle`) .onClick(() => { this.arrA[Math.floor(this.arrA.length / 2)].c = 10; }) Button(`ViewB: chg item property in middle`) .onClick(() => { this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11); }) } } }
● this.arrA[Math.floor(this.arrA.length/2)] = new ClassA(..) :該狀態變數的改變觸發2次更新:
○ ForEach:陣列項的賦值導致ForEach的 itemGenerator 被修改,因此陣列項被識別為有更改,ForEach的item builder將執行,建立新的ViewA元件例項。
○ ViewA({ label: ViewA this.arrA[first] , a: this.arrA[0] }):上述更改改變了陣列中第一個元素,所以繫結this.arrA[0]的ViewA將被更新。
● this.arrA.push(new ClassA(0)) : 將觸發2次不同效果的更新:
○ ForEach:新新增的ClassA物件對於ForEach是未知的 itemGenerator ,ForEach的item builder將執行,建立新的ViewA元件例項。
○ ViewA({ label: ViewA this.arrA[last] , a: this.arrA[this.arrA.length-1] }):陣列的最後一項有更改,因此引起第二個ViewA的例項的更改。對於ViewA({ label: ViewA this.arrA[first] , a: this.arrA[0] }),陣列的更改並沒有觸發一個陣列項更改的改變,所以第一個ViewA不會重新整理。
● this.arrA[Math.floor(this.arrA.length/2)].c: @State 無法觀察到第二層的變化,但是ClassA被@Observed裝飾,ClassA的屬性的變化將被@ObjectLink觀察到。
二維陣列
使用@Observed觀察二維陣列的變化。可以宣告一個被@Observed裝飾的繼承Array的子類。
@Observed class StringArray extends Array<String> { }
使用new StringArray()來構造StringArray的例項,new運算子使得@Observed生效,@Observed觀察到StringArray的屬性變化。
宣告一個從Array擴充套件的類class StringArray extends Array<String> {},並建立StringArray的例項。@Observed裝飾的類需要使用new運算子來構建class例項。
@Observed class StringArray extends Array<String> { } @Component struct ItemPage { @ObjectLink itemArr: StringArray; build() { Row() { Text('ItemPage') .width(100).height(100) ForEach(this.itemArr, (item: string | Resource) => { Text(item) .width(100).height(100) }, (item: string) => item ) } } } @Entry @Component struct IndexPage { @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: StringArray) => { ItemPage({ itemArr: itemArr }) }, (itemArr: string) => itemArr[0] ) Divider() Button('update') .onClick(() => { console.error('Update all items in arr'); if ((this.arr[0] as Array<String>)[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('!'); } }) } } }
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2986393/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 【中秋國慶不斷更】HarmonyOS應用視窗管理(Stage模型)模型
- 【中秋國慶不斷更】HarmonyOS網路管理開發—HTTP與WebSocketHTTPWeb
- 【中秋國慶不斷更】HarmonyOS網路管理開發—Socket連線
- SCSS 巢狀屬性CSS巢狀
- [二、狀態管理]2管理元件擁有的狀態(5)@Observed裝飾器和@ObjectLink裝飾器:巢狀類物件屬性變化元件Object巢狀物件
- java判斷實體內中屬性值內容是否有變更(包含父類屬性值)Java
- javax.validation包校驗巢狀屬性(List物件)的寫法Java巢狀物件
- Python - 物件導向程式設計 - 類變數、例項變數/類屬性、例項屬性Python物件程式設計變數
- 學習Sass 巢狀規則與屬性巢狀
- 論如何監聽物件某個屬性的變化物件
- 物件屬性值賦給變數物件變數
- Scala的類、屬性、物件欄位物件
- PHP 物件導向 (二)類屬性PHP物件
- 根據給定的字串,修改一個多層巢狀物件對應的屬性值字串巢狀物件
- 巢狀類遞迴巢狀遞迴
- JavaScript 判斷物件中是否有某屬性JavaScript物件
- python中類物件及類屬性的介紹Python物件
- [JAVA] 只知物件屬性,不知類屬性?就算類答應,static都不答應Java物件
- 理解Python中的類物件、例項物件、屬性、方法Python物件
- 優雅且語義化的斷言之—將模型屬性斷言變為模型方法斷言模型
- 【python】物件導向之類成員(屬性)Python物件
- Linux的中斷可以巢狀嗎?Linux巢狀
- ES6解構巢狀物件巢狀物件
- 物件導向 成員和巢狀物件巢狀
- JavaScript巢狀物件的全新方式JavaScript巢狀物件
- Java進階07 巢狀類Java巢狀
- js判斷物件裡面是否有某個屬性JS物件
- 【爬坑日記】vue中watch物件中某一個屬性變化Vue物件
- 類,物件,成員變數和區域性變數,匿名物件物件變數
- js物件屬性JS物件
- Pandas Series物件有哪些屬性?六大類!物件
- 深入理解JavaScript類與物件:揭秘類欄位和靜態屬性的妙用,js靜態屬性和例項屬性JavaScript物件JS
- es中如何使用巢狀物件查詢巢狀物件
- 類例項物件的class型別卻不屬於該類,何解?物件型別
- python物件屬性管理(2):property管理屬性Python物件
- CAD屬性編輯操作——物件屬性教程物件
- 【第八天】Python的類與物件.屬性Python物件
- 【Openxml】顏色變化屬性計算XML