鸿蒙应用开发-状态管理

一、基本概念

我们构建的页面多为静态界面。如果希望构建一个动态的、有交互的界面,就需要引入“状态”的概念。

在声明式UI编程框架中,UI是程序状态的运行结果,用户构建了一个UI模型,其中应用的运行时的状态是参数。当参数改变时,UI作为返回结果,也将进行对应的改变。这些运行时的状态变化所带来的UI的重新渲染,在ArkUI中统称为状态管理机制。

二、@State 装饰器

状态管理
class Person {
  name: string
  age: number
  girlfriend: Person

  // ?可选参数
  constructor(name: string, age: number, girlfriend?: Person) {
    this.name = name
    this.age = age
    this.girlfriend = girlfriend
  }
}

@Entry
@Component
struct StatePage {
  // 注意1:状态变量(必须初始化,不能为空值)
  @State name: string = 'Jack'
  @State age: number = 30
  
  // 改变对象属性的值,会触发页面重新渲染
  @State person1: Person = new Person('Tom', 20)
  // 注意2:对象嵌套对象,改变嵌套对象属性的值,不会触发页面重新渲染
  @State person2: Person = new Person('David', 26, new Person('Lily', 22))
  // 注意3:添加/删除/修改数组元素会触发页面重新渲染;修改数组中对象属性的值,不会触发页面重新渲染
  @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 装饰器

用途:用于父子组件数据同步传值

  • @Prop装饰属性(单向数据同步):父组件向子组件传值,传的是变量的值
  • @Link装饰属性(双向数据同步),父组件向子组件传值,必须带上$,表示传的是变量的引用
    @Prop、@Link 装饰器的用法

四、@Provide、@Consume 装饰器

  1. 用途:一般用于跨组件双向数据同步传值。相比@State@Link装饰器,使用更加方便简洁,如果使用@Link,会传递多次,使用@Provide@Consume,不需要我们做任何事(即不需要我们调用子组件手动传参),由系统内部自动帮我们做数据同步和传值。系统帮我们维护,会消耗系统一些资源,一般是在@Link装饰器不能满足使用需求时,会使用这个。

  2. 使用:父组件使用 @Provide 装饰属性,子组件/其它后代组件使用@Consume 装饰属性,注意属性名及类型和父组件保持一致,系统内部会自动帮我们做数据同步和传值。

五、@Observed、@ObjectLink 装饰器

@Observed、@ObjectLink的使用

六、任务统计案例

  1. 效果图


    任务统计效果图
  2. 实现代码
// 任务模型
@Observed  // @Observed装饰器,用于监听嵌套对象的属性,或数组元素对象的属性变化;与@ObjectLink配套使用(即子组件Task类型属性前加@ObjectLink)
class Task {
  // static 静态变量:这个类的所有对象共享(通过类访问)
  static id: number = 1
  // 任务名称(每次创建对象会加1)
  name: string = `任务${Task.id++}`
  // 任务状态:是否完成
  finished: boolean = false

  // 属性在定义的时候初始化,就不需要再写构造方法,new对象时候会自动初始化,也就不需要调用构造方法去创建对象
}

// 任务统计信息
class StatInfo {
  // 总任务数量
  totalTask: number = 0
  // 已完成任务数量
  finishTask: number = 0
}

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

// 任务完成样式
@Extend(Text) function finishedTaskStyle() {
  .fontColor('#b1b2b1')
  // 中划线
  .decoration({ type: TextDecorationType.LineThrough })
}

@Entry
@Component
struct TaskPage {
  // 统计信息
  @State statInfo: StatInfo = new StatInfo()

  // 跨组件双向数据同步传值
  //@Provide statInfo: StatInfo = new StatInfo()

  build() {
    Column({ space: 20 }) {
      // 1.任务进度卡片
      // @Prop装饰属性(单向数据同步):父组件向子组件传值,传的是变量的值
      TaskStatView({finishTask: this.statInfo.finishTask, totalTask: this.statInfo.totalTask}) // 不能传对象

      // 2.任务列表组件
      // @Link装饰属性(双向数据同步),父组件向子组件传值,必须带上$,表示传的是变量的引用
      TaskListView({statInfo: $statInfo}) // 可以传对象
        // 布局权重调高,剩下高度都给我(弹性布局),解决滚动时高度自适应屏幕边缘
        .layoutWeight(1)

      // 3.如果使用 @Provide、@Consume 装饰属性,如:
      // 父组件属性:@Provide statInfo: StatInfo = new StatInfo()
      // 子组件属性:@Consume statInfo: StatInfo
      //TaskStatView() // 调用组件时不需要传参,由系统内部自动帮我们做数组同步。注意:属性类型和属性名要保持一致
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#f1f2f3')
  }
}

// ======== 1.任务统计组件 ========
@Component
struct TaskStatView {
  // @Prop:单向数据同步(相当于传的拷贝,深拷贝),接受父组件传来的值,注意不能初始化
  @Prop finishTask: number
  @Prop totalTask: number

  build() {
    Row() {
      Text('任务进度:')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)

      // 堆叠容器
      Stack() {
        // 进度条组件(圆形)
        Progress({
          value: this.finishTask,
          total: this.totalTask,
          type: ProgressType.Ring
        })
          .width(100)

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


// ======== 2.任务列表组件 ========
@Component
struct TaskListView {
  // @Link:双向数据同步(相当于传的引用,浅拷贝),接受父组件传来的值,注意不能初始化
  @Link statInfo: StatInfo

  // 任务数组
  @State tasks: Task[] = []

  // 处理任务变更时,更新数据
  handleTaskChange() {
    this.statInfo.totalTask = this.tasks.length
    // filter:遍历数组,筛选出满足指定条件的元素
    this.statInfo.finishTask = this.tasks.filter(task => task.finished).length
  }

  build() {
    // 注意:build下只能有一个根组件
    Column({ space: 20 }) {
      // 新增任务按钮
      Button('新增任务')
        .width(200)
        .fontSize(20)
        .onClick(() => {
          // 向数组中添加元素
          this.tasks.push(new Task())
          // 处理任务变更
          this.handleTaskChange()
        })

      // 任务列表
      List() {
        ForEach(this.tasks, (item: Task, index) => {
          ListItem() {
            // 传递方法,相当于iOS中的block
            // 注意:方法传递时,要绑定当前this,不然会导致this变化,方法内部调用报错!
            TaskItemView({item: item, onTaskChange: this.handleTaskChange.bind(this)})
          }
          // 通过构建函数传入自定义删除按钮
          .swipeAction({ end: this.DeleteButton(index) })
        })
      }
      .width('100%')
      .height('100%')
      // List中元素居中对齐
      .alignListItem(ListItemAlign.Center)
      // 布局权重调高,剩下高度都给我(弹性布局),解决滚动时高度自适应屏幕边缘
      .layoutWeight(1)
    }
  }

  // 构建函数:自定义删除按钮
  @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(() => {
      // 删除数组元素(从index开始,删除1个)
      this.tasks.splice(index, 1)
      // 处理任务变更
      this.handleTaskChange()
    })
  }
}

// ======== 3.任务卡片组件 ========
@Component
struct TaskItemView {
  // Task类也需要加 @Observed 进行装饰
  // 使用 @Observed 和 @ObjectLink,item对象属性发生变化,触发视图重新渲染
  @ObjectLink item: Task

  // 接收父组件传过来的方法(变量是函数类型,相当于iOS中的block回调)
  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
          // 注意:这里相当于是调用了 handleTaskChange 方法
          this.onTaskChange()
        })
    }
    .cardStyle()
    .justifyContent(FlexAlign.SpaceBetween)
    .margin({bottom: 10})
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容