鴻蒙應(yīng)用開發(fā)-狀態(tài)管理

一、基本概念

我們構(gòu)建的頁面多為靜態(tài)界面。如果希望構(gòu)建一個動態(tài)的、有交互的界面,就需要引入“狀態(tài)”的概念。

在聲明式UI編程框架中,UI是程序狀態(tài)的運行結(jié)果,用戶構(gòu)建了一個UI模型,其中應(yīng)用的運行時的狀態(tài)是參數(shù)。當參數(shù)改變時,UI作為返回結(jié)果,也將進行對應(yīng)的改變。這些運行時的狀態(tài)變化所帶來的UI的重新渲染,在ArkUI中統(tǒng)稱為狀態(tài)管理機制。

二、@State 裝飾器

狀態(tài)管理
class Person {
  name: string
  age: number
  girlfriend: Person

  // ?可選參數(shù)
  constructor(name: string, age: number, girlfriend?: Person) {
    this.name = name
    this.age = age
    this.girlfriend = girlfriend
  }
}

@Entry
@Component
struct StatePage {
  // 注意1:狀態(tài)變量(必須初始化,不能為空值)
  @State name: string = 'Jack'
  @State age: number = 30
  
  // 改變對象屬性的值,會觸發(fā)頁面重新渲染
  @State person1: Person = new Person('Tom', 20)
  // 注意2:對象嵌套對象,改變嵌套對象屬性的值,不會觸發(fā)頁面重新渲染
  @State person2: Person = new Person('David', 26, new Person('Lily', 22))
  // 注意3:添加/刪除/修改數(shù)組元素會觸發(fā)頁面重新渲染;修改數(shù)組中對象屬性的值,不會觸發(fā)頁面重新渲染
  @State personList: Person[] = [
    new Person('張三', 18),
    new Person('李四', 19)
  ]

  build() {
    Row() {
      Column() {
        // 字符串模版語法
        Text(`${this.name} : ${this.age}`)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .onClick(() => {
            this.age++
          })

        Text(`${this.person1.name} , ${this.person1.age}`)
          .fontSize(30)
          .fontColor(Color.Blue)
          .onClick(value => {
            this.person1.age += 1
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

三、@Prop、@Link 裝飾器

用途:用于父子組件數(shù)據(jù)同步傳值

  • @Prop裝飾屬性(單向數(shù)據(jù)同步):父組件向子組件傳值,傳的是變量的值
  • @Link裝飾屬性(雙向數(shù)據(jù)同步),父組件向子組件傳值,必須帶上$,表示傳的是變量的引用
    @Prop、@Link 裝飾器的用法

四、@Provide、@Consume 裝飾器

  1. 用途:一般用于跨組件雙向數(shù)據(jù)同步傳值。相比@State、@Link裝飾器,使用更加方便簡潔,如果使用@Link,會傳遞多次,使用@Provide@Consume,不需要我們做任何事(即不需要我們調(diào)用子組件手動傳參),由系統(tǒng)內(nèi)部自動幫我們做數(shù)據(jù)同步和傳值。系統(tǒng)幫我們維護,會消耗系統(tǒng)一些資源,一般是在@Link裝飾器不能滿足使用需求時,會使用這個。

  2. 使用:父組件使用 @Provide 裝飾屬性,子組件/其它后代組件使用@Consume 裝飾屬性,注意屬性名及類型和父組件保持一致,系統(tǒng)內(nèi)部會自動幫我們做數(shù)據(jù)同步和傳值。

五、@Observed、@ObjectLink 裝飾器

@Observed、@ObjectLink的使用

六、任務(wù)統(tǒng)計案例

  1. 效果圖


    任務(wù)統(tǒng)計效果圖
  2. 實現(xiàn)代碼
// 任務(wù)模型
@Observed  // @Observed裝飾器,用于監(jiān)聽嵌套對象的屬性,或數(shù)組元素對象的屬性變化;與@ObjectLink配套使用(即子組件Task類型屬性前加@ObjectLink)
class Task {
  // static 靜態(tài)變量:這個類的所有對象共享(通過類訪問)
  static id: number = 1
  // 任務(wù)名稱(每次創(chuàng)建對象會加1)
  name: string = `任務(wù)${Task.id++}`
  // 任務(wù)狀態(tài):是否完成
  finished: boolean = false

  // 屬性在定義的時候初始化,就不需要再寫構(gòu)造方法,new對象時候會自動初始化,也就不需要調(diào)用構(gòu)造方法去創(chuàng)建對象
}

// 任務(wù)統(tǒng)計信息
class StatInfo {
  // 總?cè)蝿?wù)數(shù)量
  totalTask: number = 0
  // 已完成任務(wù)數(shù)量
  finishTask: number = 0
}

// 統(tǒng)一的卡片樣式
@Styles function cardStyle() {
  .width('90%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({ radius: 6, color: '#ccc', offsetX: 2, offsetY: 4 })
}

// 任務(wù)完成樣式
@Extend(Text) function finishedTaskStyle() {
  .fontColor('#b1b2b1')
  // 中劃線
  .decoration({ type: TextDecorationType.LineThrough })
}

@Entry
@Component
struct TaskPage {
  // 統(tǒng)計信息
  @State statInfo: StatInfo = new StatInfo()

  // 跨組件雙向數(shù)據(jù)同步傳值
  //@Provide statInfo: StatInfo = new StatInfo()

  build() {
    Column({ space: 20 }) {
      // 1.任務(wù)進度卡片
      // @Prop裝飾屬性(單向數(shù)據(jù)同步):父組件向子組件傳值,傳的是變量的值
      TaskStatView({finishTask: this.statInfo.finishTask, totalTask: this.statInfo.totalTask}) // 不能傳對象

      // 2.任務(wù)列表組件
      // @Link裝飾屬性(雙向數(shù)據(jù)同步),父組件向子組件傳值,必須帶上$,表示傳的是變量的引用
      TaskListView({statInfo: $statInfo}) // 可以傳對象
        // 布局權(quán)重調(diào)高,剩下高度都給我(彈性布局),解決滾動時高度自適應(yīng)屏幕邊緣
        .layoutWeight(1)

      // 3.如果使用 @Provide、@Consume 裝飾屬性,如:
      // 父組件屬性:@Provide statInfo: StatInfo = new StatInfo()
      // 子組件屬性:@Consume statInfo: StatInfo
      //TaskStatView() // 調(diào)用組件時不需要傳參,由系統(tǒng)內(nèi)部自動幫我們做數(shù)組同步。注意:屬性類型和屬性名要保持一致
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

// ======== 1.任務(wù)統(tǒng)計組件 ========
@Component
struct TaskStatView {
  // @Prop:單向數(shù)據(jù)同步(相當于傳的拷貝,深拷貝),接受父組件傳來的值,注意不能初始化
  @Prop finishTask: number
  @Prop totalTask: number

  build() {
    Row() {
      Text('任務(wù)進度:')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)

      // 堆疊容器
      Stack() {
        // 進度條組件(圓形)
        Progress({
          value: this.finishTask,
          total: this.totalTask,
          type: ProgressType.Ring
        })
          .width(100)

        Row() {
          Text(this.finishTask.toString())
            .fontColor('#36d')
            .fontSize(24)
          Text(' / ' + this.totalTask.toString())
            .fontSize(24)
        }
      }
    }
    .cardStyle()
    .margin({ top: 20 })
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}


// ======== 2.任務(wù)列表組件 ========
@Component
struct TaskListView {
  // @Link:雙向數(shù)據(jù)同步(相當于傳的引用,淺拷貝),接受父組件傳來的值,注意不能初始化
  @Link statInfo: StatInfo

  // 任務(wù)數(shù)組
  @State tasks: Task[] = []

  // 處理任務(wù)變更時,更新數(shù)據(jù)
  handleTaskChange() {
    this.statInfo.totalTask = this.tasks.length
    // filter:遍歷數(shù)組,篩選出滿足指定條件的元素
    this.statInfo.finishTask = this.tasks.filter(task => task.finished).length
  }

  build() {
    // 注意:build下只能有一個根組件
    Column({ space: 20 }) {
      // 新增任務(wù)按鈕
      Button('新增任務(wù)')
        .width(200)
        .fontSize(20)
        .onClick(() => {
          // 向數(shù)組中添加元素
          this.tasks.push(new Task())
          // 處理任務(wù)變更
          this.handleTaskChange()
        })

      // 任務(wù)列表
      List() {
        ForEach(this.tasks, (item: Task, index) => {
          ListItem() {
            // 傳遞方法,相當于iOS中的block
            // 注意:方法傳遞時,要綁定當前this,不然會導(dǎo)致this變化,方法內(nèi)部調(diào)用報錯!
            TaskItemView({item: item, onTaskChange: this.handleTaskChange.bind(this)})
          }
          // 通過構(gòu)建函數(shù)傳入自定義刪除按鈕
          .swipeAction({ end: this.DeleteButton(index) })
        })
      }
      .width('100%')
      .height('100%')
      // List中元素居中對齊
      .alignListItem(ListItemAlign.Center)
      // 布局權(quán)重調(diào)高,剩下高度都給我(彈性布局),解決滾動時高度自適應(yīng)屏幕邊緣
      .layoutWeight(1)
    }
  }

  // 構(gòu)建函數(shù):自定義刪除按鈕
  @Builder DeleteButton(index: number) {
    Button() {
      Image($r('app.media.app_icon'))
        .fillColor(Color.White)
        .width(20)
    }
    .width(40)
    .height(40)
    .type(ButtonType.Circle)
    .backgroundColor(Color.Red)
    .margin({left: 10})
    .onClick(() => {
      // 刪除數(shù)組元素(從index開始,刪除1個)
      this.tasks.splice(index, 1)
      // 處理任務(wù)變更
      this.handleTaskChange()
    })
  }
}

// ======== 3.任務(wù)卡片組件 ========
@Component
struct TaskItemView {
  // Task類也需要加 @Observed 進行裝飾
  // 使用 @Observed 和 @ObjectLink,item對象屬性發(fā)生變化,觸發(fā)視圖重新渲染
  @ObjectLink item: Task

  // 接收父組件傳過來的方法(變量是函數(shù)類型,相當于iOS中的block回調(diào))
  onTaskChange: () => void

  build() {
    Row() {
      // 這里
      if (this.item.finished) {
        Text(this.item.name)
          .fontSize(20)
          .finishedTaskStyle()
      } else {
        Text(this.item.name)
          .fontSize(20)
      }
      // 多選框
      Checkbox()
        .select(this.item.finished)
        .onChange(value => {
          this.item.finished = value
          // 注意:這里相當于是調(diào)用了 handleTaskChange 方法
          this.onTaskChange()
        })
    }
    .cardStyle()
    .justifyContent(FlexAlign.SpaceBetween)
    .margin({bottom: 10})
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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