0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看威廉希尔官方网站 视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Harmony 鸿蒙页面级变量的状态管理

王程 来源:jf_75796907 作者:jf_75796907 2024-01-25 10:42 次阅读

页面级变量的状态管理

@State、@Prop、@Link、@Provide、@Consume、@ObjectLink、@Observed和@Watch用于管理页面级变量的状态。

@State

@State装饰的变量是组件内部的状态数据,当这些状态数据被修改时,将会调用所在组件的build方法进行UI刷新。
@State状态数据具有以下特征:

  • 支持多种类型数据:支持class、number、boolean、string强类型数据的值类型和引用类型,以及这些强类型构成的数组,即Array、Array、Array、Array。不支持object和any。
  • 支持多实例:组件不同实例的内部状态数据独立。
  • 内部私有:标记为@State的属性是私有变量,只能在组件内访问。
  • 需要本地初始化:必须为所有@State变量分配初始值,变量未初始化可能导致未定义的框架异常行为。
  • 创建自定义组件时支持通过状态变量名设置初始值:在创建组件实例时,可以通过变量名显式指定@State状态变量的初始值。

示例:

在下面的示例中:

  • 用户定义的组件MyComponent定义了@State状态变量count和title。如果count或title的值发生变化,则执行MyComponent的build方法来重新渲染组件;
  • EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent;
  • 创建MyComponent实例时通过变量名给组件内的变量进行初始化,如:
MyComponent({ title: { value: 'Hello World 2' }, count: 7 })
// xxx.ets
class Model {
  value: string

  constructor(value: string) {
    this.value = value
  }
}

@Entry
@Component
struct EntryComponent {
  build() {
    Column() {
      MyComponent({ count: 1, increaseBy: 2 }) // 第1个MyComponent实例
      MyComponent({ title: { value: 'Hello World 2' }, count: 7 }) // 第2个MyComponent实例
    }
  }
}

@Component
struct MyComponent {
  @State title: Model = { value: 'Hello World' }
  @State count: number = 0
  private toggle: string = 'Hello World'
  private increaseBy: number = 1

  build() {
    Column() {
      Text(`${this.title.value}`).fontSize(30)
      Button('Click to change title')
        .margin(20)
        .onClick(() = > {
          // 修改内部状态变量title
          this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello ArkUI'
        })

      Button(`Click to increase count=${this.count}`)
        .margin(20)
        .onClick(() = > {
          // 修改内部状态变量count
          this.count += this.increaseBy
        })
    }
  }

@Prop

@Prop与@State有相同的语义,但初始化方式不同。@Prop装饰的变量必须使用其父组件提供的@State变量进行初始化,允许组件内部修改@Prop变量,但变量的更改不会通知给父组件,父组件变量的更改会同步到@prop装饰的变量,即@Prop属于单向数据绑定。
@Prop状态数据具有以下特征:

  • 支持简单类型:仅支持number、string、boolean等简单数据类型;
  • 私有:仅支持组件内访问;
  • 支持多个实例:一个组件中可以定义多个标有@Prop的属性;
  • 创建自定义组件时将值传递给@Prop变量进行初始化:在创建组件的新实例时,必须初始化所有@Prop变量,不支持在组件内部进行初始化。

说明:
@Prop修饰的变量不能在组件内部进行初始化。

示例:

在下面的示例中,当按“+1”或“-1”按钮时,父组件状态发生变化,重新执行build方法,此时将创建一个新的CountDownComponent组件实例。父组件的countDownStartValue状态变量被用于初始化子组件的@Prop变量,当按下子组件的“count - costOfOneAttempt”按钮时,其@Prop变量count将被更改,CountDownComponent重新渲染,但是count值的更改不会影响父组件的countDownStartValue值。

// xxx.ets
@Entry
@Component
struct ParentComponent {
  @State countDownStartValue: number = 10 // 初始化countDownStartValue

  build() {
    Column() {
      Text(`Grant ${this.countDownStartValue} nuggets to play.`).fontSize(18)
      Button('+1 - Nuggets in New Game')
        .margin(15)
        .onClick(() = > {
          this.countDownStartValue += 1
        })

      Button('-1  - Nuggets in New Game')
        .margin(15)
        .onClick(() = > {
          this.countDownStartValue -= 1
        })
      // 创建子组件时,必须在构造函数参数中提供其@Prop变量count的初始值,同时初始化常规变量costOfOneAttempt(非Prop变量)
      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
    }
  }
}

@Component
struct CountDownComponent {
  @Prop count: number
  private costOfOneAttempt: number

  build() {
    Column() {
      if (this.count > 0) {
        Text(`You have ${this.count} Nuggets left`).fontSize(18)
      } else {
        Text('Game over!').fontSize(18)
      }

      Button('count - costOfOneAttempt')
        .margin(15)
        .onClick(() = > {
          this.count -= this.costOfOneAttempt
        })
    }
  }
}

@Link

@Link装饰的变量可以和父组件的@State变量建立双向数据绑定:

  • 支持多种类型:@Link支持的数据类型与@State相同,即class、number、string、boolean或这些类型的数组;
  • 私有:仅支持组件内访问;
  • 单个数据源:父组件中用于初始化子组件@Link变量的必须是父组件定义的状态变量;
  • 双向通信:子组件对@Link变量的更改将同步修改父组件中的@State变量;
  • 创建自定义组件时需要将变量的引用传递给@Link变量,在创建组件的新实例时,必须使用命名参数初始化所有@Link变量。@Link变量可以使用@State变量或@Link变量的引用进行初始化,@State变量可以通过'$'操作符创建引用。

说明:
@Link修饰的变量不能在组件内部进行初始化。

简单类型示例:

@Link语义是从''操作符引出,即isPlaying是this.isPlaying内部状态的双向数据绑定。当单击子组件PlayButton中的按钮时,@Link变量更改,PlayButton与父组件中的Text和Button将同时进行刷新,同样地,当点击父组件中的Button修改this.isPlaying时,子组件PlayButton与父组件中的Text和Button也将同时刷新。

// xxx.ets
@Entry
@Component
struct Player {
  @State isPlaying: boolean = false

  build() {
    Column() {
      PlayButton({ buttonPlaying: $isPlaying })
      Text(`Player is ${this.isPlaying ? '' : 'not'} playing`).fontSize(18)
      Button('Parent:' + this.isPlaying)
        .margin(15)
        .onClick(() = > {
          this.isPlaying = !this.isPlaying
        })
    }
  }
}

@Component
struct PlayButton {
  @Link buttonPlaying: boolean

  build() {
    Column() {
      Button(this.buttonPlaying ? 'pause' : 'play')
        .margin(20)
        .onClick(() = > {
          this.buttonPlaying = !this.buttonPlaying
        })
    }
  }
}

复杂类型示例:

// xxx.ets
@Entry
@Component
struct Parent {
  @State arr: number[] = [1, 2, 3]

  build() {
    Column() {
      Child({ items: $arr })
      Button('Parent Button: splice')
        .margin(10)
        .onClick(() = > {
          this.arr.splice(0, 1, 60)
        })
      ForEach(this.arr, item = > {
        Text(item.toString()).fontSize(18).margin(10)
      }, item = > item.toString())
    }
  }
}


@Component
struct Child {
  @Link items: number[]

  build() {
    Column() {
      Button('Child Button1: push')
        .margin(15)
        .onClick(() = > {
          this.items.push(100)
        })
      Button('Child Button2: replace whole item')
        .margin(15)
        .onClick(() = > {
          this.items = [100, 200, 300]
        })
    }
  }
}

@Link、@State和@Prop结合使用示例:
下面示例中,ParentView包含ChildA和ChildB两个子组件,ParentView的状态变量counter分别用于初始化ChildA的@Prop变量和ChildB的@Link变量。

  • ChildB使用@Link建立双向数据绑定,当ChildB修改counterRef状态变量值时,该更改将同步到ParentView和ChildA共享;
  • ChildA使用@Prop建立从ParentView到自身的单向数据绑定,当ChildA修改counterVal状态变量值时,ChildA将重新渲染,但该更改不会传达给ParentView和ChildB。
// xxx.ets
@Entry
@Component
struct ParentView {
  @State counter: number = 0

  build() {
    Column() {
      ChildA({ counterVal: this.counter })
      ChildB({ counterRef: $counter })
    }
  }
}

@Component
struct ChildA {
  @Prop counterVal: number

  build() {
    Button(`ChildA: (${this.counterVal}) + 1`)
      .margin(15)
      .onClick(() = > {
        this.counterVal += 1
      })
  }
}

@Component
struct ChildB {
  @Link counterRef: number

  build() {
    Button(`ChildB: (${this.counterRef}) + 1`)
      .margin(15)
      .onClick(() = > {
        this.counterRef += 1
      })
  }
}

@Observed和ObjectLink数据管理

开发者需要在子组件中针对父组件的一个变量(parent_a)设置双向同步时,开发者可以在父组件中使用@State装饰变量(parent_a),并在子组件中使用@Link装饰对应的变量(child_a)。这样不仅可以实现父组件与单个子组件之间的数据同步,也可以实现父组件与多个子组件之间的数据同步。如下图所示,可以看到,父子组件针对ClassA类型的变量设置了双向同步,那么当子组件1中变量对应的属性c的值变化时,会通知父组件同步变化,而当父组件中属性c的值变化时,会通知所有子组件同步变化。

然而,上述例子是针对某个数据对象进行的整体同步,而当开发者只想针对父组件中某个数据对象的部分信息进行同步时,使用@Link就不能满足要求。如果这些部分信息是一个类对象,就可以使用@ObjectLink配合@Observed来实现,如下图所示。

设置要求

@Observed用于类,@ObjectLink用于变量。

@ObjectLink装饰的变量类型必须为类(class type)。

  • 类要被@Observed装饰器所装饰。
  • 不支持简单类型参数,可以使用@Prop进行单向同步。

@ObjectLink装饰的变量是不可变的。

  • 属性的改动是被允许的,当改动发生时,如果同一个对象被多个@ObjectLink变量所引用,那么所有拥有这些变量的自定义组件都会被通知进行重新渲染。

@ObjectLink装饰的变量不可设置默认值。

  • 必须让父组件中有一个由@State、@Link、@StorageLink、@Provide或@Consume装饰的变量所参与的TS表达式进行初始化。

@ObjectLink装饰的变量是私有变量,只能在组件内访问。

示例

// xxx.ets
// 父组件ViewB中的类对象ClassA与子组件ViewA保持数据同步时,可以使用@ObjectLink和@Observed,绑定该数据对象的父组件和其他子组件同步更新
var nextID: number = 0

@Observed
class ClassA {
  public name: string
  public c: number
  public id: number

  constructor(c: number, name: string = 'OK') {
    this.name = name
    this.c = c
    this.id = nextID++
  }
}

@Component
struct ViewA {
  label: string = 'ViewA1'
  @ObjectLink a: ClassA

  build() {
    Row() {
      Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
        .onClick(() = > {
          this.a.c += 1
        })
    }.margin({ top: 10 })
  }
}

@Entry
@Component
struct ViewB {
  @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)]

  build() {
    Column() {
      ForEach(this.arrA, (item) = > {
        ViewA({ label: `#${item.id}`, a: item })
      }, (item) = > item.id.toString())
      ViewA({ label: `this.arrA[first]`, a: this.arrA[0] })
      ViewA({ label: `this.arrA[last]`, a: this.arrA[this.arrA.length - 1] })

      Button(`ViewB: reset array`)
        .margin({ top: 10 })
        .onClick(() = > {
          this.arrA = [new ClassA(0), new ClassA(0)]
        })
      Button(`ViewB: push`)
        .margin({ top: 10 })
        .onClick(() = > {
          this.arrA.push(new ClassA(0))
        })
      Button(`ViewB: shift`)
        .margin({ top: 10 })
        .onClick(() = > {
          this.arrA.shift()
        })
    }.width('100%')
  }
}

@Provide和@Consume

@Provide作为数据的提供方,可以更新其子孙节点的数据,并触发页面渲染。@Consume在感知到@Provide数据的更新后,会触发当前自定义组件的重新渲染。

说明:
使用@Provide和@Consume时应避免循环引用导致死循环。

@Provide

名称说明
装饰器参数是一个string类型的常量,用于给装饰的变量起别名。如果规定别名,则提供对应别名的数据更新。如果没有,则使用变量名作为别名。推荐使用@Provide(‘alias’)这种形式。
同步机制@Provide的变量类似@State,可以修改对应变量进行页面重新渲染。也可以修改@Consume装饰的变量,反向修改@State变量。
初始值必须设置初始值。
页面重渲染场景基础类型(boolean,string,number)变量的改变;@Observed class类型变量及其属性的修改;添加,删除,更新数组中的元素。

@Consume

类型说明
初始值不可设置默认初始值。

示例

// xxx.ets
@Entry
@Component
struct CompA {
  @Provide("reviewVote") reviewVotes: number = 0;

  build() {
    Column() {
      CompB()
      Button(`CompA: ${this.reviewVotes}`)
        .margin(10)
        .onClick(() = > {
          this.reviewVotes += 1;
        })
    }
  }
}

@Component
struct CompB {
  build() {
    Column() {
      CompC()
    }
  }
}

@Component
struct CompC {
  @Consume("reviewVote") reviewVotes: number

  build() {
    Column() {
      Button(`CompC: ${this.reviewVotes}`)
        .margin(10)
        .onClick(() = > {
          this.reviewVotes += 1
        })
    }.width('100%')
  }
}

@Watch

@Watch用于监听状态变量的变化,语法结构为:

@State @Watch("onChanged") count : number = 0

如上所示,给状态变量增加一个@Watch装饰器,通过@Watch注册一个回调方法onChanged, 当状态变量count被改变时, 触发onChanged回调。

装饰器@State、@Prop、@Link、@ObjectLink、@Provide、@Consume、@StorageProp以及@StorageLink所装饰的变量均可以通过@Watch监听其变化。

说明:
深层次数据修改不会触发@Watch回调,例如无法监听数组中对象值的改变。

// xxx.ets
@Entry
@Component
struct CompA {
  @State @Watch('onBasketUpdated') shopBasket: Array< number > = [7, 12, 47, 3]
  @State totalPurchase: number = 0
  @State addPurchase: number = 0

  aboutToAppear() {
    this.updateTotal()
  }

  updateTotal(): void {
    let sum = 0;
    this.shopBasket.forEach((i) = > {
      sum += i
    })
    // 计算新的购物篮总价值,如果超过100,则适用折扣
    this.totalPurchase = (sum < 100) ? sum : 0.9 * sum
    return this.totalPurchase
  }

  // shopBasket更改时触发该方法
  onBasketUpdated(propName: string): void {
    this.updateTotal()
  }

  build() {
    Column() {
      Button('add to basket ' + this.addPurchase)
        .margin(15)
        .onClick(() = > {
          this.addPurchase = Math.round(100 * Math.random())
          this.shopBasket.push(this.addPurchase)
        })
      Text(`${this.totalPurchase}`)
        .fontSize(30)
    }
  }
}

审核编辑 黄宇

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 变量
    +关注

    关注

    0

    文章

    613

    浏览量

    28361
  • 鸿蒙
    +关注

    关注

    57

    文章

    2345

    浏览量

    42822
  • Harmony
    +关注

    关注

    0

    文章

    53

    浏览量

    2602
收藏 人收藏

    评论

    相关推荐

    OpenHarmony页面变量状态管理

    @State、@Prop、@Link、@Provide、Consume、@ObjectLink、@Observed和@Watch用于管理页面变量
    的头像 发表于 12-07 08:58 2672次阅读

    鸿蒙开发教程-管理组件状态

    跨组件层级双向同步状态是指@Provide修饰的状态变量自动对提供者组件的所有后代组件可用,后代组件通过使用@Consume装饰的变量来获得对提供的状态变量的访问。
    的头像 发表于 01-22 21:46 1321次阅读
    <b class='flag-5'>鸿蒙</b>开发教程-<b class='flag-5'>管理</b>组件<b class='flag-5'>状态</b>

    鸿蒙OS开发实例:【应用状态变量共享】

    平时在开发的过程中,我们会在应用中共享数据,在不同的页面间共享信息。虽然常用的共享信息,也可以通过不同页面中组件间信息共享的方式,但有时使用应用级别的状态管理会让开发工作变得简单。
    的头像 发表于 04-03 15:09 1324次阅读
    <b class='flag-5'>鸿蒙</b>OS开发实例:【应用<b class='flag-5'>状态变量</b>共享】

    harmonyos开发者平台

    使用@State、@Prop、@Link、@Watch、@Provide、@Consume管理页面变量状态,实现对
    的头像 发表于 05-06 16:02 841次阅读
    harmonyos开发者平台

    HarmonyOS实践之应用状态变量共享

    平时在开发的过程中,我们会在应用中共享数据,在不同的页面间共享信息。虽然常用的共享信息,也可以通过不同页面中组件间信息共享的方式,但有时使用应用级别的状态管理会让开发工作变得简单。 根
    发表于 12-27 09:48

    Harmony 鸿蒙页面变量状态管理

    页面变量状态管理 @State、@Prop、@Link、@Provide、@Consume、@ObjectLink、@Observed和
    发表于 01-24 20:04

    Harmony如何管理网页

    你好,我可能错过了一些东西,但我找不到关于Harmony如何管理网页(使用文件系统)以生成HTTP服务器中使用的c代码的答案。我使用BSP和相关的评估板(PIC32MZ EC)启动了一个项目。当生成
    发表于 07-30 12:25

    如何设置鸿蒙harmony控件的高度是屏幕的一半?

    鸿蒙harmony想让控件的高度是屏幕的一半,该如何设置?在XML和java代码中,分别该如何设置?
    发表于 03-23 11:09

    OpenHarmony应用ArkUI 状态管理开发范例

    称为状态管理机制。 自定义组件拥有变量变量必须被装饰器装饰才可以成为状态变量状态变量的改变会
    发表于 09-01 15:03

    OpenHarmony页面UI状态存储:LocalStorage

    LocalStorage是页面的UI状态存储,通过@Entry装饰器接收的参数可以在页面内共享同一个LocalStorage实例。LocalStorage也可以在UIAbility内
    发表于 10-17 17:01

    鸿蒙 OS 应用开发初体验

    kotlin 语言了,编程语言变成了类 JavaScript 的前端语言,这意味着我们需要适应用前端的思想去开发鸿蒙应用,比如状态管理。 总结 本文纯初体验遥遥领先背后的鸿蒙操作系统
    发表于 11-02 19:38

    状态变量滤波器,状态变量滤波器原理是什么?

    状态变量滤波器,状态变量滤波器原理是什么? 状态变量滤波器,又称多态变量滤波器,它可以分别从不同的点同时输出高通、带通、低通等,且
    发表于 03-24 14:24 6630次阅读

    华为p40升鸿蒙系统步骤教程

    华为鸿蒙(英语:Harmony OS,开发代号:Ark)是基于微内核的全场景分布式OS。
    的头像 发表于 07-06 11:24 5976次阅读

    Harmony 鸿蒙应用变量状态管理

    应用变量状态管理 在前面的章节中,已经讲述了如何管理页面
    的头像 发表于 01-24 21:30 493次阅读
    <b class='flag-5'>Harmony</b> <b class='flag-5'>鸿蒙</b>应用<b class='flag-5'>级</b><b class='flag-5'>变量</b>的<b class='flag-5'>状态</b><b class='flag-5'>管理</b>

    鸿蒙开发:【页面栈及任务链】

    单个UIAbility组件可以实现多个页面,并在多个页面之间跳转,这种UIAbility组件内部的页面跳转关系称为“页面栈”,由ArkUI框架统一
    的头像 发表于 06-14 10:10 409次阅读
    <b class='flag-5'>鸿蒙</b>开发:【<b class='flag-5'>页面</b>栈及任务链】