一、介紹
資料來自官網:文檔中心
在聲明式UI編程框架中,UI是程序狀態的運行結果,用戶構建了一個UI模型,其中應用的運行時的狀態是參數。當參數改變時,UI作為返回結果,也將進行對應的改變。這些運行時的狀態變化所帶來的UI的重新渲染,在ArkUI中統稱為狀態管理機制。
- View(UI):UI渲染,指將build方法內的UI描述和@Builder裝飾的方法內的UI描述映射到界面。
- State:狀態,指驅動UI更新的數據。用戶通過觸發組件的事件方法,改變狀態數據。狀態數據的改變,引起UI的重新渲染。
二、@State裝飾器:組件內狀態?
@State裝飾的變量,或稱為狀態變量,一旦變量擁有了狀態屬性,就和自定義組件的渲染綁定起來。當狀態改變時,UI會發生對應的渲染改變。
?
說明:
@State裝飾器標記的變量必須初始化,不能為空值
@State支持Object、class、string、number、boolean、enum類型以及這些類型的數組
嵌套類型以及數組中的對象屬性無法觸發視圖更新?
組件傳值代碼示例,為下面不同組件之間傳值做準備:👇
// 任務類
class Task{static id: number = 1// 任務名稱name: string = `任務${Task.id++}`// 任務狀態:是否完成finished: boolean = false
}
// 統一的卡片樣式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}@Entry
@Component
struct PropLinkPages {// 總任務數量@State totalTask: number = 0// 已完成任務數量@State finishTask: number = 0// 任務數組@State tasks: Task[] = []//此函數是更新任務總數量和已完成任務數量的handleTaskChange(){// 1.更新任務總數量this.totalTask = this.tasks.length// 2.更新已完成任務數量this.finishTask = this.tasks.filter(item => item.finished).length}build() {Column({space:10}){//1.任務進度卡片Row(){Text('任務進度:').fontSize(30).fontWeight(FontWeight.Bold)Stack(){Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring}).width(100)Row(){Text(this.finishTask.toString()).fontSize(24).fontColor('#36D')Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.card().margin({top: 5, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly)// 2.新增任務按鈕Button('新增任務').width(200).margin({bottom: 10}).onClick(() => {// 1.新增任務數據this.tasks.push(new Task())// 2.更新任務總數量this.handleTaskChange()})//3.任務列表List({space: 10}){ForEach(this.tasks,(item: Task, index) => {ListItem(){Row(){Text(item.name).fontSize(20)Checkbox().select(item.finished).onChange(val => {// 1.更新當前任務狀態item.finished = val// 2.更新已完成任務數量this.handleTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}.width('100%').height('100%').backgroundColor('#F1F2F3')}@Builder DeleteButton(index: number){Button(){Image($r('app.media.ic_public_delete_filled')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.handleTaskChange()})}
}
示例代碼說明:
這是一個展示任務進度的效果,分為任務進度條和任務列表兩部分
對于新增的任務勾選后可在任務進度中查看已勾選和總任務數量,左滑單個任務會出現刪除按鈕,可進行此任務刪除操作
示例代碼的效果:
?
三、父子組件數據同步
3.1、@Prop裝飾器:父子單向同步
@Prop裝飾的變量可以和父組件建立單向的同步關系。@Prop裝飾的變量是可變的,但是變化不會同步回其父組件。
需求:將示例代碼中的任務進度卡片封裝成TaskStatistics組件,在PropLinkPages組件中引入TaskStatistics組件,封裝后再完成數據的同步渲染
上面示例中:
父組件PropLinkPages,子組件TaskStatistics
總任務與已完成任務數據是由父組件進行維護,子組件進行渲染,所以需要父組件將數據傳遞給子組件
?使用@Prop,父子單向同步
@Prop只支持string、number、boolean、enum類型;父組件對象類型,子組件是對象屬性;不可以是數組、any
3.2、@Link裝飾器:父子雙向同步?
@Link裝飾的變量與其父組件中的數據源共享相同的值。
限制條件:@Link裝飾器不能在@Entry裝飾的自定義組件中使用
需求:將示例代碼中對任務數組的操作(新增任務與任務列表)封裝成TaskList組件,在PropLinkPages組件中引入TaskList組件
上面示例中:
父組件PropLinkPages,子組件TaskList
父子雙方都需要使用總認為與已完成任務數據,并且子組件的數據發生變化后需要通知父組件進行變化,因為上一步@Prop時父組件需要將數據傳遞給另一個子組件TaskStatistics,所以涉及到父子雙向數據綁定渲染
?使用@Link,父子雙向同步
父子類型一致:string、number、boolean、enum、object、class,以及他們的數組;
數組中元素增、刪、替換會引起刷新
嵌套類型以及數組中的對象屬性無法觸發視圖更新
?四、后代組件雙向同步
4.1、@Provide裝飾器和@Consume裝飾器:與后代組件雙向同步
@Provide和@Consume,應用于與后代組件的雙向數據同步,應用于狀態數據在多個層級之間傳遞的場景。
需求:示例代碼中分別使用@Prop與@Link進行數據傳遞,需要更改為@Provide和@Consume跨組件數據傳遞
上面示例中:
父組件PropLinkPages,子組件TaskList,子組件TaskStatistics
在父組件中使用@Provide將所需數據傳給兩個子組件,兩個子組件通過使用@Consume去獲取@Provide提供的變量,建立在@Provide和@Consume之間的雙向數據同步
?@Provide和@Consume可以通過相同的變量名或者相同的變量別名綁定,變量類型必須相同。
五、嵌套類對象屬性變化
5.1、@Observed裝飾器和@ObjectLink裝飾器:嵌套類對象屬性變化
對于多層嵌套的情況,比如二維數組,或者數組項class,或者class的屬性是class,他們的第二層的屬性變化是無法觀察到的。這就引出了@Observed/@ObjectLink裝飾器。
限制條件:
a:使用@Observed裝飾class會改變class原始的原型鏈,@Observed和其他類裝飾器裝飾同一個class可能會帶來問題。
b:@ObjectLink裝飾器不能在@Entry裝飾的自定義組件中使用。
需求: 改造任務進度的代碼,當任務完成后,此任務置灰,并有中劃線
實現步驟:
①任務數組對應的元素Task是對象類型,給Task對象添加@Observed裝飾器
②給嵌套的對象上所對應的變量上添加@ObjectLink裝飾器,但源代碼中是方法參數,所以將此段代碼封裝為TaskItem組件,在TaskItem組件中對變量item添加@ObjectLink
問題:子組件需要調父組件的方法,把父組件的方法作為參數傳遞過來,傳遞過程中存在this的丟失
解決:子組件中定義onTaskChange方法,傳遞給父組件時對函數使用bind方法將this傳遞進去
如下:TaskItem({item:item,onTaskChange:this.handleTaskChange.bind(this)})
// 任務類
@Observed
class Task{static id: number = 1// 任務名稱name: string = `任務${Task.id++}`// 任務狀態:是否完成finished: boolean = false
}
// 統一的卡片樣式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}
// 任務完成樣式
@Extend(Text) function finishedTask(){.decoration({type:TextDecorationType.LineThrough}).fontColor('#B1B2B1')
}@Entry
@Component
struct PropLinkPages {// 總任務數量@Provide totalTask: number = 0// 已完成任務數量@Provide finishTask: number = 0build() {Column({space:10}){//1.任務進度卡片TaskStatistics()//2.任務列表TaskList()}.width('100%').height('100%').backgroundColor('#F1F2F3')}
}@Component
struct TaskList {// 任務數組@State tasks: Task[] = []@Consume totalTask: number@Consume finishTask: number//此函數是更新任務總數量和已完成任務數量的handleTaskChange(){// 1.更新任務總數量this.totalTask = this.tasks.length// 2.更新已完成任務數量this.finishTask = this.tasks.filter(item => item.finished).length}build() {Column(){// 2.新增任務按鈕Button('新增任務').width(200).margin({bottom: 10}).onClick(() => {// 1.新增任務數據this.tasks.push(new Task())// 2.更新任務總數量this.handleTaskChange()})//3.任務列表List({space: 10}){ForEach(this.tasks,(item: Task, index) => {ListItem(){TaskItem({item:item,onTaskChange:this.handleTaskChange.bind(this)})}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}}@Builder DeleteButton(index: number){Button(){Image($r('app.media.ic_public_delete_filled')).fillColor(Color.White).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(() => {this.tasks.splice(index, 1)this.handleTaskChange()})}
}@Component
struct TaskStatistics {@Consume totalTask: number@Consume finishTask: numberbuild() {Row(){Text('任務進度:').fontSize(30).fontWeight(FontWeight.Bold)Stack(){Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring}).width(100)Row(){Text(this.finishTask.toString()).fontSize(24).fontColor('#36D')Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.card().margin({top: 5, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly)}
}@Component
struct TaskItem {@ObjectLink item: TaskonTaskChange: () => voidbuild() {Row(){if(this.item.finished){Text(this.item.name).finishedTask()}else{Text(this.item.name)}Checkbox().select(this.item.finished).onChange(val => {// 1.更新當前任務狀態this.item.finished = val// 2.更新已完成任務數量this.onTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}
}
實現效果:
最后:👏👏😊😊😊👍👍