当前位置: 首页 > article >正文

《深入理解组件间数据同步:@Provide/@Consume与@Observed/@ObjectLink的特性及限制》

文章目录

  • @Provide/@Consume状态变量
    • 引言
    • 概述
    • 观察变化
    • 限制条件
  • @Observed/@ObjecktLink:嵌套类对象属性变化
    • 引言
    • 概述
    • 观察变化
    • 限制条件

@Provide/@Consume状态变量

引言

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

其中@Provide装饰的变量是在祖先组件中,可以理解为被“提供”给后代的状态变量。@Consume装饰的变量是在后代组件中,去“消费(绑定)”祖先组件提供的变量。

概述

  • @Provide装饰的状态变量自动对其所有后代组件可用,即该变量被“provide”给他的后代组件。由此可见,@Provide的方便之处在于,开发者不需要多次在组件之间传递变量。
  • 后代通过使用@Consume去获取@Provide提供的变量,建立在@Provide和@Consume之间的双向数据同步,与@State/@Link不同的是,前者可以在多层级的父子组件之间传递。
  • @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装饰的变量,会发生运行时报错。

@Provide初始化规则图示

0000000000011111111.20241206162509.66597602768490418137257572334625:50001231000000:2800:FBB42EB64D92EA0D993E69CB4974EF245F7EB07A5426D54A9CF20A517BF1264C.png

** @Consume初始化规则图示**

img

观察变化

  • 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。
  • 当装饰的数据类型为class或者Object的时候,可以观察到赋值和属性赋值的变化(属性为Object.keys(observedObject)返回的所有属性)。
  • 当装饰的对象是array的时候,可以观察到数组的添加、删除、更新数组单元。
  • 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds 更新Date的属性。
@Component
struct CompD {
  @Consume selectedDate: Date;

  build() {
    Column() {
      Button(`child increase the day by 1`)
        .onClick(() => {
          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
        })
      Button('child update the new date')
        .margin(10)
        .onClick(() => {
          this.selectedDate = new Date('2023-09-09')
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
    }
  }
}

@Entry
@Component
struct CompA {
  @Provide selectedDate: Date = new Date('2021-08-08')

  build() {
    Column() {
      Button('parent increase the day by 1')
        .margin(10)
        .onClick(() => {
          this.selectedDate.setDate(this.selectedDate.getDate() + 1)
        })
      Button('parent update the new date')
        .margin(10)
        .onClick(() => {
          this.selectedDate = new Date('2023-07-07')
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
      CompD()
    }
  }
}
  • 当装饰的变量是Map时,可以观察到Map整体的赋值,同时可通过调用Map的接口set, clear, delete 更新Map的值。
  • 当装饰的变量是Set时,可以观察到Set整体的赋值,同时可通过调用Set的接口add, clear, delete 更新Set的值。

限制条件

  1. @Provider/@Consumer的参数key必须为string类型,否则编译期会报错。
// 错误写法,编译报错
let change: number = 10;
@Provide(change) message: string = 'Hello';

// 正确写法
let change: string = 'change';
@Provide(change) message: string = 'Hello';
  1. @Consume装饰的变量不能本地初始化,也不能在构造参数中传入初始化,否则编译期会报错。@Consume仅能通过key来匹配对应的@Provide变量进行初始化。

【反例】

@Component
struct Child {
  @Consume msg: string;
  // 错误写法,不允许本地初始化
  @Consume msg1: string = 'Hello';

  build() {
    Text(this.msg)
  }
}

@Entry
@Component
struct Example {
  @Provide message: string = 'Hello';

  build() {
    Column() {
      // 错误写法,不允许外部传入初始化
      Child({msg: 'Hello'})
    }
  }
}

【正例】

@Component
struct Child {
  @Consume num: number;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
    }
  }
}

@Entry
@Component
struct Parent {
  @Provide num: number = 10;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
      Child()
    }
  }
}
  1. @Provide的key重复定义时,框架会抛出运行时错误,提醒开发者重复定义key,如果开发者需要重复key,可以使用allowoverride。
// 错误写法,a重复定义
@Provide('a') count: number = 10;
@Provide('a') num: number = 10;

// 正确写法
@Provide('a') count: number = 10;
@Provide('b') num: number = 10;
  1. 在初始化@Consume变量时,如果开发者没有定义对应key的@Provide变量,框架会抛出运行时错误,提示开发者初始化@Consume变量失败,原因是无法找到其对应key的@Provide变量。

【反例】

@Component
struct Child {
  @Consume num: number;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
    }
  }
}

@Entry
@Component
struct Parent {
  // 错误写法,缺少@Provide
  num: number = 10;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
      Child()
    }
  }
}

【正例】

@Component
struct Child {
  @Consume num: number;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
    }
  }
}

@Entry
@Component
struct Parent {
  // 正确写法
  @Provide num: number = 10;

  build() {
    Column() {
      Text(`num的值: ${this.num}`)
      Child()
    }
  }
}
  1. @Provide与@Consume不支持装饰Function类型的变量,框架会抛出运行时错误。

@Observed/@ObjecktLink:嵌套类对象属性变化

引言

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

概述

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

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

说明

@ObjectLink装饰的变量不能被赋值,如果要使用赋值操作,请使用@Prop。

  • @Prop装饰的变量和数据源的关系是是单向同步,@Prop装饰的变量在本地拷贝了数据源,所以它允许本地更改,如果父组件中的数据源有更新,@Prop装饰的变量本地的修改将被覆盖;
  • @ObjectLink装饰的变量和数据源的关系是双向同步,@ObjectLink装饰的变量相当于指向数据源的指针。禁止对@ObjectLink装饰的变量赋值,如果一旦发生@ObjectLink装饰的变量的赋值,则同步链将被打断。因为@ObjectLink装饰的变量通过数据源(Object)引用来初始化。对于实现双向数据同步的@ObjectLink,赋值相当于更新父组件中的数组项或者class的属性,TypeScript/JavaScript不能实现,会发生运行时报错。

初始化规则图示

观察变化

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

class Child {
  public num: number;

  constructor(num: number) {
    this.num = num;
  }
}

@Observed
class Parent {
  public child: Child;
  public count: number;

  constructor(child: Child, count: number) {
    this.child = child;
    this.count = count;
  }
}

以上示例中,Parent被@Observed装饰,其成员变量的赋值的变化是可以被观察到的,但对于Child,没有被@Observed装饰,其属性的修改不能被观察到。

@ObjectLink parent: Parent

// 赋值变化可以被观察到
this.parent.child = new Child(5)
this.parent.count = 5

// Child没有被@Observed装饰,其属性的变化观察不到
this.parent.child.num = 5

@ObjectLink:@ObjectLink只能接收被@Observed装饰class的实例,推荐设计单独的自定义组件来渲染每一个数组或对象。此时,对象数组或嵌套对象(属性是对象的对象称为嵌套对象)需要两个自定义组件,一个自定义组件呈现外部数组/对象,另一个自定义组件呈现嵌套在数组/对象内的类对象。可以观察到:

  • 其属性的数值的变化,其中属性是指Object.keys(observedObject)返回的所有属性。
  • 如果数据源是数组,则可以观察到数组item的替换,如果数据源是class,可观察到class的属性的变化,。

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

@Observed
class DateClass extends Date {
  constructor(args: number | string) {
    super(args)
  }
}

@Observed
class NewDate {
  public data: DateClass;

  constructor(data: DateClass) {
    this.data = data;
  }
}

@Component
struct Child {
  label: string = 'date';
  @ObjectLink data: DateClass;

  build() {
    Column() {
      Button(`child increase the day by 1`)
        .onClick(() => {
          this.data.setDate(this.data.getDate() + 1);
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.data
      })
    }
  }
}

@Entry
@Component
struct Parent {
  @State newData: NewDate = new NewDate(new DateClass('2023-1-1'));

  build() {
    Column() {
      Child({ label: 'date', data: this.newData.data })

      Button(`parent update the new date`)
        .onClick(() => {
          this.newData.data = new DateClass('2023-07-07');
        })
      Button(`ViewB: this.newData = new NewDate(new DateClass('2023-08-20'))`)
        .onClick(() => {
          this.newData = new NewDate(new DateClass('2023-08-20'));
        })
    }
  }
}

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

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

限制条件

  1. 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
  2. @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
  3. @ObjectLink装饰的变量类型需要为显式的被@Observed装饰的类,如果未指定类型,或其不是@Observed装饰的class,编译期会报错。
@Observed
class Info {
  count: number;

  constructor(count: number) {
    this.count = count;
  }
}

class Test {
  msg: number;

  constructor(msg: number) {
    this.msg = msg;
  }
}

// 错误写法,编译报错
@ObjectLink count;
@ObjectLink test: Test;

// 正确写法
@ObjectLink count: Info;
  1. @ObjectLink装饰的变量不能本地初始化,仅能通过构造参数从父组件传入初始值,否则编译期会报错。
@Observed
class Info {
  count: number;

  constructor(count: number) {
    this.count = count;
  }
}

// 错误写法,编译报错
@ObjectLink count: Info = new Info(10);

// 正确写法
@ObjectLink count: Info;
  1. @ObjectLink装饰的变量是只读的,不能被赋值,否则会有运行时报错提示Cannot set property when setter is undefined。如果需要对@ObjectLink装饰的变量进行整体替换,可以在父组件对其进行整体替换。

【反例】

@Observed
class Info {
  count: number;

  constructor(count: number) {
    this.count = count;
  }
}

@Component
struct Child {
  @ObjectLink num: Info;

  build() {
    Column() {
      Text(`num的值: ${this.num.count}`)
        .onClick(() => {
          // 错误写法,\@ObjectLink装饰的变量不能被赋值
          this.num = new Info(10);
        })
    }
  }
}

@Entry
@Component
struct Parent {
  @State num: Info = new Info(10);

  build() {
    Column() {
      Text(`count的值: ${this.num}`)
      Child({num: this.num})
    }
  }
}

【正例】

@Observed
class Info {
  count: number;

  constructor(count: number) {
    this.count = count;
  }
}

@Component
struct Child {
  @ObjectLink num: Info;

  build() {
    Column() {
      Text(`num的值: ${this.num.count}`)
        .onClick(() => {
          // 正确写法,可以更改@ObjectLink装饰变量的成员属性
          this.num.count = 20;
        })
    }
  }
}

@Entry
@Component
struct Parent {
  @State num: Info = new Info(10);

  build() {
    Column() {
      Text(`count的值: ${this.num}`)
      Button('click')
        .onClick(() => {
          // 可以在父组件做整体替换
          this.num = new Info(30);
        })
      Child({num: this.num})
    }
  }
}
fo;

  build() {
    Column() {
      Text(`num的值: ${this.num.count}`)
        .onClick(() => {
          // 正确写法,可以更改@ObjectLink装饰变量的成员属性
          this.num.count = 20;
        })
    }
  }
}

@Entry
@Component
struct Parent {
  @State num: Info = new Info(10);

  build() {
    Column() {
      Text(`count的值: ${this.num}`)
      Button('click')
        .onClick(() => {
          // 可以在父组件做整体替换
          this.num = new Info(30);
        })
      Child({num: this.num})
    }
  }
}

http://www.kler.cn/a/428244.html

相关文章:

  • 1.27补题 回训练营
  • USB 3.1-GL3510-52芯片原理图设计
  • 再见了流氓软件~~
  • docker安装emqx
  • 3、C#基于.net framework的应用开发实战编程 - 实现(三、三) - 编程手把手系列文章...
  • 如何看待 OpenAI 的12天“shipmas”发布计划?
  • Github 2024-12-08 php开源项目日报 Top10
  • 常见函数的Taylor级数展开的可视化过程
  • React初体验 - [Next.js项目]
  • Hive 中 IP 字典的应用:让你的数据分析更加精准
  • 反爬虫机制的全面解析
  • 在做题中学习(79):最小K个数
  • 【Java】使用Socket手搓三次握手 从原理到实践
  • 代码随想录-算法训练营day36(贪心算法06:单调递增的数字,监控二叉树,总结)
  • 六安市第二届网络安全大赛复现
  • 【系统架构设计师】真题论文: 论负载均衡技术在 Web 系统中的应用(包括解题思路和素材)
  • 024、Docker与SSH在分布式系统中的实践指南
  • base64转file文件对象
  • c++ QT中cmake项目,直接在cmakelist中添加翻译设置
  • OpenHarmony系统中实现Android虚拟化、模拟器相关的功能,包括桌面显示,详细解决方案
  • React第十三节开发中常见问题之(视图更新、事件处理)
  • c++总复习
  • 青牛科技---摄氏温度传感器D35使用手册
  • Linux Ubuntu
  • 聊聊用Rust来写CDD程序
  • mysql8 主从复制一直失败