@Prop裝飾的變量可以和父組件建立單向的同步關系。@Prop裝飾的變量是可變的,但是變化不會同步回其父組件。
說明:
從API version 9開始,該裝飾器支持在ArkTS卡片中使用。
概述
@Prop裝飾的變量和父組件建立單向的同步關系:
? ● @Prop變量允許在本地修改,但修改后的變化不會同步回父組件。
? ● 當數據源更改時,@Prop裝飾的變量都會更新,并且會覆蓋本地所有更改。因此,數值的同步是父組件到子組件(所屬組件),子組件數值的變化不會同步到父組件。
裝飾器使用規則說明
@Prop變量裝飾器 | 說明 |
---|---|
裝飾器參數 | 無 |
同步類型 | 單向同步:對父組件狀態變量值的修改,將同步給子組件@Prop裝飾的變量,子組件@Prop變量的修改不會同步到父組件的狀態變量上。嵌套類型的場景請參考觀察變化。 |
允許裝飾的變量類型 | Object、class、string、number、boolean、enum類型,以及這些類型的數組。不支持any,不支持簡單類型和復雜類型的聯合類型,不允許使用undefined和null。支持Date類型。支持類型的場景請參考觀察變化。必須指定類型。說明 :不支持Length、ResourceStr、ResourceColor類型,Length,ResourceStr、ResourceColor為簡單類型和復雜類型的聯合類型。在父組件中,傳遞給@Prop裝飾的值不能為undefined或者null,反例如下所示。CompA ({ aProp: undefined })CompA ({ aProp: null })@Prop和數據源類型需要相同,有以下三種情況:- @Prop裝飾的變量和@State以及其他裝飾器同步時雙方的類型必須相同,示例請參考父組件@State到子組件@Prop簡單數據類型同步。- @Prop裝飾的變量和@State以及其他裝飾器裝飾的數組的項同步時 ,@Prop的類型需要和@State裝飾的數組的數組項相同,比如@Prop : T和@State : Array,示例請參考父組件@State數組中的項到子組件@Prop簡單數據類型同步;- 當父組件狀態變量為Object或者class時,@Prop裝飾的變量和父組件狀態變量的屬性類型相同,示例請參考從父組件中的@State類對象屬性到@Prop簡單類型的同步。 |
嵌套傳遞層數 | 在組件復用場景,建議@Prop深度嵌套數據不要超過5層,嵌套太多會導致深拷貝占用的空間過大以及GarbageCollection(垃圾回收),引起性能問題,此時更建議使用@ObjectLink。如果子組件的數據不想同步回父組件,建議采用@Reusable中的aboutToReuse,實現父組件向子組件傳遞數據,具體用例請參考組件復用場景。 |
被裝飾變量的初始值 | 允許本地初始化。 |
變量的傳遞/訪問規則說明
傳遞/訪問 | 說明 |
---|---|
從父組件初始化 | 如果本地有初始化,則是可選的。沒有的話,則必選,支持父組件中的常規變量(常規變量對@Prop賦值,只是數值的初始化,常規變量的變化不會觸發UI刷新。只有狀態變量才能觸發UI刷新)、@State、@Link、@Prop、@Provide、@Consume、@ObjectLink、@StorageLink、@StorageProp、@LocalStorageLink和@LocalStorageProp去初始化子組件中的@Prop變量。 |
用于初始化子組件 | @Prop支持去初始化子組件中的常規變量、@State、@Link、@Prop、@Provide。 |
是否支持組件外訪問 | @Prop裝飾的變量是私有的,只能在組件內訪問。 |
圖1 初始化規則圖示
觀察變化和行為表現
觀察變化
@Prop裝飾的數據可以觀察到以下變化。
? ● 當裝飾的類型是允許的類型,即Object、class、string、number、boolean、enum類型都可以觀察到賦值的變化。
// 簡單類型 @Prop count: number; // 賦值的變化可以被觀察到 this.count = 1; // 復雜類型 @Prop count: Model; // 可以觀察到賦值的變化 this.title = new Model('Hi');
當裝飾的類型是Object或者class復雜類型時,可以觀察到第一層的屬性的變化,屬性即Object.keys(observedObject)返回的所有屬性;
class ClassA { public value: string; constructor(value: string) { this.value = value; } } class Model { public value: string; public a: ClassA; constructor(value: string, a: ClassA) { this.value = value; this.a = a; } } @Prop title: Model; // 可以觀察到第一層的變化 this.title.value = 'Hi' // 觀察不到第二層的變化 this.title.a.value = 'ArkUi'
對于嵌套場景,如果class是被@Observed裝飾的,可以觀察到class屬性的變化,示例請參考@Prop嵌套場景。
當裝飾的類型是數組的時候,可以觀察到數組本身的賦值、添加、刪除和更新。
// @State裝飾的對象為數組時 @Prop title: string[] // 數組自身的賦值可以觀察到 this.title = ['1'] // 數組項的賦值可以觀察到 this.title[0] = '2' // 刪除數組項可以觀察到 this.title.pop() // 新增數組項可以觀察到 this.title.push('3')
對于@State和@Prop的同步場景:
? ● 使用父組件中@State變量的值初始化子組件中的@Prop變量。當@State變量變化時,該變量值也會同步更新至@Prop變量。
? ● @Prop裝飾的變量的修改不會影響其數據源@State裝飾變量的值。
? ● 除了@State,數據源也可以用@Link或@Prop裝飾,對@Prop的同步機制是相同的。
? ● 數據源和@Prop變量的類型需要相同,@Prop允許簡單類型和class類型。
? ● 當裝飾的對象是Date時,可以觀察到Date整體的賦值,同時可通過調用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的屬性。
@Component struct DateComponent { @Prop selectedDate: Date = new Date(''); build() { Column() { Button('child update the new date') .margin(10) .onClick(() => { this.selectedDate = new Date('2023-09-09') }) Button(`child increase the year by 1`).onClick(() => { this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1) }) DatePicker({ start: new Date('1970-1-1'), end: new Date('2100-1-1'), selected: this.selectedDate }) } } } @Entry @Component struct ParentComponent { @State parentSelectedDate: Date = new Date('2021-08-08'); build() { Column() { Button('parent update the new date') .margin(10) .onClick(() => { this.parentSelectedDate = new Date('2023-07-07') }) Button('parent increase the day by 1') .margin(10) .onClick(() => { this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1) }) DatePicker({ start: new Date('1970-1-1'), end: new Date('2100-1-1'), selected: this.parentSelectedDate }) DateComponent({selectedDate:this.parentSelectedDate}) } } }
框架行為
要理解@Prop變量值初始化和更新機制,有必要了解父組件和擁有@Prop變量的子組件初始渲染和更新流程。
? 1. 初始渲染:
? a. 執行父組件的build()函數將創建子組件的新實例,將數據源傳遞給子組件;
? b. 初始化子組件@Prop裝飾的變量。
? 2. 更新:
? a. 子組件@Prop更新時,更新僅停留在當前子組件,不會同步回父組件;
? b. 當父組件的數據源更新時,子組件的@Prop裝飾的變量將被來自父組件的數據源重置,所有@Prop裝飾的本地的修改將被父組件的更新覆蓋。
使用場景
父組件@State到子組件@Prop簡單數據類型同步
以下示例是@State到子組件@Prop簡單數據同步,父組件ParentComponent的狀態變量countDownStartValue初始化子組件CountDownComponent中@Prop裝飾的count,點擊“Try again”,count的修改僅保留在CountDownComponent 不會同步給父組件ParentComponent。
ParentComponent的狀態變量countDownStartValue的變化將重置CountDownComponent的count。
@Component struct CountDownComponent { @Prop count: number = 0; costOfOneAttempt: number = 1; build() { Column() { if (this.count > 0) { Text(`You have ${this.count} Nuggets left`) } else { Text('Game over!') } // @Prop裝飾的變量不會同步給父組件 Button(`Try again`).onClick(() => { this.count -= this.costOfOneAttempt; }) } } } @Entry @Component struct ParentComponent { @State countDownStartValue: number = 10; build() { Column() { Text(`Grant ${this.countDownStartValue} nuggets to play.`) // 父組件的數據源的修改會同步給子組件 Button(`+1 - Nuggets in New Game`).onClick(() => { this.countDownStartValue += 1; }) // 父組件的修改會同步給子組件 Button(`-1 - Nuggets in New Game`).onClick(() => { this.countDownStartValue -= 1; }) CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) } } }
在上面的示例中:
? 1. CountDownComponent子組件首次創建時其@Prop裝飾的count變量將從父組件@State裝飾的countDownStartValue變量初始化;
? 2. 按“+1”或“-1”按鈕時,父組件的@State裝飾的countDownStartValue值會變化,這將觸發父組件重新渲染,在父組件重新渲染過程中會刷新使用countDownStartValue狀態變量的UI組件并單向同步更新CountDownComponent子組件中的count值;
? 3. 更新count狀態變量值也會觸發CountDownComponent的重新渲染,在重新渲染過程中,評估使用count狀態變量的if語句條件(this.count > 0),并執行true分支中的使用count狀態變量的UI組件相關描述來更新Text組件的UI顯示;
? 4. 當按下子組件CountDownComponent的“Try again”按鈕時,其@Prop變量count將被更改,但是count值的更改不會影響父組件的countDownStartValue值;
? 5. 父組件的countDownStartValue值會變化時,父組件的修改將覆蓋掉子組件CountDownComponent中count本地的修改。
父組件@State數組項到子組件@Prop簡單數據類型同步
父組件中@State如果裝飾的數組,其數組項也可以初始化@Prop。以下示例中父組件Index中@State裝飾的數組arr,將其數組項初始化子組件Child中@Prop裝飾的value。
@Component struct Child { @Prop value: number = 0; build() { Text(`${this.value}`) .fontSize(50) .onClick(()=>{this.value++}) } } @Entry @Component struct Index { @State arr: number[] = [1,2,3]; build() { Row() { Column() { Child({value: this.arr[0]}) Child({value: this.arr[1]}) Child({value: this.arr[2]}) Divider().height(5) ForEach(this.arr, (item: void) => { Child({value: item}) }, (item: string) => item.toString() ) Text('replace entire arr') .fontSize(50) .onClick(()=>{ // 兩個數組都包含項“3”。 this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3]; }) } } } }
初始渲染創建6個子組件實例,每個@Prop裝飾的變量初始化都在本地拷貝了一份數組項。子組件onclick事件處理程序會更改局部變量值。
假設我們點擊了多次,所有變量的本地取值都是“7”。
7 7 7 ---- 7 7 7
單擊replace entire arr后,屏幕將顯示以下信息。
3 4 5 ---- 7 4 5
? ● 在子組件Child中做的所有的修改都不會同步回父組件Index組件,所以即使6個組件顯示都為7,但在父組件Index中,this.arr保存的值依舊是[1,2,3]。
? ● 點擊replace entire arr,this.arr[0] == 1成立,將this.arr賦值為[3, 4, 5];
? ● 因為this.arr[0]已更改,Child({value: this.arr[0]})組件將this.arr[0]更新同步到實例@Prop裝飾的變量。Child({value: this.arr[1]})和Child({value: this.arr[2]})的情況也類似。
? ● this.arr的更改觸發ForEach更新,this.arr更新的前后都有數值為3的數組項:[3, 4, 5] 和[1, 2, 3]。根據diff機制,數組項“3”將被保留,刪除“1”和“2”的數組項,添加為“4”和“5”的數組項。這就意味著,數組項“3”的組件不會重新生成,而是將其移動到第一位。所以“3”對應的組件不會更新,此時“3”對應的組件數值為“7”,ForEach最終的渲染結果是“7”,“4”,“5”。
從父組件中的@State類對象屬性到@Prop簡單類型的同步
如果圖書館有一本圖書和兩位用戶,每位用戶都可以將圖書標記為已讀,此標記行為不會影響其它讀者用戶。從代碼角度講,對@Prop圖書對象的本地更改不會同步給圖書館組件中的@State圖書對象。
在此示例中,圖書類可以使用@Observed裝飾器,但不是必須的,只有在嵌套結構時需要此裝飾器。這一點我們會在從父組件中的@State數組項到@Prop class類型的同步說明。
class Book { public title: string; public pages: number; public readIt: boolean = false; constructor(title: string, pages: number) { this.title = title; this.pages = pages; } } @Component struct ReaderComp { @Prop book: Book = new Book("", 0); build() { Row() { Text(this.book.title) Text(`...has${this.book.pages} pages!`) Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`) .onClick(() => this.book.readIt = true) } } } @Entry @Component struct Library { @State book: Book = new Book('100 secrets of C++', 765); build() { Column() { ReaderComp({ book: this.book }) ReaderComp({ book: this.book }) } } }
從父組件中的@State數組項到@Prop class類型的同步
在下面的示例中,更改了@State 修飾的allBooks數組中Book對象上的屬性,但點擊“Mark read for everyone”無反應。這是因為該屬性是第二層的嵌套屬性,@State裝飾器只能觀察到第一層屬性,不會觀察到此屬性更改,所以框架不會更新ReaderComp。
let nextId: number = 1; // @Observed class Book { public id: number; public title: string; public pages: number; public readIt: boolean = false; constructor(title: string, pages: number) { this.id = nextId++; this.title = title; this.pages = pages; } } @Component struct ReaderComp { @Prop book: Book = new Book("", 1); build() { Row() { Text(this.book.title) Text(`...has${this.book.pages} pages!`) Text(`...${this.book.readIt ? "I have read" : 'I have not read it'}`) .onClick(() => this.book.readIt = true) } } } @Entry @Component struct Library { @State allBooks: Book[] = [new Book("100 secrets of C++", 765), new Book("Effective C++", 651), new Book("The C++ programming language", 1765)]; build() { Column() { Text('library`s all time favorite') ReaderComp({ book: this.allBooks[2] }) Divider() Text('Books on loaan to a reader') ForEach(this.allBooks, (book: Book) => { ReaderComp({ book: book }) }, (book: Book) => book.id.toString()) Button('Add new') .onClick(() => { this.allBooks.push(new Book("The C++ Standard Library", 512)); }) Button('Remove first book') .onClick(() => { this.allBooks.shift(); }) Button("Mark read for everyone") .onClick(() => { this.allBooks.forEach((book) => book.readIt = true) }) } } }
需要使用@Observed裝飾class Book,Book的屬性將被觀察。 需要注意的是,@Prop在子組件裝飾的狀態變量和父組件的數據源是單向同步關系,即ReaderComp中的@Prop book的修改不會同步給父組件Library。而父組件只會在數值有更新的時候(和上一次狀態的對比),才會觸發UI的重新渲染。
@Observed class Book { public id: number; public title: string; public pages: number; public readIt: boolean = false; constructor(title: string, pages: number) { this.id = nextId++; this.title = title; this.pages = pages; } }
@Observed裝飾的類的實例會被不透明的代理對象包裝,此代理可以檢測到包裝對象內的所有屬性更改。如果發生這種情況,此時,代理通知@Prop,@Prop對象值被更新。
@Prop本地初始化不和父組件同步
為了支持@Component裝飾的組件復用場景,@Prop支持本地初始化,這樣可以讓@Prop是否與父組件建立同步關系變得可選。當且僅當@Prop有本地初始化時,從父組件向子組件傳遞@Prop的數據源才是可選的。
下面的示例中,子組件包含兩個@Prop變量:
? ● @Prop customCounter沒有本地初始化,所以需要父組件提供數據源去初始化@Prop,并當父組件的數據源變化時,@Prop也將被更新;
? ● @Prop customCounter2有本地初始化,在這種情況下,@Prop依舊允許但非強制父組件同步數據源給@Prop。
@Component struct MyComponent { @Prop customCounter: number = 0; @Prop customCounter2: number = 5; build() { Column() { Row() { Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010') } Row() { Button('Click to change locally !').width(180).height(60).margin({ top: 10 }) .onClick(() => { this.customCounter2++ }) }.height(100).width(180) Row() { Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010') } } } } @Entry @Component struct MainProgram { @State mainCounter: number = 10; build() { Column() { Row() { Column() { Button('Click to change number').width(480).height(60).margin({ top: 10, bottom: 10 }) .onClick(() => { this.mainCounter++ }) } } Row() { Column() { // customCounter必須從父組件初始化,因為MyComponent的customCounter成員變量缺少本地初始化;此處,customCounter2可以不做初始化。 MyComponent({ customCounter: this.mainCounter }) // customCounter2也可以從父組件初始化,父組件初始化的值會覆蓋子組件customCounter2的本地初始化的值 MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter }) } } } } }
@Prop嵌套場景
在嵌套場景下,每一層都要用@Observed裝飾,且每一層都要被@Prop接收,這樣才能觀察到嵌套場景。
// 以下是嵌套類對象的數據結構。 @Observed class ClassA { public title: string; constructor(title: string) { this.title = title; } } @Observed class ClassB { public name: string; public a: ClassA; constructor(name: string, a: ClassA) { this.name = name; this.a = a; } }
以下組件層次結構呈現的是@Prop嵌套場景的數據結構。
@Entry @Component struct Parent { @State votes: ClassB = new ClassB('Hello', new ClassA('world')) build() { Column() { Button('change') .onClick(() => { this.votes.name = "aaaaa" this.votes.a.title = "wwwww" }) Child({ vote: this.votes }) } } } @Component struct Child { @Prop vote: ClassB = new ClassB('', new ClassA('')); build() { Column() { Text(this.vote.name).fontSize(36).fontColor(Color.Red).margin(50) .onClick(() => { this.vote.name = 'Bye' }) Text(this.vote.a.title).fontSize(36).fontColor(Color.Blue) .onClick(() => { this.vote.a.title = "openHarmony" }) Child1({vote1:this.vote.a}) } } } @Component struct Child1 { @Prop vote1: ClassA = new ClassA(''); build() { Column() { Text(this.vote1.title).fontSize(36).fontColor(Color.Red).margin(50) .onClick(() => { this.vote1.title = 'Bye Bye' }) } } }
審核編輯 黃宇
-
變量
+關注
關注
0文章
613瀏覽量
28411 -
組件
+關注
關注
1文章
513瀏覽量
17853 -
OpenHarmony
+關注
關注
25文章
3728瀏覽量
16403
發布評論請先 登錄
相關推薦
評論