一、狀態(tài)管理
在聲明式UI編程框架中,UI是程序狀態(tài)的運(yùn)行結(jié)果,用戶構(gòu)建了一個(gè)UI模型,其中應(yīng)用的運(yùn)行時(shí)的狀態(tài)是參數(shù)。當(dāng)參數(shù)改變時(shí),UI作為返回結(jié)果,也將進(jìn)行對(duì)應(yīng)的改變。這些運(yùn)行時(shí)的狀態(tài)變化所帶來的UI的重新渲染,在ArkUI中統(tǒng)稱為狀態(tài)管理機(jī)制。
自定義組件擁有變量,變量必須被裝飾器裝飾才可以成為狀態(tài)變量,狀態(tài)變量的改變會(huì)引起UI的渲染刷新。如果不使用狀態(tài)變量,UI只能在初始化時(shí)渲染,后續(xù)將不會(huì)再刷新。下圖展示了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ù)被修改時(shí),將會(huì)調(diào)用所在組件的 build() 方法刷新UI。 @State 狀態(tài)數(shù)據(jù)具有以下特征:
- @State裝飾器標(biāo)記的變量必須初始化,不能為空值
- @state支持object、class、string、number、boolean、enum類型以及這些類型的數(shù)組
- 嵌套類型以及數(shù)組中的對(duì)象屬性無法觸發(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修飾,點(diǎn)擊修改私有變量,然后會(huì)自動(dòng)修改刷新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ù)組,下面演示,點(diǎn)擊修改Sutdent對(duì)象的年齡屬性,點(diǎn)擊一次,頁面重新渲染一次:
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{
//私有變量的值是一個(gè)對(duì)象
@State s:Student = new Student(2301,"馬保國(guó)", 73)
//@State必須初始化。否則會(huì)報(bào)錯(cuò)
//@State s:Student
build() {
Column(){
Text(`${this.s.sid}:${this.s.name}:${this.s.age}`)
.fontSize(30)
.onClick(()=>{
//變量通過@State修飾,點(diǎn)擊修改私有變量(點(diǎn)擊一次自增1),然后會(huì)自動(dòng)修改刷新UI
this.s.age++
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
預(yù)覽效果如下:

2.3.嵌套類型的對(duì)象屬性無法觸發(fā)視圖更新
下面的案例中Student對(duì)象嵌套了一個(gè)Pet對(duì)象,當(dāng)修改Pet對(duì)象屬性的時(shí)候,是無法觸發(fā)視圖的更新,下面的代碼中,點(diǎn)擊的時(shí)候雖然數(shù)據(jù)修改了,點(diǎn)擊界面并沒有修改,代碼如下:
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{
//私有變量的值是一個(gè)對(duì)象
@State s:Student = new Student(2301,"馬保國(guó)", 73, new Pet("大黃",3))
//@State必須初始化。否則會(huì)報(bào)錯(cuò)
//@State s:Student
build() {
Column(){
//修改Student的屬性是可以的
Text(`${this.s.sid}:${this.s.name}:${this.s.age}`)
.fontSize(30)
.onClick(()=>{
//變量通過@State修飾,點(diǎn)擊修改私有變量(點(diǎn)擊一次自增1),然后會(huì)自動(dòng)修改刷新UI
this.s.age++
})
//修改Student的中包含的pet對(duì)象的屬性值,@State裝飾器是做不到的
Text(`${this.s.pet.petName}:${this.s.pet.petAge}`)
.fontSize(30)
.onClick(()=>{
//點(diǎn)擊修改變屬性的值
this.s.pet.petAge++
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
預(yù)覽效果如下:

2.4.數(shù)組中的對(duì)象屬性無法觸發(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{
//私有變量的值是一個(gè)對(duì)象
@State s:Student = new Student(2301,"馬保國(guó)", 73, new Pet("大黃",3))
//準(zhǔn)備一個(gè)數(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修飾,點(diǎn)擊修改私有變量(點(diǎn)擊一次自增1),然后會(huì)自動(dòng)修改刷新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(()=>{
//點(diǎn)擊后發(fā)現(xiàn)修改了數(shù)據(jù),但是由于屬性屬于數(shù)組的對(duì)象,@State無法讓修改后自動(dòng)渲染
pet.petAge++
})
}.width("100%").justifyContent(FlexAlign.SpaceAround)
})
}
.width("100%").height("100%")
.justifyContent(FlexAlign.Center)//主軸方向?qū)R
}
}
點(diǎn)擊修改的年齡是屬于,pets數(shù)組中對(duì)象的屬性,使用@State裝飾器無法觸發(fā)視圖的渲染,點(diǎn)擊頁面無法更新,預(yù)覽效果如下:

三、案例練習(xí)
這里實(shí)現(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ù)組的長(zhǎng)度)
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ù)組的長(zhǎng)度)
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ù)字顯示在一起,放在一個(gè)容器中
//任務(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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}
.size({width:"100%",height:"100%"})
.backgroundColor("#F0F8FF")
}
}
四、@Prop和@Link
之前章節(jié)的時(shí)候,我們吧所有的代碼都寫在一起,這樣會(huì)導(dǎo)致代碼的可讀性很差,所以我們會(huì)把功能封裝成不同的組件

這時(shí)候在父子組件需要進(jìn)行數(shù)據(jù)同步的時(shí)候,可以通過@Prop和@Link裝飾器來做到。在父組件中用@State裝飾,在自組件中用@Prop或@Link裝飾。

說明:
- @Prop用于子組件只監(jiān)聽父組件的數(shù)據(jù)改變而改變,自己不對(duì)數(shù)據(jù)改變,俗稱單項(xiàng)同步
- @Link用于子組件與父組件都會(huì)對(duì)數(shù)據(jù)改變,都需要在數(shù)據(jù)改變的時(shí)候發(fā)生相應(yīng)的更新。俗稱雙向同步
4.1.@Prop裝飾器
將章節(jié)二中的代碼,數(shù)據(jù)統(tǒng)計(jì)和展示分別抽取成兩個(gè)子組件,這里先抽取出來數(shù)據(jù)統(tǒng)計(jì)部分,代碼如下:
//任務(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ù)組的長(zhǎng)度)
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ù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}
.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ù)字顯示在一起,放在一個(gè)容器中
//任務(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)度變量的時(shí)候,使用@Prop裝飾器。
4.2.@Link裝飾器
將新增任務(wù)按鈕和任務(wù)列表抽取成第二個(gè)子組件TaskList,由于TaskList子組件本身需要修改數(shù)據(jù)(任務(wù)總量和已完成任務(wù)進(jìn)度),同時(shí)父組件需要感知到子組件的修改,將數(shù)據(jù)傳入到上一章節(jié)定義TaskStatusProgress子組件中,進(jìn)行數(shù)據(jù)展示,所以這是一個(gè)雙向的數(shù)據(jù)同步,需要在子組件中定義變量任務(wù)總量和已完成任務(wù)的時(shí)候使用@Link裝飾器實(shí)現(xiàn)雙向的數(shù)據(jù)同步。但是需要注意的是,在父組件調(diào)用TaskLink子組件的時(shí)候,傳入?yún)?shù)的時(shí)候需要使用$,同時(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ù)組的長(zhǎng)度)
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ù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.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ù)字顯示在一起,放在一個(gè)容器中
//任務(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ù)組的長(zhǎng)度)
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ù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
4.2.變量數(shù)據(jù)類型說明
@Prop和@Link變量類型和初始化方式說明如下:

需要注意的是,數(shù)據(jù)同步的時(shí)候:
- @Prop父組件是對(duì)象類型,則子組件是對(duì)象屬性
- @Link父子類型一致
1)Prop父組件變量是對(duì)象類型,則子組件是對(duì)象屬性,這里以TaskStatusProgress任務(wù)進(jìn)度子組件進(jìn)行演示,因?yàn)門askList必須是雙向同步,父組件才可以知道數(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)計(jì)信息抽取出來形成一個(gè)類
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 父子組件變量類型是對(duì)象, @Prop子組件變量類型是對(duì)象的屬性
//創(chuàng)建統(tǒng)計(jì)信息對(duì)象
@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 父組件是對(duì)象,子組件則可以使用“@Prop”作為對(duì)象的屬性
//總?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ù)字顯示在一起,放在一個(gè)容器中
//任務(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ù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
2)@Link演示,父子組件變量同為對(duì)象
//任務(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)計(jì)信息抽取出來形成一個(gè)類
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 父子組件變量類型都可以是對(duì)象
//創(chuàng)建統(tǒng)計(jì)信息對(duì)象
@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ù)字顯示在一起,放在一個(gè)容器中
//任務(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 父子組件變量類型都可以是對(duì)象
//總?cè)蝿?wù)數(shù)量
@Link stat:StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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ù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
五、@Provide和Consume
@Provide和Consume可以跨組件提供類似于@State和@Link的雙向同步。如下圖所示:

但是需要注意 :
- @Provide:父組件使用
- @Consume:子組件或者后代組件使用
- 同時(shí)在在調(diào)用子組件或者后代組件的時(shí)候,子組件或者后代組件定義了參數(shù),也是不需要傳入,會(huì)自動(dò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})
}
//將統(tǒng)計(jì)信息抽取出來形成一個(gè)類
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 父子組件變量類型是對(duì)象, @Prop子組件變量類型是對(duì)象的屬性
//創(chuàng)建統(tǒng)計(jì)信息對(duì)象
@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實(shí)現(xiàn)雙向同步,調(diào)用組件的時(shí)候不需要傳入值,會(huì)自動(dòng)傳入
@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ù)字顯示在一起,放在一個(gè)容器中
//任務(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實(shí)現(xiàn)雙向同步,調(diào)用組件的時(shí)候不需要傳入值,會(huì)自動(dòng)傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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對(duì)象屬性taskStatus
Checkbox()
.select(item.taskStatus)
.onChange((value:boolean)=>{
//1.更新當(dāng)前已完成任務(wù)狀態(tài),勾選后修改狀態(tài)為true
item.taskStatus = value
/*//2.統(tǒng)計(jì)已完成的數(shù)量,就是統(tǒng)計(jì)數(shù)組中狀態(tài)為true的元素個(gè)數(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向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
預(yù)覽效果如下:

六、@Observed和@objectLink
@objectLink和@observed裝飾器用于在涉及嵌套對(duì)象或數(shù)組元素為對(duì)象的場(chǎng)景中進(jìn)行雙向數(shù)據(jù)同步
6.1.案例1
以之前的學(xué)生信息展示的基礎(chǔ)案例中,點(diǎn)擊修改學(xué)生寵物年齡的功能和修改寵物列表中寵物信息,修改后無法同步為例,原因在于:
- 學(xué)生的寵物年齡,是屬于對(duì)象的嵌套
- 寵物列表是屬于數(shù)組中有對(duì)象
要解決上面的問題,就需要@Observed和@objectLink裝飾器來實(shí)現(xiàn)
1)需要給嵌套的對(duì)象和數(shù)組中對(duì)象添加@Observed裝飾器,Pet對(duì)象屬于嵌套的所以添加裝飾器
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 //實(shí)現(xiàn)雙向數(shù)據(jù)同步
//寵物
class Pet{
petName:string
petAge:number
constructor(petName:string,petAge:number) {
this.petName = petName
this.petAge = petAge
}
}
2)將需要修改重新渲染的功能抽取出來定義子組件,然后給變量添加@objectLink注解
/**
* 數(shù)組元素為對(duì)象,實(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(()=>{
//點(diǎn)擊后發(fā)現(xiàn)修改了數(shù)據(jù),但是由于屬性屬于數(shù)組的對(duì)象,@State無法讓修改后自動(dòng)渲染
this.pet.petAge++
})
}.width("100%").justifyContent(FlexAlign.SpaceAround)
}
}
/**
* 嵌套對(duì)象,實(shí)現(xiàn)數(shù)據(jù)同步
*/
@Component
struct PetInfo {
//子組件的變量必須使用@ObjectLink
@ObjectLink pet:Pet
build() {
//修改Student的屬性是可以的
Text(`寵物:${this.pet.petName},${this.pet.petAge}`)
.fontSize(30)
}
}
注意:其中的對(duì)象嵌套,學(xué)生對(duì)象里面有個(gè)寵物對(duì)象,這里在定義的時(shí)候,接受的參數(shù)一定是寵物對(duì)象
3)調(diào)用定義的子組件
@Entry
@Component
struct StateExample03{
//私有變量的值是一個(gè)對(duì)象
@State s:Student = new Student(2301,"馬保國(guó)", 73, new Pet("大黃",3))
//準(zhǔn)備一個(gè)數(shù)組
@State pets:Pet[] = [new Pet("小白",2300), new Pet("小癡", 1100)]
build() {
Column({space:20}){
/**
* 數(shù)組元素為對(duì)象,實(shí)現(xiàn)數(shù)據(jù)同步
* 調(diào)用PetInfo, 這里的this.s.pet是屬于student對(duì)象的pet屬性
*/
PetInfo({pet:this.s.pet})
.onClick(()=>{
//變量通過@State修飾,點(diǎn)擊修改私有變量(點(diǎn)擊一次自增1),然后會(huì)自動(dòng)修改刷新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)=>{
/**
* 嵌套對(duì)象,實(shí)現(xiàn)數(shù)據(jù)同步
* 調(diào)用PetList
*/
PetList({pet:pet})
.onClick(()=>{
//變量通過@State修飾,點(diǎn)擊修改私有變量(點(diǎn)擊一次自增1),然后會(huì)自動(dòng)修改刷新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")
}
/**
* 這個(gè)由于任務(wù)列表里面存放的對(duì)象,所以需要使用@objectLink,實(shí)現(xiàn)雙向同步,抽取組件
*/
@Component
struct TaskItem {
//雙向同步數(shù)組中的對(duì)象
@ObjectLink item:Task
//由于數(shù)據(jù)更新函數(shù),在父組件TaskList,無法移動(dòng)到這里,所以需要把父組件中的數(shù)據(jù)跟新的函數(shù)DataUpdate(),當(dāng)成參數(shù)傳遞給子組件
onChangeTask: ()=>void //表示onChangeTask是一個(gè)無參返回值為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對(duì)象屬性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實(shí)現(xiàn)雙向同步,調(diào)用組件的時(shí)候不需要傳入值,會(huì)自動(dòng)傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
//實(shí)現(xiàn)數(shù)組中對(duì)象數(shù)據(jù)的同步,調(diào)用封裝的子組件
//this.DataUpdate.bind(this)將函數(shù)當(dāng)成參數(shù)傳遞過去,bind(this)表示使用父組件TaskList的對(duì)象,因?yàn)楦碌臄?shù)據(jù)在父組件TaskList中
TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
這里有個(gè)新的問題,新定義的子組件TaskItem中沒有數(shù)據(jù)更新的方法DataUpdate,這時(shí)候無法更新數(shù)據(jù),而更新數(shù)據(jù)的方法在TaskList中,為了能在子組件中調(diào)用父組件的函數(shù),就需要在組件中定義一個(gè)參數(shù)為函數(shù),調(diào)用的時(shí)候把數(shù)據(jù)更新方法當(dāng)做函數(shù)傳入即可,語法如下:

調(diào)用的時(shí)候,數(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)計(jì)信息抽取出來形成一個(gè)類
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 父子組件變量類型是對(duì)象, @Prop子組件變量類型是對(duì)象的屬性
//創(chuàng)建統(tǒng)計(jì)信息對(duì)象
@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實(shí)現(xiàn)雙向同步,調(diào)用組件的時(shí)候不需要傳入值,會(huì)自動(dòng)傳入
@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ù)字顯示在一起,放在一個(gè)容器中
//任務(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實(shí)現(xiàn)雙向同步,調(diào)用組件的時(shí)候不需要傳入值,會(huì)自動(dòng)傳入
@Consume stat: StateInfo
//保存添加任務(wù)的數(shù)組
@State tasks: Task[] = []
//將跟新數(shù)據(jù)的操作進(jìn)一步抽取
DataUpdate(){
//需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
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ù)組中添加一個(gè)值
this.tasks.push(new Task())
//2.新增任務(wù)后,需要跟新一下任務(wù)總量(就是任務(wù)數(shù)組的長(zhǎng)度)
this.stat.totalTask = this.tasks.length
})
//3.任務(wù)列表
List({space:5}){
ForEach(this.tasks,(item:Task, index:number)=>{
ListItem(){
//實(shí)現(xiàn)數(shù)組中對(duì)象數(shù)據(jù)的同步,調(diào)用封裝的子組件
//this.DataUpdate.bind(this)將函數(shù)當(dāng)成參數(shù)傳遞過去,bind(this)表示使用父組件TaskList的對(duì)象,因?yàn)楦碌臄?shù)據(jù)在父組件TaskList中
TaskItem({item:item, onChangeTask:this.DataUpdate.bind(this)})
}
/**
* 用于設(shè)置ListItem的劃出組件。
* - start: ListItem向右劃動(dòng)時(shí)item左邊的組件(List垂直布局時(shí))或ListItem向下劃動(dòng)時(shí)item上方的組件(List水平布局時(shí))。
* - end: ListItem向左劃動(dòng)時(shí)item右邊的組件(List垂直布局時(shí))或ListItem向上劃動(dòng)時(shí)item下方的組件(List水平布局時(shí))。
* - edgeEffect: 滑動(dòng)效果。
*/
.swipeAction({end: this.DeleteTaskButton(index)})
})
}
.width("100%")
.layoutWeight(1) //忽略元素本身尺寸設(shè)置,表示自適應(yīng)占滿剩余空間。
.alignListItem(ListItemAlign.Center) //ListItem在List交叉軸方向的布局方式(這里就是水平方向居中對(duì)齊),默認(rèn)為首部對(duì)齊。
}.width("100%").height("100%")
}
}
//任務(wù)列表置灰加下劃線樣式組件
@Extend(Text) function finishedTask(){
.decoration({type:TextDecorationType.LineThrough}) //LineThrough
.fontColor("#B1B2B1")
}
/**
* 這個(gè)由于任務(wù)列表里面存放的對(duì)象,所以需要使用@objectLink,實(shí)現(xiàn)雙向同步,抽取組件
*/
@Component
struct TaskItem {
//雙向同步數(shù)組中的對(duì)象
@ObjectLink item:Task
//由于數(shù)據(jù)更新函數(shù),在父組件TaskList,無法移動(dòng)到這里,所以需要把父組件中的數(shù)據(jù)跟新的函數(shù)DataUpdate(),當(dāng)成參數(shù)傳遞給子組件
onChangeTask: ()=>void //表示onChangeTask是一個(gè)無參返回值為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對(duì)象屬性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)
}
}