HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制ForEach循环渲染
ForEach基于数组类型数据执行循环渲染。说明,从API version 9开始,该接口支持在ArkTS卡片中使用。
 一、接口描述
ForEach(arr: any[], itemGenerator: (item: any, index?: number) => void,keyGenerator?: (item: any, index?: number) => string 
)

 二、使用限制
 ForEach必须在容器组件内使用。
 生成的子组件应当是允许包含在ForEach父容器组件中的子组件。
 允许子组件生成器函数中包含if/else条件渲染,同时也允许ForEach包含在if/else条件渲染语句中。
 itemGenerator函数的调用顺序不一定和数组中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正确运行
ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }), item => Text(`${item.i}. item.data.label`),item => item.data.id.toString())
三、开发者的建议
 建议开发者不要假设项构造函数的执行顺序。执行顺序可能不能是数组中项的排列顺序。
 不要假设数组项是否是初始渲染。ForEach的初始渲染在@Component首次渲染时构建所有数组项。后续框架版本中可能会将此行为更改为延迟加载模式。
 使用 index参数对UI更新性能有严重的负面影响,请尽量避免。
 如果项构造函数中使用index参数,则项索引函数中也必须使用该参数。否则,如果项索引函数未使用index参数,ForEach在生成实际的键值时,框架也会把index考虑进来,默认将index拼接在后面。
 四、使用场景
 1.简单ForEach示例
 根据arr数据分别创建3个Text和Divide组件。
@Entry
@Component
struct MyComponent {@State arr: number[] = [10, 20, 30];build() {Column({ space: 5 }) {Button('Reverse Array').onClick(() => {this.arr.reverse();})ForEach(this.arr, (item: number) => {Text(`item value: ${item}`).fontSize(18)Divider().strokeWidth(2)}, (item: number) => item.toString())}}
}2.复杂ForEach示例
@Component
struct CounterView {label: string;@State count: number = 0;build() {Button(`${this.label}-${this.count} click +1`).width(300).height(40).backgroundColor('#a0ffa0').onClick(() => {this.count++;})}
}@Entry
@Component
struct MainView {@State arr: number[] = Array.from(Array(10).keys()); // [0.,.9]nextUnused: number = this.arr.length;build() {Column() {Button(`push new item`).onClick(() => {this.arr.push(this.nextUnused++)}).width(300).height(40)Button(`pop last item`).onClick(() => {this.arr.pop()}).width(300).height(40)Button(`prepend new item (unshift)`).onClick(() => {this.arr.unshift(this.nextUnused++)}).width(300).height(40)Button(`remove first item (shift)`).onClick(() => {this.arr.shift()}).width(300).height(40)Button(`insert at pos ${Math.floor(this.arr.length / 2)}`).onClick(() => {this.arr.splice(Math.floor(this.arr.length / 2), 0, this.nextUnused++);}).width(300).height(40)Button(`remove at pos ${Math.floor(this.arr.length / 2)}`).onClick(() => {this.arr.splice(Math.floor(this.arr.length / 2), 1);}).width(300).height(40)Button(`set at pos ${Math.floor(this.arr.length / 2)} to ${this.nextUnused}`).onClick(() => {this.arr[Math.floor(this.arr.length / 2)] = this.nextUnused++;}).width(300).height(40)ForEach(this.arr,(item) => {CounterView({ label: item.toString() })},(item) => item.toString())}}
MainView拥有一个@State装饰的数字数组。添加、删除和替换数组项是可观察到的变化事件,当这些事件发生时,MainView内的ForEach都会更新。
 项目索引函数为每个数组项创建唯一且持久的键值,ArkUI框架通过此键值确定数组中的项是否有变化,只要键值相同,数组项的值就假定不变,但其索引位置可能会更改。此机制的运行前提是不同的数组项不能有相同的键值。
 使用计算出的ID,框架可以对添加、删除和保留的数组项加以区分:
 (1)框架将删除已删除数组项的UI组件。
 (2)框架仅对新添加的数组项执行项构造函数。
 (3)框架不会为保留的数组项执行项构造函数。如果数组中的项索引已更改,框架将仅根据新顺序移动其UI组件,但不会更新该UI组件。
 建议使用项目索引函数,但这是可选的。生成的ID必须是唯一的,这意味着不能为数组中的不同项计算出相同的ID。即使两个数组项具有相同的值,其ID也必须不同。
 如果数组项值更改,则ID必须更改。
 示例:如前所述,id生成函数是可选的。以下是不带项索引函数的ForEach:
 ForEach(this.arr,
 (item) => {
 CounterView({ label: item.toString() })
 }
 )
 如果没有提供项ID函数,则框架会尝试在更新ForEach时智能检测数组更改。但是,它可能会删除子组件,并为在数组中移动(索引被更改)的数组项重新执行项构造函数。在上面的示例中,这将更改应用程序针对CounterView counter状态的行为。创建新的CounterView实例时,counter的值将初始化为0。
 3.使用@ObjectLink的ForEach示例
 当需要保留重复子组件的状态时,@ObjectLink可将状态在组件树中向父组件推送。
let NextID: number = 0;@Observed
class MyCounter {public id: number;public c: number;constructor(c: number) {this.id = NextID++;this.c = c;}
}@Component
struct CounterView {@ObjectLink counter: MyCounter;label: string = 'CounterView';build() {Button(`CounterView [${this.label}] this.counter.c=${this.counter.c} +1`).width(200).height(50).onClick(() => {this.counter.c += 1;})}
}@Entry
@Component
struct MainView {@State firstIndex: number = 0;@State counters: Array<MyCounter> = [new MyCounter(0), new MyCounter(0), new MyCounter(0),new MyCounter(0), new MyCounter(0)];build() {Column() {ForEach(this.counters.slice(this.firstIndex, this.firstIndex + 3),(item) => {CounterView({ label: `Counter item #${item.id}`, counter: item })},(item) => item.id.toString())Button(`Counters: shift up`).width(200).height(50).onClick(() => {this.firstIndex = Math.min(this.firstIndex + 1, this.counters.length - 3);})Button(`counters: shift down`).width(200).height(50).onClick(() => {this.firstIndex = Math.max(0, this.firstIndex - 1);})}}
}当增加firstIndex的值时,Mainview内的ForEach将更新,并删除与项ID firstIndex-1关联的CounterView子组件。对于ID为firstindex + 3的数组项,将创建新的CounterView子组件实例。由于CounterView子组件的状态变量counter值由父组件Mainview维护,故重建CounterView子组件实例不会重建状态变量counter值。
 说明,违反上述数组项ID规则是最常见的应用开发错误,尤其是在Array场景下,因为执行过程中很容易添加重复的数字。
 4.ForEach的嵌套使用
 允许将ForEach嵌套在同一组件中的另一个ForEach中,但更推荐将组件拆分为两个,每个构造函数只包含一个ForEach。下面为ForEach嵌套使用反例。
class Month {year: number;month: number;days: number[];constructor(year: number, month: number, days: number[]) {this.year = year;this.month = month;this.days = days;}
}
@Component
struct CalendarExample {// 模拟6个月@State calendar : Month[] = [new Month(2020, 1, [...Array(31).keys()]),new Month(2020, 2, [...Array(28).keys()]),new Month(2020, 3, [...Array(31).keys()]),new Month(2020, 4, [...Array(30).keys()]),new Month(2020, 5, [...Array(31).keys()]),new Month(2020, 6, [...Array(30).keys()])]build() {Column() {Button() {Text('next month')}.onClick(() => {this.calendar.shift()this.calendar.push(new Month(year: 2020, month: 7, days: [...Array(31).keys()]))})ForEach(this.calendar,(item: Month) => {ForEach(item.days,(day : number) => {// 构建日期块},(day : number) => day.toString())// 内部ForEach},(item: Month) => (item.year * 12 + item.month).toString() // 字段与年和月一起使用,作为月份的唯一ID。)// 外部ForEach}}
}以上示例存在两个问题:
 (1)代码可读性差。
 (2)对于上述的年月份数据的数组结构形式,由于框架无法观察到针对该数组中Month数据结构的改变(比如day数组变化),从而内层的ForEach无法刷新日期显示。
 建议应用设计时将Calendar拆分为Year、Month和Day子组件。定义一个“Day”模型类,以保存有关day的信息,并用@Observed装饰此类。DayView组件利用ObjectLink装饰变量以绑定day数据。对MonthView和Month模型类执行同样的操作。
 5.ForEach中使用可选index参数示例
 可以在构造函数和ID生成函数中使用可选的index参数。
@Entry
@Component
struct ForEachWithIndex {@State arr: number[] = [4, 3, 1, 5];build() {Column() {ForEach(this.arr,(it, indx) => {Text(`Item: ${indx} - ${it}`)},(it, indx) => {return `${indx} - ${it}`})}}
}必须正确构造ID生成函数。当在项构造函数中使用index参数时,ID生成函数也必须使用index参数,以生成唯一ID和给定源数组项的ID。当数组项在数组中的索引位置发生变化时,其ID会发生变化。
 此示例还说明了index参数会造成显著性能下降。即使项在源数组中移动而不做修改,因为索引发生改变,依赖该数组项的UI仍然需要重新渲染。例如,使用索引排序时,数组只需要将ForEach未修改的子UI节点移动到正确的位置,这对于框架来说是一个轻量级操作。而使用索引时,所有子UI节点都需要重新构建,这操作负担要重得多。
相关文章:
 
HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制ForEach循环渲染
ForEach基于数组类型数据执行循环渲染。说明,从API version 9开始,该接口支持在ArkTS卡片中使用。 一、接口描述 ForEach(arr: any[], itemGenerator: (item: any, index?: number) > void,keyGenerator?: (item: any, index?: number) > stri…...
 
Powered by Paraverse | 平行云助力彼真科技打造演出“新物种”
01 怎么看待虚拟演出 彼真科技 我们怎么看待虚拟演出? 虚拟演出给音乐人或者音乐行业带来了哪些新的机会?通过呈现一场高标准的虚拟演出,我们的能力延伸点在哪里? 先说一下我们认知里的虚拟演出的本质: 音乐演出是一…...
 
企微配置回调服务
1、企微配置可信域名 2、企微获取成员userID 3、企微获取用户敏感数据 4、企微配置回调服务 文章目录 一、简介1、概述2、相关文档地址 二、企微配置消息服务器1、配置消息接收参数2、参数解析3、参数拼接规则 三、代码编写—使用已有库1、代码下载2、代码修改3、服务代码编写 …...
 
机器人远程控制软件设计
机器人远程控制软件设计 That’s all....
面试题-React(二):React中的虚拟DOM是什么?
一、什么是虚拟DOM? 虚拟DOM是React的核心概念之一,它是一个轻量级的JavaScript对象树,用于表示真实DOM的状态。在React中,当数据发生变化时,首先会在虚拟DOM上执行DOM更新,而不是直接操作真实DOM。然后&a…...
 
分布式链路追踪——Dapper, a Large-Scale Distributed Systems Tracing Infrastructure
要解决的问题 如何记录请求经过多个分布式服务的信息,以便分析问题所在?如何保证这些信息得到完整的追踪?如何尽可能不影响服务性能? 追踪 当用户请求到达前端A,将会发送rpc请求给中间层B、C;B可以立刻作…...
 
【IEEE会议】第二届IEEE云计算、大数据应用与软件工程国际学术会议 (CBASE2023)
第二届IEEE云计算、大数据应用与软件工程国际学术会议 (CBASE2023) 随着大数据时代的到来,对数据获取的随时性和对计算的需求也在逐渐增长。为推动大数据时代的云计算与软件工程的发展,促进该领域学术交流,在CBASE 2022成功举办的…...
 
Streamlit项目:基于讯飞星火认知大模型开发Web智能对话应用
文章目录 1 前言2 API获取3 官方文档的调用代码4 Streamlit 网页的搭建4.1 代码及效果展示4.2 Streamlit相关知识点 5 结语 1 前言 科大讯飞公司于2023年8月15日发布了讯飞认知大模型V2.0,这是一款集跨领域知识和语言理解能力于一体的新一代认知智能大模型。前日&a…...
 
[Vue]解决npm run dev报错node:internal/modules/cjs/loader:1031 throw err;
解决: 有2中方法,建议先尝试第一种,不行再第二种 第一种: 重新安装依赖环境 删除项目的node_modules文件夹,重新执行 # 安装依赖环境 npm install# 运行 npm run dev 我只用了第一种方法就可以了 ,第二种方法从别的博主那看到…...
 
nginx反向代理后实现nginx和apache两种web服务器能够记录客户端的真实IP地址
一.构建环境 二.配置反向代理 1.基于源码安装的nginx环境下修改nginx.conf(设备1) 2.通过windows powershell进行修改hosts文件并测试 3.设备2和设备3上查看日志,可以看到访问来源都是代理服务器(2.190)而不是真实…...
 
【仿写tomcat】四、解析http请求信息,响应给前端,HttpServletRequest、HttpServletResponse的简单实现
思考 在解析请求之前我们要思考一个问题,我们解析的是其中的哪些内容? 对于最基本的实现,当然是请求类型,请求的url以及请求参数,我们可以根据请求的类型作出对应的处理,通过url在我们的mapstore中找到se…...
 
FL Studio21.1中文完整版Win/Mac
FL Studio All Plugins Edition【中文完整版 Win/Mac】适合音乐制作人/工作室使用,全套插件!(20.9新增Vintage Chorus,Pitch Shifter变调插件)FL Studio是超多顶级音乐人的启蒙首选!包括百大DJ冠军Martin Garrix&…...
 
基于Mysql+Vue+Django的协同过滤和内容推荐算法的智能音乐推荐系统——深度学习算法应用(含全部工程源码)+数据集
目录 前言总体设计系统整体结构图系统流程图 运行环境Python 环境MySQL环境VUE环境 模块实现1. 数据请求和储存2. 数据处理计算歌曲、歌手、用户相似度计算用户推荐集 3. 数据存储与后台4. 数据展示 系统测试工程源代码下载其它资料下载 前言 本项目以丰富的网易云音乐数据为基…...
 
Python Web开发 Django 简介
今天来为大家介绍 Python 另一个 Web 开发框架 Django,它是一个基于 Python 定制的开源 Web 应用框架,最早源于一个在线新闻 Web 网站,后于2005年开源。Django 的功能大而全,它提供的一站式解决的思路,能让开发者不用在…...
 
HAproxy搭建web集群
目录 一、HAproxy 概述 二、HAproxy 主要特性 三、HAproxy 负载均衡策略(八种) 四、LVS、Nginx、HAproxy区别 Nginx LVS HAproxy 五、HAproxy部署实战 问题总结: 一、HAproxy 概述 HAProxy是可提供高可用性、负载均衡以及基于TCP和HTTP应用的代理࿰…...
临时用工小程序:一款便捷的用工管理软件
随着企业对人力资源需求的不断增长,临时用工需求也日益旺盛。为了满足这一需求,我们研发了一款名为“临时用工小程序”的软件系统,旨在帮助企业实现临时用工的高效管理。 一、技术栈介绍 后端技术栈 本系统采用Java语言作为开发语言&#…...
 
Android Studio 之 Android 中使用 HanLP 进行句子段落的分词处理(包括词的属性处理)的简单整理
Android Studio 之 Android 中使用 HanLP 进行句子段落的分词处理(包括词的属性处理)的简单整理 目录 Android Studio 之 Android 中使用 HanLP 进行句子段落的分词处理(包括词的属性处理)的简单整理 一、简单介绍 二、实现原理…...
CSDN编程题-每日一练(2023-08-20)
CSDN编程题-每日一练(2023-08-19) 一、题目名称:等差数列二、题目名称:喜水青蛙三、题目名称:括号匹配一、题目名称:等差数列 时间限制:1000ms内存限制:256M 题目描述: 给定一已排序的正整数组成的数组,求需要在中间至少插入多少个数才能将其补全成为一等差数列。 “…...
 
大数据:NumPy进阶应用详解
专栏介绍 结合自身经验和内部资料总结的Python教程,每天3-5章,最短1个月就能全方位的完成Python的学习并进行实战开发,学完了定能成为大佬!加油吧!卷起来! 全部文章请访问专栏:《Python全栈教…...
 
new String创建几个对象
在java17中 : 问题1:new String("abc")会产生多少个对象? 分两种情况: 情况1: 如果”abc”这个字符串常量不存在,则创建两个对象,分别是“abc”这个字符串常量,以及ne…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
 
定时器任务——若依源码分析
分析util包下面的工具类schedule utils: ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类,封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz,先构建任务的 JobD…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...
 
ABAP设计模式之---“简单设计原则(Simple Design)”
“Simple Design”(简单设计)是软件开发中的一个重要理念,倡导以最简单的方式实现软件功能,以确保代码清晰易懂、易维护,并在项目需求变化时能够快速适应。 其核心目标是避免复杂和过度设计,遵循“让事情保…...
 
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
 
解读《网络安全法》最新修订,把握网络安全新趋势
《网络安全法》自2017年施行以来,在维护网络空间安全方面发挥了重要作用。但随着网络环境的日益复杂,网络攻击、数据泄露等事件频发,现行法律已难以完全适应新的风险挑战。 2025年3月28日,国家网信办会同相关部门起草了《网络安全…...
 
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
