HarmonyOS——ArkUI狀態(tài)管理

一、狀態(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)
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容