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包装类,通知其数据更新。
相关推荐
excel5 分钟前
前端必备:从能力检测到 UA-CH,浏览器客户端检测的完整指南
前端
前端小巷子12 分钟前
Vue 3全面提速剖析
前端·vue.js·面试
悟空聊架构18 分钟前
我的网站被攻击了,被干掉了 120G 流量,还在持续攻击中...
java·前端·架构
CodeSheep20 分钟前
国内 IT 公司时薪排行榜。
前端·后端·程序员
尖椒土豆sss23 分钟前
踩坑vue项目中使用 iframe 嵌套子系统无法登录,不报错问题!
前端·vue.js
遗悲风24 分钟前
html二次作业
前端·html
江城开朗的豌豆28 分钟前
React输入框优化:如何精准获取用户输入完成后的最终值?
前端·javascript·全栈
CF14年老兵28 分钟前
从卡顿到飞驰:我是如何用WebAssembly引爆React性能的
前端·react.js·trae
嘤嘤嘤*30 分钟前
鸿蒙自带组件效果大全
华为·harmonyos
画月的亮31 分钟前
前端处理导出PDF。Vue导出pdf
前端·vue.js·pdf