状态管理之复杂对象

        前面我们学习的@State、@Prop、@Link、@Provide、@Consume这些装饰器都只能更新对象的直接赋值、对象属性赋值;如果对象的属性又是一个对象,也就是嵌套对象,那么对嵌套对象的属性的更新是不会被观察到的,所以复杂状态管理用于解决该问题。

        对于复杂对象可以用@ObjectLink、@Observed解决嵌套对象的更新,二维数组,或者数组项class,或者class的属性是class,他们的第二层的属性变化可以用该方式解决。

1、概述

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

  • 被@Observed装饰的类,可以被观察到属性的变化;
  • 子组件中@ObjectLink装饰器装饰的状态变量用于接收@Observed装饰的类的实例,和父组件中对应的状态变量建立双向数据绑定。这个实例可以是数组中的被@Observed装饰的项,或者是class object中的属性,这个属性同样也需要被@Observed装饰。
  • 单独使用@Observed是没有任何作用的,需要搭配@ObjectLink或者@Prop使用。
  • 使用@Observed装饰class会改变class原始的原型链,@Observed和其他类装饰器装饰同一个class可能会带来问题。
  • @ObjectLink装饰器不能在@Entry装饰的自定义组件中使用。
  • @ObjectLink的属性是可以改变的,但是变量的分配是不允许的,也就是说这个装饰器装饰变量是只读的,不能被改变。同时被装饰的变量不允许有初始值,初值必需从父组件初始化。

2、观察变化和行为表现 

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

class ClassA {public c: number;constructor(c: number) {this.c = c;}
}@Observed
class ClassB {public a: ClassA;public b: number;constructor(a: ClassA, b: number) {this.a = a;this.b = b;}
}

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

@ObjectLink b: ClassB// 赋值变化可以被观察到
this.b.a = new ClassA(5)
this.b.b = 5// ClassA没有被@Observed装饰,其属性的变化观察不到
this.b.a.c = 5

        @ObjectLink:@ObjectLink只能接收被@Observed装饰class的实例,可以观察到:

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

3、框架行为

  1. 初始渲染:
    1. @Observed装饰的class的实例会被不透明的代理对象包装,代理了class上的属性的setter和getter方法
    2. 子组件中@ObjectLink装饰的从父组件初始化,接收被@Observed装饰的class的实例,@ObjectLink的包装类会将自己注册给@Observed class。
  2. 属性更新:当@Observed装饰的class属性改变时,会走到代理的setter和getter,然后遍历依赖它的@ObjectLink包装类,通知数据更新。

4、嵌套对象


let NextID: number = 1;/*** @Observed 使ClassA属性的变化能被观察到*/
@Observed
class ClassA {public id: number;public c: number;constructor(c: number) {this.id = NextID++;this.c = c;}
}@Observed
class ClassB {public a: ClassA;constructor(a: ClassA) {this.a = a;}
}@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;})}}
}/*** 嵌套对象变化监听*/
@Entry
@Component
struct NestObjectDemoPage {@State b: ClassB = new ClassB(new ClassA(0));build() {Column({ space: 10 }) {ViewA({ label: 'ViewA #1', a: this.b.a })ViewA({ label: 'ViewA #2', a: this.b.a })Button(`ViewB: this.b.a.c+= 1`).onClick(() => {//嵌套属性变化this.b.a.c += 1;})Button(`ViewB: this.b.a = new ClassA(0)`).onClick(() => {//属性对象赋值this.b.a = new ClassA(0);})Button(`ViewB: this.b = new ClassB(ClassA(0))`).onClick(() => {//对象赋值this.b = new ClassB(new ClassA(0));})}.margin({top: 48})}
}

        

        该示例演示了对象赋值、对象属性赋值、嵌套对象属性赋值的行为观察,这些行为都能及时的观察到并触发UI更新。 

5、对象数组

// objectLinkNestedObjects.ets
let id: number = 1;/*** @Observed 使ClassA2属性的变化能被观察到*/
@Observed
class ClassA2 {public id: number;public c: number;constructor(c: number) {this.id = id++;this.c = c;}
}@Component
struct ViewA2 {// 子组件ViewA的@ObjectLink的类型是ClassA@ObjectLink a: ClassA2;label: string = 'ViewA1';build() {Row() {Button(`ViewA [${this.label}] this.a.c = ${this.a.c} +1`).onClick(() => {this.a.c += 1;})}}
}/*** 对象数组变化监听*/
@Entry
@Component
struct ObjectArrayDemoPage {// ViewB中有@State装饰的ClassA[]@State arrA: ClassA2[] = [new ClassA2(0), new ClassA2(0)];build() {Column({ space: 10 }) {ForEach(this.arrA,(item: ClassA2) => {ViewA2({ label: `#${item.id}`, a: item })},(item) => item.id.toString())// 使用@State装饰的数组的数组项初始化@ObjectLink,其中数组项是被@Observed装饰的ClassA的实例ViewA2({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })ViewA2({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })Button(`ViewB: reset array`).onClick(() => {//数组赋值this.arrA = [new ClassA2(0), new ClassA2(0)];})Button(`ViewB: push`).onClick(() => {//数组的变化[在数组末尾加一个元素]this.arrA.push(new ClassA2(0))})Button(`ViewB: shift`).onClick(() => {//数组的变化[在数组头部删除一个元素]this.arrA.shift()})Button(`ViewB: chg item property in middle`).onClick(() => {//数组中对象属性的赋值this.arrA[Math.floor(this.arrA.length / 2)].c = 10;})Button(`ViewB: chg item object in middle`).onClick(() => {//对象数组的赋值this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA2(11);})}.margin({top: 48})}
}

     案例分析:

  • this.arrA[Math.floor(this.arrA.length/2)] = new ClassA(..) :该状态变量的改变触发2次更新:
    1. ForEach:数组项的赋值导致ForEach的itemGenerator被修改,因此数组项被识别为有更改,ForEach的item builder将执行,创建新的ViewA组件实例。
    2. ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] }):上述更改改变了数组中第一个元素,所以绑定this.arrA[0]的ViewA将被更新;
  • this.arrA.push(new ClassA(0)) : 将触发2次不同效果的更新:
    1. ForEach:新添加的ClassA对象对于ForEach是未知的itemGenerator,ForEach的item builder将执行,创建新的ViewA组件实例。
    2. ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] }):数组的最后一项有更改,因此引起第二个ViewA的实例的更改。对于ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] }),数组的更改并没有触发一个数组项更改的改变,所以第一个ViewA不会刷新。
  • this.arrA[Math.floor(this.arrA.length/2)].c:@State无法观察到第二层的变化,但是ClassA被@Observed装饰,ClassA的属性的变化将被@ObjectLink观

6、二维数组

@Observed
class StringArray extends Array<String> {
}@Component
struct ItemPage {@ObjectLinkitemArr: StringArray;private scroller: Scroller = new Scroller()build() {Scroll(this.scroller) {Row() {Text('ItemPage').width(100).height(100)ForEach(this.itemArr,item => {Text(item).width(100).height(100)},item => item)}}.scrollable(ScrollDirection.Horizontal)}
}/*** 二维数组变化监听,[bug]发现有性能问题*/
@Entry
@Component
struct NestedObjectDemo2Page {@State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()];build() {Column() {ItemPage({ itemArr: this.arr[0] })ItemPage({ itemArr: this.arr[1] })ItemPage({ itemArr: this.arr[2] })Divider()ForEach(this.arr,(itemArr: StringArray, index: number) => {ItemPage({ itemArr: itemArr })},itemArr => itemArr[0])Divider()Button('update').onClick(() => {console.error('Update all items in arr');if (this.arr[0][0] !== undefined) {// 正常情况下需要有一个真实的ID来与ForEach一起使用,但此处没有// 因此需要确保推送的字符串是唯一的。this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`);this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`);this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`);} else {this.arr[0].push('Hello');this.arr[1].push('World');this.arr[2].push('!');}})}.margin({top: 48})}
}

        使用new StringArray()来构造StringArray的实例,new运算符使得@Observed生效,@Observed观察到StringArray的属性变化。

        声明一个从Array扩展的类class StringArray extends Array<String> {},并创建StringArray的实例。@Observed装饰的类需要使用new运算符来构建class实例。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/233860.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

前后端分离跨域问题的OPTIONS请求

本篇文章用于个人的问题记录 问题描述: 使用了springbootvue3做前后端分离,使用sa-token做登录认证 由于sa-token的前后端分离的登录认证需要在请求发起时自定义添加头部satoken 好那么问题来了,我请求的时候看我的请求头是存在satoken这个头部信息的 但我在springboot的拦截…

【sprintboot+vue3】解决前后端分离项目遇到的问题

目录 一、Access to XMLHttpRequest at http://127.0.0.1:8088/api/hello from origin http://localhost:5173 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource. 二、报错[vue/compiler-sfc] 一、Access to …

uniapp笔记

/pages/component/swiper/swiper /pages/component/button/button navigator image 设置界面标题 页面跳转 设置TabBar 发起一个请求 网络请求

明理信息科技打造专属个人或企业知识付费平台,核心功能设计

在当今信息爆炸的时代&#xff0c;知识管理已经成为了每个人必须面对的问题。然而&#xff0c;市面上的知识付费平台大多数都是通用的&#xff0c;无法满足个性化需求。 因此&#xff0c;明理信息科技提供了一款专属定制的适合个人的知识付费平台。核心产品能力如下&#xff1…

画图之C4架构图idea和vscode环境搭建篇

VS Code 下C4-PlantUML安装 安装VS Code 直接官网下载安装即可,过程略去。 安装PlantUML插件 在VS Code的Extensions窗口中搜索PlantUML,安装PlantUML插件。 配置VS Code代码片段 安装完PlantUML之后,为了提高效率,我们最好安装PlantUML相关的代码片段。 打开VS Cod…

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69)

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69) 大家好&#xff0c;小辰今天给大家介绍一个基于协同过滤算法的旅游推荐系统

WPF Halcon机器视觉和运动控制软件通用框架,插件式开发,开箱即用 仅供学习!

点我下载&#xff0c;仅供个人学习使用 参考easyvision开发&#xff0c;集成几十个软件算子此版本以添加ui设计器。具体功能如上所示&#xff0c;可以自定义变量&#xff0c;写c#脚本&#xff0c;自定义流程&#xff0c;包含了halcon脚本和封装的算子&#xff0c;可自定义ui&a…

MySQL的增删改查(进阶)--上

1. 数据库约束 1.1 约束类型 NOT NULL - 指示某列不能存储 NULL 值。 UNIQUE - 保证某列的每行必须有唯一的值。 DEFAULT - 规定没有给列赋值时的默认值。 PRIMARY KEY - NOT NULL 和 UNIQUE 的结合。确保某列&#xff08;或两个列多个列的结合&#xff09;有唯一标识&#xf…

需要token的原因----

需要token的原因主要有以下几点&#xff1a; 安全性&#xff1a;Token的使用可以增强用户信息的安全性。在用户向服务端请求数据时&#xff0c;服务端需要通过数据库来判断用户名和密码是否正确&#xff0c;以确定是否提供所需内容。Token作为一层额外的验证&#xff0c;可以验…

2023中国品牌节金谱奖荣誉发布 酷开科技获颁OTT行业科技创新奖

11月17日—19日&#xff0c;以“复苏与腾飞”为主题的2023第十七届中国品牌节&#xff0c;在杭州市云栖小镇国际会展中心成功举行。在18日晚间的荣耀盛典上&#xff0c;“TopBrand 2023中国品牌节金谱奖”荣誉发布&#xff0c;酷开科技斩获OTT行业科技创新奖。 酷开科技作为OTT…

2023/12/19 work

1. 基于UDP的TFTP文件传输 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <myhead.h>//实现下载功能 int do_download(int cfd, struct sockaddr_in sin) {//定义变量存储下载请求包char buf[516] "";//定义变量存…

工作服穿戴监测识别摄像机

工作服穿戴监测识别摄像机是一种通过图像识别技术&#xff0c;实时监测和识别工作场所穿戴的工作服是否符合要求的设备。它可以有效地监测员工是否穿戴了正确的工作服&#xff0c;提高工作场所的安全性和管理效率。 这种摄像机利用先进的计算机视觉技术&#xff0c;能够快速准确…

九牧:科技卫浴,长期主义

“没有做错什么&#xff0c;但却输给了时代”&#xff0c;这是人们给当年手机巨头诺基亚的注解。 谁也没有想到&#xff0c;曾在手机行业称雄的诺基亚&#xff0c;最终败给了时代。当年&#xff0c;在2G向3G、4G跨越的时候&#xff0c;苹果、微软的iOS和安卓系统将手机从简单的…

【QT】C++/Qt使用Qt自带工具windeployqt打包

基本操作 运行项目debug或者release 将运行后的可执行文件单独放到一个文件夹中 根据项目使用的kits来选择Qt的打包工具 打开工具后移动到exe文件夹下执行windeployqt xxx.exe 预览图 问题 打包后再其他电脑上运行出现下图错误 将自己电脑的这个文件拷到可执行文件夹中既…

redis 7.2.3 官方配置文件 redis.conf sentinel.conf

文章目录 Intro解压配置使用等官方配置文件模板redis.conf 仅配置项redis.conf 完整版(配置项注释)sentinel.conf 仅配置项sentinel.conf 完整版(配置项注释) Intro 在下载页面&#xff1a;https://redis.io/download/ 下载最新版本的redis&#xff1a; https://github.com/re…

linux搭建gitlab

gitlab的介绍 区别于github&#xff0c;github是面向互联网基于git实现的代码托管平台&#xff0c;gitlab是基于Ruby语言实现的git管理平台软件&#xff0c;一般用于公司内部代码仓库。 gitlab组成 Nginx 静态Web服务器Gitlab-workhorse 轻量级的反向代理服务器Gitlab-shell 用…

PHP 读取excel输入为HTML

目录 介绍 安装扩展 读取excel文件 输入为html 保存到文件 总结 介绍 以前都是使用phpexcel&#xff0c;不过已经不再更新了&#xff0c; 不过不用担心还可以使用phpspreadsheet来替代它进行操作。 PHPSpreadsheet-在PHP中读取、创建和编写电子表格文档-电子表格引擎。…

docker 安装及配置 nginx + tomcat(四):高可用

文章目录 1. 引言2. 高可用架构3. 实际步骤3.1 虚拟机新建系统3.2 安装 keepalived3.3 配置 keepalived3.4 启动 keepalived3.5 验证高可用3.5.1 查看当前效果3.5.2 模拟灾难 4 参考 1. 引言 前情提要&#xff1a; 《docker 安装及配置 nginx tomcat&#xff08;一&#xff0…

Arcgis导出为tiff

原有一幅影像&#xff0c;在进行一些操作之后&#xff0c;需要导出为tiff 比如我对他进行一个重采样&#xff0c;48m分辨率变为96m 在重采样后的数据图层上右键&#xff0c;导出数据 为什么有时会导出为.gdb格式的呢&#xff1f; 可能是位置处在一个文件地理数据库.gdb下

【go-zero】 go-zero API 如何接入 Nacos 被 java 服务调用 | go集成java服务

一、场景 外层使用的是springcloud alibaba 这一套java的分布式架构 然后需要接入go-zero的api服务 这里我们将对api服务接入Nacos进行一个说明 二、实战 1、package 因为使用的是go-zero框架 这里我们会优先使用go-zero生态的包 github 包如下: github.com/nacos-group/naco…