美文网首页
鸿蒙应用开发-状态管理

鸿蒙应用开发-状态管理

作者: irenb | 来源:发表于2024-01-09 16:59 被阅读0次

一、基本概念

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

在声明式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})
  }
}

相关文章

网友评论

      本文标题:鸿蒙应用开发-状态管理

      本文链接:https://www.haomeiwen.com/subject/zvlandtx.html