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

鸿蒙-状态管理V1和V2在ForEach循环渲染的表现

目录

    • 前提
    • 遇到的问题
    • 换V2呗

状态管理V2已经出来好长时间了,移除GAP说明也有一段时间了,相信有一部分朋友已经开始着手从V1迁移到V2了,应该也踩了不少坑。
下面向大家分享一下我使用状态管理V1和Foreach时遇到的坑,以及状态管理V2在Foreach循环渲染中的表现。

前提

这里就先默认大家都已经熟悉状态管理V1中的@Observed装饰器和@ObjectLink装饰器,以及ForEach循环渲染相关的知识,并且仔细阅读过ForEach:循环渲染章节中的渲染结果非预期了。

遇到的问题

先说场景需求:
典型的支付结算页面选择优惠券的场景。当用户在结算页面点击优惠券时,跳转到优惠券列表页面,并在该页面向服务器请求优惠券列表数据。
这是服务器会根据传入的订单信息按照需求计算出默认选中哪个优惠券,该页面支持下拉刷新。
我们来简化一下优惠券数据,关键数据优惠券id,抵扣信息和描述。于是我们很容易写出如下代码:


//数据类
@Observed
class CouponData {id:string = ''name: string = ''defaultSelect: boolean = false
}//用于展示数据的控件
@Component
struct CouponView {@Watch('onCouponDataChange') @ObjectLink model: CouponData//优惠券是单选,因此选中|取消选中优惠券时通知父组件更新数据onChangeSelect:(id:string,select:boolean)=>void = (id:string,select:boolean)=>{}onCouponDataChange() {hilog.error(0x01, 'ForeachPage', `onCouponDataChange ${this.model.id}  ${this.model.defaultSelect}`)}build() {Row() {Text(`${this.model.name} , select :${this.model.defaultSelect}`)Circle().width(20).height(20).fill(this.model.defaultSelect ? Color.Red : Color.Gray).stroke(this.model.defaultSelect ? Color.Red : Color.Grey)}.padding({ top: 10, bottom: 10 }).onClick((_)=>{this.onChangeSelect(this.model.id,!this.model.defaultSelect)})}
}//为了简单展示,这里没有从服务器获取数据;下拉刷新也用按钮代替;点击确认时弹个toast提示一下选中的优惠券id@Entry
@Component
struct ForeachPage {@State couponDataList: CouponData[] = []aboutToAppear(): void {this.initData()}//模拟一下数据initData() {this.couponDataList = []for (let i = 0; i < 5; i++) {let model: CouponData = new CouponData()model.id= i.toString()model.name = `优惠券 ${i}`if (i == 1) {model.defaultSelect = true} else {model.defaultSelect = false}this.couponDataList.push(model)}}build() {Column() {//就当这里是下拉刷新了,问题不大Button("刷新").onClick((_)=>{this.initData()})List() {ForEach(this.couponDataList, (model: CouponData) => {ListItem() {CouponView({ model: model ,onChangeSelect:(id:string,select:boolean)=>{hilog.error(0x01, 'ForeachPage', `onChangeSelect ${id} ${select}`)this.couponDataList.forEach((data:CouponData)=>{if(data.id == id){data.defaultSelect = select}else{if(select){data.defaultSelect =false;}}})}})}}, (item: CouponData,index:number) => {let key = item.id +"__" +item.defaultSelecthilog.error(0x01, 'ForeachPage', key)return key})}.layoutWeight(1)Button("确定").onClick((_)=>{let selectCouponID:string = '未选中';this.couponDataList.forEach((couponData:CouponData)=>{if(couponData.defaultSelect){selectCouponID = couponData.id}})promptAction.showToast({message:`选中的优惠券是 ${selectCouponID}`})})}.height('100%').width('100%')}
}

使用了ForEach循环渲染来生成List的子组件,并且根据开发文档的使用建议,我们没有让index参与key的生成,而是使用优惠券的唯一id作为key。
运行后切换选中状态,完美。
但是遇到了两个问题:

  1. 点击刷新后,并没有将第二项设置未选中、其他项设置为未选中。
  2. 没有办法切换选中状态。
    -----emmmmmm------
    不急,肯定有它的原因。

看日志:发现在切换选中状态的时候列表项的key没有打印,说明选中状态的切换也就是UI的刷新不是因为key发生了变化,而是因为ObjectLink和Observed观测能力驱动的UI发生的变化。

接着就能确认问题1:因为切换选中状态时key没有变化,导致点击刷新之后,第二次列表的key和刚进入时列表key一致,因此UI没有刷新。
但这里有个问题:为什么参与计算key的属性发生了变化,但key却不会变化?这可能和ObjectLink和Observed观测能力的实现有关,这里没有确认。

但为什么没有办法切换选中状态?看文档中@State是可以观测到数组项赋值的。
根据问题1的结论接着推论:因为key相同,不会重新绘制列表项,这就引起了另外一个问题:列表项没有被重新绘制,因此列表项还是绑定着点击刷新之前数组中的对象,但我们点击列表项时,修改的是数组中的新对象,因此更不会刷新UI。

为了验证这个推论,我们第一次对数组赋值时将第二项默认选中设置为true; 点击刷新的时候,将第四项默认选中设置为true。
修改一下initData方法

    firstInit:boolean = true;initData() {this.couponDataList=[]for (let i = 0; i < 5; i++) {let model: CouponData = new CouponData()model.id= i.toString()model.name = `优惠券 ${i+1}`if(this.firstInit){if (i == 1 ) {model.defaultSelect = true} else {model.defaultSelect = false}}else{if (i == 3 ) {model.defaultSelect = true} else {model.defaultSelect = false}}this.couponDataList.push(model)}this.firstInit = false;}

这时候,我们进入页面,默认选中了第二项。然后点击第一项,将第一项切换为选中状态。之后点击刷新。发现第一项和第四项都变成了选中状态。

这时候我们点击第二项,可以将第二项切换为选中状态,并且第四项切换为未选中状态。这是是因key发生了变化,列表项重绘,绑定了数组中新的对象。

然后点击第三项或者第五项,都可以将第二项切换为未选中状态,但第三项和第五项本身不会被选中。因为第三项和第五项没有重绘,还是绑定的数组中之前的对象。

这时候选中第二项或者第四项之后,再点击第一项,发现并没有将第二项或者第四项切换为未选中状态,这是因为第一项没有被重绘,绑定的还是数组中之前的对象,并且是选中状态,这时候我们点击第一项是取消第一项的选中,并不会修改其他数据。

这里也验证了我们上面的推论。

这里就有人问了:

emmm,那怎么办?
凉拌呗,换V2。
不行哇,这个数据类在其他地方也在用,还都是用的V1。
你看,着kpi不就有着落了嘛

好吧,也有个比较恶心的办法,不追求极致性能、数据量较小的时候可以拿来应急:
定义一个变量,让这个变量参与key的生成,并且在每次刷新的时候都修改这个变量,进而达到强制让key发生变化,重绘所有列表项。

refreshTime:number = 0;
initData() {this.refreshTime = systemDateTime.getTime()...
}
//ForEach额key生成方法
(item: CouponData,index:number) => {let key = item.id +"__" +item.defaultSelect +"__"+this.refreshTimehilog.error(0x01, 'ForeachPage', key)return key}

emmm,这样可以正常刷新。

换V2呗

改动也没多少,不过有一点比较恶心,就是被@ObservedV2修饰的类,参与UI展示的属性必须被@Trace修饰,属性少了还好说,属性多了纯纯体力活。
写了个插件,可以从json字符串转为ArkTS对象,并且自动加上@Trace修饰
github
gitee
gitcode

@Entry
@ComponentV2 //修改为V2
struct ForeachPage {@Local couponDataList: CouponData[] = [] //修改为V2aboutToAppear(): void {this.initData()}initData() {this.couponDataList = []for (let i = 0; i < 5; i++) {let model: CouponData = new CouponData()model.id = i.toString()model.name = `优惠券 ${i + 1}`if (i == 1) {model.defaultSelect = true} else {model.defaultSelect = false}this.couponDataList.push(model)}}build() {Column() {Button("刷新").onClick((_) => {this.initData()})List() {ForEach(this.couponDataList, (model: CouponData) => {ListItem() {CouponView({model: model, onChangeSelect: (id: string, select: boolean) => {hilog.error(0x01, 'ForeachPage', `onChangeSelect ${id} ${select}`)this.couponDataList.forEach((data: CouponData) => {if (data.id == id) {data.defaultSelect = select} else {if (select) {data.defaultSelect = false;}}})}})}}, (item: CouponData, index: number) => {let key = item.id + "__" + item.defaultSelecthilog.error(0x01, 'ForeachPage', key)return key})}.layoutWeight(1)Button("确定").onClick((_) => {let selectCouponID: string = '未选中';this.couponDataList.forEach((couponData: CouponData) => {if (couponData.defaultSelect) {selectCouponID = couponData.id}})promptAction.showToast({ message: `选中的优惠券是 ${selectCouponID}` })})}.height('100%').width('100%')}
}@ComponentV2   //修改为V2
struct CouponView {@Require @Param model: CouponData   //修改为V2@Event //修改为V2onChangeSelect: (id: string, select: boolean) => void = (id: string, select: boolean) => {}aboutToAppear(): void {hilog.error(0x01, 'ForeachPage', `aboutToAppear ${this.model.id}`)}build() {Row() {Text(`${this.model.name} , select :${this.model.defaultSelect}`)Circle().width(20).height(20).fill(this.model.defaultSelect ? Color.Red : Color.Gray).stroke(this.model.defaultSelect ? Color.Red : Color.Grey)}.padding({ top: 10, bottom: 10 }).onClick((_) => {this.onChangeSelect(this.model.id, !this.model.defaultSelect)})}
}@ObservedV2 //修改为V2
class CouponData {id: string = ''@Trace name: string = '' //修改为V2@Trace defaultSelect: boolean = false //修改为V2
}

当我们切换选中状态,然后点击刷新后,再次切换选中状态也是正常的。通过CouponViewaboutToAppear方法的日志,也可以看到只重绘了key发生改变的列表项。

所以,那么,因此,迁移到V2不?

你问我迁移了吗?正在迁移,或许等到V3出来,我就迁移完了。

相关文章:

鸿蒙-状态管理V1和V2在ForEach循环渲染的表现

目录 前提遇到的问题换V2呗 状态管理V2已经出来好长时间了&#xff0c;移除GAP说明也有一段时间了&#xff0c;相信有一部分朋友已经开始着手从V1迁移到V2了&#xff0c;应该也踩了不少坑。 下面向大家分享一下我使用状态管理V1和Foreach时遇到的坑&#xff0c;以及状态管理V2在…...

深入了解递归、堆与栈:C#中的内存管理与函数调用

在编程中&#xff0c;理解如何有效地管理内存以及如何控制程序的执行流程是每个开发者必须掌握的基本概念。C#作为一种高级编程语言&#xff0c;其内存管理和函数调用机制包括递归、堆与栈。本文将详细讲解这三者的工作原理、用途以及它们在C#中的实现和应用。 1. 递归 (Recur…...

图论---Prim堆优化(稀疏图)

题目通常会提示数据范围&#xff1a; 若 V ≤ 500&#xff0c;两种方法均可&#xff08;朴素Prim更稳&#xff09;。 若 V ≤ 1e5&#xff0c;必须用优先队列Prim vector 存图。 #include <iostream> #include <vector> #include <queue> #include <…...

stm32之GPIO函数详解和上机实验

目录 1.LED和蜂鸣器1.1 LED1.2 蜂鸣器 2.实验2.1 库函数&#xff1a;RCC和GPIO2.1.1 RCC函数1. RCC_AHBPeriphClockCmd2. RCC_APB2PeriphClockCmd3. RCC_APB1PeriphClockCmd 2.1.2 GPIO函数1. GPIO_DeInit2. GPIO_AFIODeInit3. GPIO_Init4. GPIO_StructInit5. GPIO_ReadInputDa…...

用 PyQt5 和 asyncio 打造接口并发测试 GUI 工具

接口并发测试是测试工程师日常工作中的重要一环&#xff0c;而一个直观的 GUI 工具能有效提升工作效率和体验。本篇文章将带你用 PyQt5 和 asyncio 从零实现一个美观且功能实用的接口并发测试工具。 我们将实现以下功能&#xff1a; 请求方法选择器 添加了一个下拉框 QComboBo…...

OpenHarmony Camera开发指导(四):相机会话管理(ArkTS)

概述 相机在使用预览、拍照、录像、获取元数据等功能前&#xff0c;都需要先创建相机会话。 相机会话Session的功能如下&#xff1a; 配置相机的输入流和输出流。 配置输入流即添加设备输入&#xff0c;通俗来讲即选择某一个摄像头进行拍照录像&#xff1b;配置输出流&#x…...

深入探索RAG(检索增强生成)模型的优化技巧

&#x1f4cc; 友情提示&#xff1a; 本文内容由银河易创AI&#xff08;https://ai.eaigx.com&#xff09;创作平台的gpt-4o-mini模型生成&#xff0c;旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证&#xff0c;建议读者通过官方文档或实践进一步确认其准…...

Spring boot 中的IOC容器对Bean的管理

Spring Boot 中 IOC 容器对 Bean 的管理&#xff0c;涵盖从容器启动到 Bean 的生命周期管理的全流程。 步骤 1&#xff1a;理解 Spring Boot 的容器启动 Spring Boot 的 IOC 容器基于 ApplicationContext&#xff0c;在应用启动时自动初始化。 入口类&#xff1a;通过 SpringB…...

Qt实战之将自定义插件(minGW)显示到Qt Creator列表的方法

Qt以其强大的跨平台特性和丰富的功能&#xff0c;成为众多开发者构建图形用户界面&#xff08;GUI&#xff09;应用程序的首选框架。而在Qt开发的过程中&#xff0c;自定义插件能够极大地拓展应用程序的功能边界&#xff0c;让开发者实现各种独特的、个性化的交互效果。想象一下…...

【Vue】TypeScript与Vue3集成

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Vue 文章目录 1. 前言2. 环境准备与基础搭建2.1. 安装 Node.js 与 npm/yarn/pnpm2.2. 创建 Vue3 TypeScript 项目2.2.1. 使用 Vue CLI2.2.2. 使用 Vite&#xff08;推荐&#xff09;2.2.3. 目录结构简述 3. Vue3 TS 基础语法整…...

Linux之七大难命令(The Seven Difficult Commands of Linux)

Linux之七大难命令 、背景 作为Linux的初学者&#xff0c;肯定要先掌握高频使用的指令&#xff0c;这样才能让Linux的学习在短时间内事半功倍。但是&#xff0c;有些指令虽然功能强大&#xff0c;但因参数多而让初学者们很害怕&#xff0c;今天介绍Linux中高频使用&#xff0…...

Spring Boot单元测试实战指南:从零到高效测试

在Spring Boot开发中&#xff0c;单元测试是保障代码质量的核心环节。本文将基于实际开发场景&#xff0c;手把手教你如何快速实现分层测试、模拟依赖、编写高效断言&#xff0c;并分享最佳实践&#xff01; 一、5分钟环境搭建 添加依赖 在pom.xml中引入spring-boot-starter-te…...

5.3.1 MvvmLight以及CommunityToolkit.Mvvm介绍

MvvmLight、CommunityToolkit.Mvvm是开源包,他们为实现 MVVM(Model-View-ViewModel)模式提供了一系列实用的特性和工具,能帮助开发者更高效地构建 WPF、UWP、MAUI 等应用程序。 本文介绍如下: 一、使用(旧)的MvvmLight库 其特点如下,要继承的基类是ViewModelBase;且使用…...

Dbeaver 执行 SQL 语句和执行 SQL 脚本的区别

执行 SQL 语句 执行 SQL 语句对应图标&#xff1a; 适用于执行单个 SQL 的情形&#xff0c;默认是在光标处或选中的文本上执行 SQL 查询。 实际上同时选择多个 SQL 并通过该方式去执行也可能成功&#xff0c;只是有失败的风险。因此不建议使用它来同时执行多个 SQL 语句。 情况…...

《Python3网络爬虫开发实战(第二版)》配套案例 spa6

Scrape | Moviehttps://spa6.scrape.center/ 请求影片列表api时&#xff0c;不仅有分页参数&#xff0c;还多了一个token&#xff0c;通过重发请求发现token有时间限制&#xff0c;所以得逆向token的生成代码。 通过xhr断点定位到接口请求位置 刷新页面或者点翻页按钮&#x…...

AWS 中国区 CloudFront SSL 证书到期更换实战指南

适用场景: AWS 中国区(宁夏区域 cn-northwest-1 或北京区域 cn-north-1)CloudFront 分配的 SSL 证书到期后无缝替换,域名主体为 domain.cn。 背景与痛点 当 CloudFront 使用的 SSL 证书即将到期时,需手动替换新证书以避免服务中断。由于 AWS 中国区 不支持 ACM 证书,必须…...

Python基础语法:字面量,注释,关键字,标识符,变量和引用,程序执行的3大流程

目录 字面量&#xff08;数据的类型&#xff09; 字面量的含义 常见字面量类型&#xff08;6种&#xff09; 输出各类字面量&#xff08;print语句&#xff09; 注释&#xff08;单行和多行注释&#xff09; 注释的作用 单行注释和多行注释 单行注释&#xff08;ctrl/&a…...

SPL 量化 获取数据

下载数据 我们将股票数据分享在百度网盘上供下载&#xff0c;每工作日更新。 目前可供下载的数据有 A 股的日 K 线数据、股票代码列表和上市公司的基本面数据 下载链接&#xff1a; 百度网盘 下载数据的文件格式为 btx&#xff0c;是 SPL 的特有二进制格式。 btx 称为集文…...

VMware与Docker:虚拟化技术的双轨演进与融合实践

一、虚拟化的本质与价值重构 虚拟化&#xff08;Virtualization&#xff09;是通过软件抽象层将物理资源转化为可动态分配的虚拟单元&#xff0c;其核心价值在于打破"一机一用"的刚性架构&#xff0c;实现三大突破性转变&#xff1a; 资源解耦&#xff1a;硬件资源…...

3. pandas笔记之:创建

以下是 Pandas 主要数据结构的创建方式整理&#xff0c;涵盖 Series 和 DataFrame 的常见创建方法&#xff1a; 一、Series 创建方式 从列表/数组创建 import pandas as pd import numpy as np# 基础列表 s1 pd.Series([1, 3, 5, np.nan, 6])# 指定索引 s2 pd.Series([10, …...

潞晨科技将暂停DeepSeek API服务,AI大模型技术红利普惠化与市场竞争白热化叠加,内卷恶果,开始显现!

潞晨科技宣布暂停DeepSeek API服务的事件,不仅暴露了AI大模型行业的技术与成本博弈,更折射出国内AI生态中中小企业的生存困境和行业内卷的深层矛盾。这一事件背后,既有企业个体商业模式的局限性,也揭示了整个行业在技术迭代、成本控制和市场策略上的系统性挑战。 一、潞晨科…...

某大型电解铝厂电解系统谐波治理装置改造沃伦森电气

电解铝行业谐波治理解决方案——无源滤波装置优化升级&#xff0c;保障稳定运行 在电解铝生产过程中&#xff0c;谐波污染问题严重影响电网电能质量&#xff0c;甚至可能导致滤波装置损坏&#xff0c;引发群爆事故。河南登封某大型电解铝厂通过无源滤波装置智能化改造&#xff…...

Rust 学习笔记:安装 Rust

Rust 学习笔记&#xff1a;安装 Rust Rust 学习笔记&#xff1a;安装 Rust在 Windows 上安装 Rust命令行创建 Rust 项目在 Mac/Linux 上安装 Rust一些命令升级卸载cargo -hrustc -h 安装 RustRoverrust-analyzer Rust 学习笔记&#xff1a;安装 Rust 在 Windows 上安装 Rust …...

精准落地设计,现代项目管理中的深度实践

在数字化转型浪潮席卷全球的当下&#xff0c;项目管理的复杂性呈指数级增长。无论是软件开发、大型工程建设&#xff0c;还是企业流程再造&#xff0c;都面临着设计理念与实际执行之间的鸿沟。《人月神话》第6章中关于确保体系结构师设计准确落地的论述&#xff0c;为破解这一难…...

编译 C++ 报错“找不到 g++ 编译器”的终极解决方案(含 Windows/Linux/macOS)

前言 在使用终端编译 C 程序时&#xff0c;报错&#xff1a; 或类似提示&#xff0c;意味着你的系统尚未正确安装或配置 g 编译器。本篇将从零手把手教你在 Windows / Linux / macOS 下安装并配置 g&#xff0c;适用于新手或 C 入门阶段的你。 什么是 g&#xff1f; g 是 GN…...

联易融出席深圳链主企业供应链金融座谈会,加速对接票交所系统

近日&#xff0c;深圳市委金融办组织召开全市链主企业供应链金融高质量发展座谈会。联易融作为供应链金融企业代表&#xff0c;与虾皮信息科技、电子元器件和集成电路国际交易中心等代表性机构以及行业协会、金融机构参加了会议。 发展供应链金融是破解中小微企业融资难、融资…...

html单页业务介绍源码

源码介绍 html单页业务介绍源码&#xff0c;源码由HTMLCSSJS组成&#xff0c;记事本打开源码文件可以进行内容文字之类的修改&#xff0c;双击html文件可以本地运行 效果预览 源码免费获取 html单页业务介绍源码...

单体OJ项目

单体项目版本、微服务版还需我再钻研钻研。 项目介绍 在系统前台&#xff0c;管理员可以创建、管理题目;用户可以自由搜索题目、阅读题目、编写并提交代码。 在系统后端&#xff0c;能够根据管理员设定的题目测试用例在代码沙箱 中对代码进行编译、运行、判断输出是否正确。 其…...

豆包桌面版 1.47.4 可做浏览器,免安装绿色版

自己动手升级更新办法&#xff1a; 下载新版本后安装&#xff0c;把 C:\Users\用户名\AppData\Local\Doubao\Application 文件夹的文件&#xff0c;拷贝替换 DoubaoPortable\App\Doubao 文件夹的文件&#xff0c;就升级成功了。 再把安装的豆包彻底卸载就可以。 桌面版比网页版…...

数据分析案例:医疗健康数据分析

目录 数据分析案例:医疗健康数据分析1. 项目背景2. 数据加载与预处理2.1 加载数据2.2 数据清洗3. 探索性数据分析(EDA)3.1 再入院率概览3.2 按年龄分组的再入院率3.3 住院时长与再入院4. 特征工程与可视化5. 模型构建与评估5.1 数据划分5.2 训练逻辑回归5.3 模型评估6. 业务…...