一、狀態(tài)管理
在聲明式UI編程框架中,UI是程序狀態(tài)的運行結(jié)果,用戶構(gòu)建了一個UI模型,其中應(yīng)用的運行時的狀態(tài)是參數(shù)。當(dāng)參數(shù)改變時,UI作為返回結(jié)果,也將進(jìn)行對應(yīng)的改變。這些運行時的狀態(tài)變化所帶來的UI的重新渲染,在ArkUI中統(tǒng)稱為狀態(tài)管理機制。
自定義組件擁有變量,變量必須被裝飾器裝飾才可以成為狀態(tài)變量,狀態(tài)變量的改變會引起UI的渲染刷新。如果不使用狀態(tài)變量,UI只能在初始化時渲染,后續(xù)將不會再刷新。下圖展示了State和View(UI)之間的關(guān)系。
說明如下:
- View(UI):UI渲染,一般指自定義組件的build方法和@Builder裝飾的方法內(nèi)的UI描述。
- State:狀態(tài),一般指的是裝飾器裝飾的數(shù)據(jù)。用戶通過觸發(fā)組件的事件方法,改變狀態(tài)數(shù)據(jù)。狀態(tài)數(shù)據(jù)的改變,引起UI的重新渲染。
二、@State修飾符
@State
?裝飾的變量是組件內(nèi)部的狀態(tài)數(shù)據(jù),當(dāng)這些狀態(tài)數(shù)據(jù)被修改時,將會調(diào)用所在組件的?build()
?方法刷新UI。?@State
?狀態(tài)數(shù)據(jù)具有以下特征:
- @State裝飾器標(biāo)記的變量必須初始化,不能為空值
- @state支持object、class、string、number、boolean、enum類型以及這些類型的數(shù)組
- 嵌套類型以及數(shù)組中的對象屬性無法觸發(fā)視圖更新
- 標(biāo)記為?
@State
?的屬性是私有變量,只能在組件內(nèi)訪問。
2.1.@State修飾符案例
創(chuàng)建StateExample01.ets,代碼如下:
@Entry
@Component
struct StateExample01 {
@State message:string = "Hello World"
build() {
Column(){
Text(this.message)
.fontSize(50)
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量,然后會自動修改刷新UI
this.message = "Hi Augus"
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
預(yù)覽效果如下:
2.2.@state修飾的私有變量類型
@state支持object、class、string、number、boolean、enum類型以及這些類型的數(shù)組,下面演示,點擊修改Sutdent對象的年齡屬性,點擊一次,頁面重新渲染一次:
class Student{
sid:number
name:string
age:number
constructor(sid:number,name:string,age:number) {
this.sid = sid
this.name = name
this.age = age
}
}
@Entry
@Component
struct StateExample02{
//私有變量的值是一個對象
@State s:Student = new Student(2301,"馬保國", 73)
//@State必須初始化。否則會報錯
//@State s:Student
build() {
Column(){
Text(`${this.s.sid}:${this.s.name}:${this.s.age}`)
.fontSize(30)
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量(點擊一次自增1),然后會自動修改刷新UI
this.s.age++
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
預(yù)覽效果如下:
2.3.嵌套類型的對象屬性無法觸發(fā)視圖更新
下面的案例中Student對象嵌套了一個Pet對象,當(dāng)修改Pet對象屬性的時候,是無法觸發(fā)視圖的更新,下面的代碼中,點擊的時候雖然數(shù)據(jù)修改了,點擊界面并沒有修改,代碼如下:
class Student{
sid:number
name:string
age:number
//寵物
pet:Pet
constructor(sid:number,name:string,age:number,pet:Pet) {
this.sid = sid
this.name = name
this.age = age
this.pet = pet
}
}
//寵物
class Pet{
petName:string
petAge:number
constructor(petName:string,petAge:number) {
this.petName = petName
this.petAge = petAge
}
}
@Entry
@Component
struct StateExample03{
//私有變量的值是一個對象
@State s:Student = new Student(2301,"馬保國", 73, new Pet("大黃",3))
//@State必須初始化。否則會報錯
//@State s:Student
build() {
Column(){
//修改Student的屬性是可以的
Text(`${this.s.sid}:${this.s.name}:${this.s.age}`)
.fontSize(30)
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量(點擊一次自增1),然后會自動修改刷新UI
this.s.age++
})
//修改Student的中包含的pet對象的屬性值,@State裝飾器是做不到的
Text(`${this.s.pet.petName}:${this.s.pet.petAge}`)
.fontSize(30)
.onClick(()=>{
//點擊修改變屬性的值
this.s.pet.petAge++
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
預(yù)覽效果如下:
2.4.數(shù)組中的對象屬性無法觸發(fā)視圖更新
class Student{
sid:number
name:string
age:number
//寵物
pet:Pet
constructor(sid:number,name:string,age:number,pet:Pet) {
this.sid = sid
this.name = name
this.age = age
this.pet = pet
}
}
//寵物
class Pet{
petName:string
petAge:number
constructor(petName:string,petAge:number) {
this.petName = petName
this.petAge = petAge
}
}
@Entry
@Component
struct StateExample03{
//私有變量的值是一個對象
@State s:Student = new Student(2301,"馬保國", 73, new Pet("大黃",3))
//準(zhǔn)備一個數(shù)組
@State pets:Pet[] = [new Pet("小白",2300), new Pet("小癡", 1100)]
build() {
Column({space:20}){
//修改Student的屬性是可以的
Text(`${this.s.sid}:${this.s.name}:${this.s.age}`)
.fontSize(30)
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量(點擊一次自增1),然后會自動修改刷新UI
this.s.age++
})
//添加寵物
Button("添加").onClick(()=>{
this.pets.push(new Pet("小灰"+1, 10))
})
Text("---------寵物列表------").fontSize(30).width("100%")
ForEach(this.pets,(pet:Pet, index)=>{
Row(){
Text(`${pet.petName}:${pet.petAge}`).fontSize(20)
Button("修改年齡").onClick(()=>{
//點擊后發(fā)現(xiàn)修改了數(shù)據(jù),但是由于屬性屬于數(shù)組的對象,@State無法讓修改后自動渲染
pet.petAge++
})
}.width("100%").justifyContent(FlexAlign.SpaceAround)
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
點擊修改的年齡是屬于,pets數(shù)組中對象的屬性,使用@State裝飾器無法觸發(fā)視圖的渲染,點擊頁面無法更新,預(yù)覽效果如下:
三、案例練習(xí)
這里實現(xiàn)如下效果,作為后續(xù)裝飾器講解的案例代碼。
代碼如下:
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
@Entry
@Component
struct StatusManagement {
//總?cè)蝿?wù)數(shù)量
@State totalTask:number = 0
//已完成數(shù)量
@State finishTask:number = 0
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
/*//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
四、@Prop和@Link
之前章節(jié)的時候,我們吧所有的代碼都寫在一起,這樣會導(dǎo)致代碼的可讀性很差,所以我們會把功能封裝成不同的組件
這時候在父子組件需要進(jìn)行數(shù)據(jù)同步的時候,可以通過@Prop和@Link裝飾器來做到。在父組件中用@State裝飾,在自組件中用@Prop或@Link裝飾。
說明:
- @Prop用于子組件只監(jiān)聽父組件的數(shù)據(jù)改變而改變,自己不對數(shù)據(jù)改變,俗稱單項同步
- @Link用于子組件與父組件都會對數(shù)據(jù)改變,都需要在數(shù)據(jù)改變的時候發(fā)生相應(yīng)的更新。俗稱雙向同步
4.1.@Prop裝飾器
將章節(jié)二中的代碼,數(shù)據(jù)統(tǒng)計和展示分別抽取成兩個子組件,這里先抽取出來數(shù)據(jù)統(tǒng)計部分,代碼如下:
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
@Entry
@Component
struct StatusManagement {
//總?cè)蝿?wù)數(shù)量
@State totalTask:number = 0
//已完成數(shù)量
@State finishTask:number = 0
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
/*//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件
TaskStatusProgress({totalTask:this.totalTask, finishTask: this.finishTask})
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO “@Prop”、“@Link”修飾的變量不允許在本地初始化
//總?cè)蝿?wù)數(shù)量
@Prop totalTask:number
//已完成數(shù)量
@Prop finishTask:number
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
上面的代碼將任務(wù)進(jìn)度抽取成組件TaskStatusProgress ,然后在調(diào)用即可,但是需要注意的是,作為子組件TaskStatusProgress?,只需要監(jiān)控父組件的任務(wù)總量和已完成任務(wù)的值,然后自己進(jìn)行渲染即可,并不需要改變數(shù)據(jù),所以在TaskStatusProgress 子組件中定義任務(wù)總量和任務(wù)進(jìn)度變量的時候,使用@Prop裝飾器。
4.2.@Link裝飾器
將新增任務(wù)按鈕和任務(wù)列表抽取成第二個子組件TaskList,由于TaskList子組件本身需要修改數(shù)據(jù)(任務(wù)總量和已完成任務(wù)進(jìn)度),同時父組件需要感知到子組件的修改,將數(shù)據(jù)傳入到上一章節(jié)定義TaskStatusProgress子組件中,進(jìn)行數(shù)據(jù)展示,所以這是一個雙向的數(shù)據(jù)同步,需要在子組件中定義變量任務(wù)總量和已完成任務(wù)的時候使用@Link裝飾器實現(xiàn)雙向的數(shù)據(jù)同步。但是需要注意的是,在父組件調(diào)用TaskLink子組件的時候,傳入?yún)?shù)的時候需要使用$,同時不能使用this,才可以如下:
//2.任務(wù)列表
TaskList({totalTask: $totalTask, finishTask:$finishTask})
子組件TaskList如下:
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//總?cè)蝿?wù)數(shù)量
@Link totalTask:number
//已完成數(shù)量
@Link finishTask:number
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
/*//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
完整的代碼如下:
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
@Entry
@Component
struct StatusManagement {
//總?cè)蝿?wù)數(shù)量
@State totalTask:number = 0
//已完成數(shù)量
@State finishTask:number = 0
//保存添加任務(wù)的數(shù)組
//@State tasks: Task[] = []
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件
TaskStatusProgress({totalTask:this.totalTask, finishTask: this.finishTask})
//2.任務(wù)列表
TaskList({totalTask: $totalTask, finishTask:$finishTask})
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO “@Prop”、“@Link”修飾的變量不允許在本地初始化
//總?cè)蝿?wù)數(shù)量
@Prop totalTask:number
//已完成數(shù)量
@Prop finishTask:number
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//總?cè)蝿?wù)數(shù)量
@Link totalTask:number
//已完成數(shù)量
@Link finishTask:number
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
/*//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
4.2.變量數(shù)據(jù)類型說明
@Prop和@Link變量類型和初始化方式說明如下:
需要注意的是,數(shù)據(jù)同步的時候:
- @Prop父組件是對象類型,則子組件是對象屬性
- @Link父子類型一致
1)Prop父組件變量是對象類型,則子組件是對象屬性,這里以TaskStatusProgress任務(wù)進(jìn)度子組件進(jìn)行演示,因為TaskList必須是雙向同步,父組件才可以知道數(shù)據(jù)變化,必須使用@Link
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
//將統(tǒng)計信息抽取出來形成一個類
class StateInfo{
//總?cè)蝿?wù)數(shù)量
totalTask:number
//已完成數(shù)量
finishTask:number
constructor( totalTask:number = 0,finishTask:number = 0 ) {
this.totalTask = totalTask
this.finishTask = finishTask
}
}
@Entry
@Component
struct StatusManagement {
//TODO 父子組件變量類型是對象, @Prop子組件變量類型是對象的屬性
//創(chuàng)建統(tǒng)計信息對象
@State stat: StateInfo = new StateInfo()
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件,使用的是@Prop,通過屬性傳入
TaskStatusProgress({totalTask:this.stat.totalTask, finishTask: this.stat.finishTask})
//2.任務(wù)列表
//TODO 子組件使用的@Link, 通過$符的方式傳值
TaskList({stat:$stat})
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO 父組件是對象,子組件則可以使用“@Prop”作為對象的屬性
//總?cè)蝿?wù)數(shù)量
@Prop totalTask:number
//已完成數(shù)量
@Prop finishTask:number
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//TODO
@Link stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
2)@Link演示,父子組件變量同為對象
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
//將統(tǒng)計信息抽取出來形成一個類
class StateInfo{
//總?cè)蝿?wù)數(shù)量
totalTask:number
//已完成數(shù)量
finishTask:number
constructor( totalTask:number = 0,finishTask:number = 0 ) {
this.totalTask = totalTask
this.finishTask = finishTask
}
}
@Entry
@Component
struct StatusManagement {
//TODO @Link 父子組件變量類型都可以是對象
//創(chuàng)建統(tǒng)計信息對象
@State stat: StateInfo = new StateInfo()
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件
TaskStatusProgress({totalTask:this.stat.totalTask, finishTask: this.stat.finishTask})
//2.任務(wù)列表
//TODO 這里任然使用$參數(shù)名的形式
TaskList({stat:$stat})
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO “@Prop”、“@Link”修飾的變量不允許在本地初始化
//總?cè)蝿?wù)數(shù)量
@Prop totalTask:number
//已完成數(shù)量
@Prop finishTask:number
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.finishTask, total:this.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//TODO @Link 父子組件變量類型都可以是對象
//總?cè)蝿?wù)數(shù)量
@Link stat:StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
/*//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
五、@Provide和Consume
@Provide和Consume可以跨組件提供類似于@State和@Link的雙向同步。如下圖所示:
但是需要注意 :
- @Provide:父組件使用
- @Consume:子組件或者后代組件使用
- 同時在在調(diào)用子組件或者后代組件的時候,子組件或者后代組件定義了參數(shù),也是不需要傳入,會自動隱式的傳入
代碼案例如下:
//任務(wù)類
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
//將統(tǒng)計信息抽取出來形成一個類
class StateInfo{
//總?cè)蝿?wù)數(shù)量
totalTask:number
//已完成數(shù)量
finishTask:number
constructor( totalTask:number = 0,finishTask:number = 0 ) {
this.totalTask = totalTask
this.finishTask = finishTask
}
}
@Entry
@Component
struct StatusManagement {
//TODO 父子組件變量類型是對象, @Prop子組件變量類型是對象的屬性
//創(chuàng)建統(tǒng)計信息對象
@Provide stat: StateInfo = new StateInfo()
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件,使用的是@Prop,通過屬性傳入
TaskStatusProgress()
//2.任務(wù)列表
//TODO 子組件使用的@Link, 通過$符的方式傳值
TaskList()
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO 通過@Consume實現(xiàn)雙向同步,調(diào)用組件的時候不需要傳入值,會自動傳入
@Consume stat: StateInfo
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.stat.finishTask, total:this.stat.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.stat.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.stat.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//TODO 通過@Consume實現(xiàn)雙向同步,調(diào)用組件的時候不需要傳入值,會自動傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
Row(){
//文本
Text(item.name).fontColor(20)
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計已完成的數(shù)量,就是統(tǒng)計數(shù)組中狀態(tài)為true的元素個數(shù)
this.finishTask = this.tasks.filter(item=> item.taskStatus).length*/
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
預(yù)覽效果如下:
六、@Observed和@objectLink
@objectLink和@observed裝飾器用于在涉及嵌套對象或數(shù)組元素為對象的場景中進(jìn)行雙向數(shù)據(jù)同步
6.1.案例1
以之前的學(xué)生信息展示的基礎(chǔ)案例中,點擊修改學(xué)生寵物年齡的功能和修改寵物列表中寵物信息,修改后無法同步為例,原因在于:
- 學(xué)生的寵物年齡,是屬于對象的嵌套
- 寵物列表是屬于數(shù)組中有對象
要解決上面的問題,就需要@Observed和@objectLink裝飾器來實現(xiàn)
1)需要給嵌套的對象和數(shù)組中對象添加@Observed裝飾器,Pet對象屬于嵌套的所以添加裝飾器
class Student{
sid:number
name:string
age:number
//寵物
pet:Pet
constructor(sid:number,name:string,age:number,pet:Pet) {
this.sid = sid
this.name = name
this.age = age
this.pet = pet
}
}
@Observed //實現(xiàn)雙向數(shù)據(jù)同步
//寵物
class Pet{
petName:string
petAge:number
constructor(petName:string,petAge:number) {
this.petName = petName
this.petAge = petAge
}
}
2)將需要修改重新渲染的功能抽取出來定義子組件,然后給變量添加@objectLink注解
/**
* 數(shù)組元素為對象,實現(xiàn)數(shù)據(jù)同步
*/
@Component
struct PetList {
//子組件的變量必須使用@ObjectLink
@ObjectLink pet:Pet
build() {
Row(){
Text(`${this.pet.petName}:${this.pet.petAge}`).fontSize(20)
Button("修改年齡").onClick(()=>{
//點擊后發(fā)現(xiàn)修改了數(shù)據(jù),但是由于屬性屬于數(shù)組的對象,@State無法讓修改后自動渲染
this.pet.petAge++
})
}.width("100%").justifyContent(FlexAlign.SpaceAround)
}
}
/**
* 嵌套對象,實現(xiàn)數(shù)據(jù)同步
*/
@Component
struct PetInfo {
//子組件的變量必須使用@ObjectLink
@ObjectLink pet:Pet
build() {
//修改Student的屬性是可以的
Text(`寵物:${this.pet.petName},${this.pet.petAge}`)
.fontSize(30)
}
}
注意:其中的對象嵌套,學(xué)生對象里面有個寵物對象,這里在定義的時候,接受的參數(shù)一定是寵物對象
3)調(diào)用定義的子組件
@Entry
@Component
struct StateExample03{
//私有變量的值是一個對象
@State s:Student = new Student(2301,"馬保國", 73, new Pet("大黃",3))
//準(zhǔn)備一個數(shù)組
@State pets:Pet[] = [new Pet("小白",2300), new Pet("小癡", 1100)]
build() {
Column({space:20}){
/**
* 數(shù)組元素為對象,實現(xiàn)數(shù)據(jù)同步
* 調(diào)用PetInfo, 這里的this.s.pet是屬于student對象的pet屬性
*/
PetInfo({pet:this.s.pet})
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量(點擊一次自增1),然后會自動修改刷新UI
this.s.pet.petAge++
})
//添加寵物
Button("添加").onClick(()=>{
this.pets.push(new Pet("小灰"+1, 10))
})
Text("---------寵物列表------").fontSize(30).width("100%")
ForEach(this.pets,(pet:Pet, index)=>{
/**
* 嵌套對象,實現(xiàn)數(shù)據(jù)同步
* 調(diào)用PetList
*/
PetList({pet:pet})
.onClick(()=>{
//變量通過@State修飾,點擊修改私有變量(點擊一次自增1),然后會自動修改刷新UI
this.s.pet.petAge++
})
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
6.1.案例2
還是任務(wù)進(jìn)度列表案例,之前的功能還剩余一部分,當(dāng)任務(wù)完成后,任務(wù)的名稱需要置灰并且出現(xiàn)中劃線,效果如下所示:
1)在任務(wù)類上添加裝飾器@Observed
//任務(wù)類
@Observed
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
2)在任務(wù)列表中渲染任務(wù)組件功能抽取出來形成子組件,里面使用@ObjectLink裝飾器修飾變量
//任務(wù)列表置灰加下劃線樣式組件
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough}) //LineThrough
.fontColor("#B1B2B1")
}
/**
* 這個由于任務(wù)列表里面存放的對象,所以需要使用@objectLink,實現(xiàn)雙向同步,抽取組件
*/
@Component
struct TaskItem {
//雙向同步數(shù)組中的對象
@ObjectLink item:Task
//由于數(shù)據(jù)更新函數(shù),在父組件TaskList,無法移動到這里,所以需要把父組件中的數(shù)據(jù)跟新的函數(shù)DataUpdate(),當(dāng)成參數(shù)傳遞給子組件
onChangeTask: ()=>void //表示onChangeTask是一個無參返回值為void的函數(shù)
build() {
Row(){
//TODO 判斷是否是完成狀態(tài),如果是完成狀態(tài),則修改為置灰加中劃線
if(this.item.taskStatus){
Text(this.item.name).finishedTask() //調(diào)用定義的樣式組件
}else {
//文本
Text(this.item.name).fontColor(20)
}
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(this.item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
this.item.taskStatus = value
//2.上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.onChangeTask() //更新數(shù)據(jù)方法在父組件,當(dāng)成參數(shù)傳遞到這里,然后調(diào)用
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
}
3)在任務(wù)列表組件中調(diào)用上面封裝的子組件?TaskItem,代碼如下:
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//TODO 通過@Consume實現(xiàn)雙向同步,調(diào)用組件的時候不需要傳入值,會自動傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
//實現(xiàn)數(shù)組中對象數(shù)據(jù)的同步,調(diào)用封裝的子組件
//this.DataUpdate.bind(this)將函數(shù)當(dāng)成參數(shù)傳遞過去,bind(this)表示使用父組件TaskList的對象,因為更新的數(shù)據(jù)在父組件TaskList中
TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
這里有個新的問題,新定義的子組件TaskItem中沒有數(shù)據(jù)更新的方法DataUpdate,這時候無法更新數(shù)據(jù),而更新數(shù)據(jù)的方法在TaskList中,為了能在子組件中調(diào)用父組件的函數(shù),就需要在組件中定義一個參數(shù)為函數(shù),調(diào)用的時候把數(shù)據(jù)更新方法當(dāng)做函數(shù)傳入即可,語法如下:
調(diào)用的時候,數(shù)據(jù)更新的方法DataUpdate,更新的數(shù)據(jù)也在父組件中,所以需要指定是修改的父組件中的數(shù)據(jù)(綁定父組件的this),如下:
4)完整的代碼如下:
//任務(wù)類
@Observed
class Task{
static id:number = 1;
//任務(wù)名稱,id每次增加1
name:string = `任務(wù)${Task.id++}`
//任務(wù)狀態(tài),是否完成
taskStatus:boolean = false
}
//統(tǒng)一的卡片樣式
@Styles function card(){
.width("90%")
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
//為當(dāng)前組件添加陰影效果
.shadow({radius:6, color:"1F000000",offsetX:2,offsetY:4})
}
//將統(tǒng)計信息抽取出來形成一個類
class StateInfo{
//總?cè)蝿?wù)數(shù)量
totalTask:number
//已完成數(shù)量
finishTask:number
constructor( totalTask:number = 0,finishTask:number = 0 ) {
this.totalTask = totalTask
this.finishTask = finishTask
}
}
@Entry
@Component
struct StatusManagement {
//TODO 父子組件變量類型是對象, @Prop子組件變量類型是對象的屬性
//創(chuàng)建統(tǒng)計信息對象
@Provide stat: StateInfo = new StateInfo()
build() {
Column({space:20}){
//1.任務(wù)進(jìn)度 這里直接調(diào)用自定義的組件,使用的是@Prop,通過屬性傳入
TaskStatusProgress()
//2.任務(wù)列表
//TODO 子組件使用的@Link, 通過$符的方式傳值
TaskList()
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
/**
* 定義任務(wù)進(jìn)度組件
* 使用@Prop裝飾器,監(jiān)控父組件的數(shù)據(jù)狀態(tài),而改變自身的數(shù)據(jù)
*/
@Component
struct TaskStatusProgress {
//TODO 通過@Consume實現(xiàn)雙向同步,調(diào)用組件的時候不需要傳入值,會自動傳入
@Consume stat: StateInfo
build() {
//1.任務(wù)進(jìn)度
Row(){
Text("任務(wù)進(jìn)度:")
.fontSize(30) //字體大小
.fontWeight(FontWeight.Bold)//字體加粗
//環(huán)形和數(shù)字要使用堆疊容器,
Stack(){
//環(huán)形組件: 進(jìn)度、總量、樣式
Progress({value:this.stat.finishTask, total:this.stat.totalTask,type:ProgressType.Ring})
.width(90)
Row(){//讓數(shù)字顯示在一起,放在一個容器中
//任務(wù)完成量
Text(`${this.stat.finishTask}`)
.fontSize(25) //字體大小
.fontColor("#0000CD")
//任務(wù)總量
Text(` / ${this.stat.totalTask}`)
.fontSize(25) //字體大小
}
}
}
.width("100%")
.margin({top:20,bottom:20})
.justifyContent(FlexAlign.SpaceAround) //主軸方向布局
.card()
}
}
/**
* 定義任務(wù)列表子組件
*/
@Component
struct TaskList {
//TODO 通過@Consume實現(xiàn)雙向同步,調(diào)用組件的時候不需要傳入值,會自動傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
//跟新已完成任務(wù)總數(shù)
this.stat.finishTask = this.tasks.filter(item=> item.taskStatus).length
}
//自定義刪除刪除
@Builder DeleteTaskButton(index:number){
Button(){
Image($r("app.media.icon_remove_button"))
.width(20)
.fillColor("#B0E0E6")
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.onClick(()=>{
//去數(shù)組中刪除
this.tasks.splice(index, 1)
//上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.DataUpdate()
})
.backgroundColor(Color.Red)
.margin(10)
}
build() {
Column(){
//2.添加任務(wù)按鈕
Button("添加任務(wù)")
.width(200)
.onClick(()=>{
//1.添加任務(wù),就是給任務(wù)數(shù)組中添加一個值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
//實現(xiàn)數(shù)組中對象數(shù)據(jù)的同步,調(diào)用封裝的子組件
//this.DataUpdate.bind(this)將函數(shù)當(dāng)成參數(shù)傳遞過去,bind(this)表示使用父組件TaskList的對象,因為更新的數(shù)據(jù)在父組件TaskList中
TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動時item左邊的組件(List垂直布局時)或ListItem向下劃動時item上方的組件(List水平布局時)。
* - end: ListItem向左劃動時item右邊的組件(List垂直布局時)或ListItem向上劃動時item下方的組件(List水平布局時)。
* - edgeEffect: 滑動效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對齊),默認(rèn)為首部對齊。
}.width("100%").height("100%")
}
}
//任務(wù)列表置灰加下劃線樣式組件
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough}) //LineThrough
.fontColor("#B1B2B1")
}
/**
* 這個由于任務(wù)列表里面存放的對象,所以需要使用@objectLink,實現(xiàn)雙向同步,抽取組件
*/
@Component
struct TaskItem {
//雙向同步數(shù)組中的對象
@ObjectLink item:Task
//由于數(shù)據(jù)更新函數(shù),在父組件TaskList,無法移動到這里,所以需要把父組件中的數(shù)據(jù)跟新的函數(shù)DataUpdate(),當(dāng)成參數(shù)傳遞給子組件
onChangeTask: ()=>void //表示onChangeTask是一個無參返回值為void的函數(shù)
build() {
Row(){
//TODO 判斷是否是完成狀態(tài),如果是完成狀態(tài),則修改為置灰加中劃線
if(this.item.taskStatus){
Text(this.item.name).finishedTask() //調(diào)用定義的樣式組件
}else {
//文本
Text(this.item.name).fontColor(20)
}
//單選框,select決定是否選中,類型布爾值,取Task對象屬性taskStatus
Checkbox()
.select(this.item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
this.item.taskStatus = value
//2.上面的更新數(shù)據(jù)進(jìn)一步封裝,然后調(diào)用
this.onChangeTask() //更新數(shù)據(jù)方法在父組件,當(dāng)成參數(shù)傳遞到這里,然后調(diào)用
})
}
.width("100%")
.card()
.justifyContent(FlexAlign.SpaceBetween)
}
}
鴻蒙全棧開發(fā)全新學(xué)習(xí)指南
也為了積極培養(yǎng)鴻蒙生態(tài)人才,讓大家都能學(xué)習(xí)到鴻蒙開發(fā)最新的技術(shù),針對一些在職人員、0基礎(chǔ)小白、應(yīng)屆生/計算機專業(yè)、鴻蒙愛好者等人群,整理了一套純血版鴻蒙(HarmonyOS Next)全棧開發(fā)技術(shù)的學(xué)習(xí)路線[包含了大APP實戰(zhàn)項目開發(fā)]。
本路線共分為四個階段:
第一階段:鴻蒙初中級開發(fā)必備技能
第二階段:鴻蒙南北雙向高工技能基礎(chǔ):https://qr21.cn/Bm8gyp
第三階段:應(yīng)用開發(fā)中高級就業(yè)技術(shù)
第四階段:全網(wǎng)首發(fā)-工業(yè)級南向設(shè)備開發(fā)就業(yè)技術(shù):https://qr21.cn/Bm8gyp
《鴻蒙 (Harmony OS)開發(fā)學(xué)習(xí)手冊》(共計892頁)
如何快速入門?
1.基本概念
2.構(gòu)建第一個ArkTS應(yīng)用
3.……
開發(fā)基礎(chǔ)知識:https://qr21.cn/Bm8gyp
1.應(yīng)用基礎(chǔ)知識
2.配置文件
3.應(yīng)用數(shù)據(jù)管理
4.應(yīng)用安全管理
5.應(yīng)用隱私保護(hù)
6.三方應(yīng)用調(diào)用管控機制
7.資源分類與訪問
8.學(xué)習(xí)ArkTS語言
9.……
基于ArkTS 開發(fā)
1.Ability開發(fā)
2.UI開發(fā)
3.公共事件與通知
4.窗口管理
5.媒體
6.安全
7.網(wǎng)絡(luò)與鏈接
8.電話服務(wù)
9.數(shù)據(jù)管理
10.后臺任務(wù)(Background Task)管理
11.設(shè)備管理
12.設(shè)備使用信息統(tǒng)計
13.DFX
14.國際化開發(fā)
15.折疊屏系列
16.……
鴻蒙開發(fā)面試真題(含參考答案):https://qr21.cn/Bm8gyp
鴻蒙入門教學(xué)視頻:
美團APP實戰(zhàn)開發(fā)教學(xué):https://qr21.cn/Bm8gyp
文章來源:http://www.zghlxwxcb.cn/news/detail-859747.html
寫在最后
- 如果你覺得這篇內(nèi)容對你還蠻有幫助,我想邀請你幫我三個小忙:
- 點贊,轉(zhuǎn)發(fā),有你們的 『點贊和評論』,才是我創(chuàng)造的動力。
- 關(guān)注小編,同時可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識。
- 想要獲取更多完整鴻蒙最新學(xué)習(xí)資源,請移步前往小編:
https://qr21.cn/FV7h05
文章來源地址http://www.zghlxwxcb.cn/news/detail-859747.html
到了這里,關(guān)于HarmonyOS ArkUI實戰(zhàn)開發(fā)—狀態(tài)管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!