ArkUI-状态管理-@Provide、@Consume、@Observed、@ObjectLink

ArkUI-状态管理

@Provide装饰器和@Consume装饰器:与后代组件双向同步

@Provide和@Consume,应用于与后台组件的双向数据同步,应用于状态数据在多个层级之间传递的场景。不同于之前提到的父子组件之间通过命名参数传递的方式,@Provide和@Consume摆脱参数传递机制的束缚,实现跨层级传递。

其中@Provide装饰的变量是在祖先组件中,@Consume装饰的变量是在后代组件中。

概述

@Provide和@Consume装饰的变量有以下特性:

  • @Provide装饰的状态变量自动对其所有的后代组件可用。

  • 后代通过使用@Consume去获取@Provide提供的变量。

  • @Provide和@Consume可以通过相同的变量名或者相同的变量别名绑定,建议类型相同,否则会发生类型隐式转换,可能导致应用数据异常。

    // 通过相同的变量名绑定
    @Provide a: number = 0;
    @Consume a: number;

    // 通过相同的变量别名绑定
    @Provide('a') b: number = 0;
    @Consume('a') c: number;

@Provide和@Consume通过相同的变量名或者相同的变量别名绑定时,@Provide装饰的变量和@Consume装饰的变量是一对多的关系。不允许在同一个自定义组件内、包括其子组件内生命多个同名或者同别名的@Provide装饰的变量,@Provide的属性名或者别名需要唯一且确定,如果生命多个同名或者同别名的@Provide装饰的变量,会发生运行时错误。

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数据的变化。
  • 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化。
  • 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。

框架行为

  1. 初始渲染:
    • @Provide装饰的变量会以map的形式,传递给当前@Provide所属组件的所有子组件。
    • 子组件如果使用@Consume变量,则会在map中查找是否有该变量名/别名对应的@Provide的变量,如果查找不到,框架会抛出JS ERROR
    • 在初始化@Consume变量时,和@State/@Link的流程类似,@Consume变量会在map中查找对应的@Provide变量进行保存,并把自己注册给@Provide。
  2. 当@Provide装饰的数据发生变化时:
    • 通过初始渲染的步骤可知,子组件@Consume已把自己注册给父组件。父组件@Provide变量变更后,会遍历所有依赖它的系统㢟和状态变量。
    • 通知@Consume更新后,子组件所有依赖@Consume的系统组件都会被通知更新。
  3. 当@Consume装饰的数据发生变化时:
    通过初始渲染的步骤可知,子组件@Consume持有@Provide的实例。在@Consume更新后调用@Provide的更新方法,将更新值同步回@Provide,以此实现@Consume向@Provide的同步更新。

Provide支持allowOverride参数

上边我们提到,不允许在同一个自定义组件内、包括其子组件内生命多个同名或者同别名的@Provide装饰的变量,@Provide的属性名或者别名需要唯一且确定,如果生命多个同名或者同别名的@Provide装饰的变量,会发生运行时错误。

如果需要对@Provide装饰的变量进行重写,可以子组件中重写的位置使用allowOverride参数。

@Component
struct GrandSon {
  // @Consume装饰的变量通过相同的属性名绑定其祖先内的@Provide装饰的变量
  @Consume("reviewVotes") reviewVotes: number;

  build() {
    Column() {
      Text(`reviewVotes(${this.reviewVotes})`) // Text显示10
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
    }
    .width('50%')
  }
}

@Component
struct Child {
  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 10;

  build() {
    Row({ space: 5 }) {
      GrandSon()
    }
  }
}

@Component
struct Parent {
  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20;

  build() {
    Child()
  }
}

@Entry
@Component
struct GrandParent {
  @Provide("reviewVotes") reviewVotes: number = 40;

  build() {
    Column() {
      Button(`reviewVotes(${this.reviewVotes}), give +1`)
        .onClick(() => this.reviewVotes += 1)
      Parent()
    }
  }
}

在上面的示例中:

  • GrandParent声明了@Provide("reviewVotes") reviewVotes: number = 40
  • Parent是GrandParent的子组件,声明@Provide为allowOverride,重写父组件GrandParent中的reviewVotes变量为20。如果不设置allowOverride,则会抛出运行时报错,提示@Provide重复定义。Child同理。
  • GrandSon在初始化@Consume的时候,@Consume装饰的变量通过相同的属性名绑定其最近的祖先的@Provide装饰的变量
  • GrandSon查找到相同属性名的@Provide在祖先Child中,所以@Consume("reviewVotes") reviewVotes: number初始化数值为10。如果Child中没有定义与@Consume同名的@Provide,则继续向上寻找Parent中的同名@Provide值为20,以此类推。
  • 如果查找到根节点还没有找到key对应的@Provide,则会报初始化@Consume找不到@Provide的报错。

@Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化

前边我们讲到的装饰器,对于class的观测,都无法观测到嵌套属性但是在实际应用开发中,应用会根据开发需要,封装自己的数据模型。对于多层嵌套的情况,比如二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化是无法观察到的。这就引出了@Observed/@ObjectLink装饰器。

概述

@ObjectLink和@Observed类装饰器用于涉及嵌套对象或数组的场景中进行双向数据同步:

  • 被@Observed装饰的类,可以被观察到属性的变化。
  • 子组件中@ObjectLink装饰器装饰的状态变量,用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
  • @Observed用于嵌套类场景中,观察对象类属性变化,要配合自定义组件使用(示例详见嵌套对象),如果要做数据双/单向同步,需要搭配@ObjectLink或者@Prop使用。

限制条件

  • 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
  • @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
  • @ObjectLink装饰的数据为可读类型,不允许@ObjectLink装饰的数据自身赋值。
  • 如果需要使用赋值操作,使用@Prop。

观察变化

  • @Observed装饰的类,如果其属性为非简单类型,比如class、Object或者数组,也需要被@Observed装饰,否则将观察不到其属性的变化。

  • @ObjectLink只能接收被@Observed装饰的class的实例。

  • 继承Date的class时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。

  • 继承Map的class时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。

  • 继承Set的class时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。

框架行为

  1. 初始渲染
    • @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
    • 子组件中@ObjectLink装饰的从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
  2. 属性更新:
    • 当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知其数据更新。
相关推荐
Algorithm15762 分钟前
云原生相关的 Go 语言工程师技术路线(含博客网址导航)
开发语言·云原生·golang
m0_748255263 分钟前
easyExcel导出大数据量EXCEL文件,前端实现进度条或者遮罩层
前端·excel
shinelord明11 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
Monly2118 分钟前
Java(若依):修改Tomcat的版本
java·开发语言·tomcat
boligongzhu19 分钟前
DALSA工业相机SDK二次开发(图像采集及保存)C#版
开发语言·c#·dalsa
Eric.Lee202119 分钟前
moviepy将图片序列制作成视频并加载字幕 - python 实现
开发语言·python·音视频·moviepy·字幕视频合成·图像制作为视频
7yewh21 分钟前
嵌入式Linux QT+OpenCV基于人脸识别的考勤系统 项目
linux·开发语言·arm开发·驱动开发·qt·opencv·嵌入式linux
waicsdn_haha33 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
_WndProc35 分钟前
C++ 日志输出
开发语言·c++·算法
web1478621072336 分钟前
C# .Net Web 路由相关配置
前端·c#·.net