HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制ForEach循环渲染

news2024/12/25 22:06:57

ForEach基于数组类型数据执行循环渲染。说明,从API version 9开始,该接口支持在ArkTS卡片中使用。
一、接口描述

ForEach(
  arr: any[], 
  itemGenerator: (item: any, index?: number) => void,
  keyGenerator?: (item: any, index?: number) => string 
)

#2023盲盒+码#HarmonyOS/OpenHarmony应用开发-ArkTS语言渲染控制ForEach循环渲染-开源基础软件社区


二、使用限制
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节点都需要重新构建,这操作负担要重得多。

 

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

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

相关文章

谈谈IP地址和子网掩码的概念及应用

个人主页&#xff1a;insist--个人主页​​​​​​ 本文专栏&#xff1a;网络基础——带你走进网络世界 本专栏会持续更新网络基础知识&#xff0c;希望大家多多支持&#xff0c;让我们一起探索这个神奇而广阔的网络世界。 目录 一、IP地址的概念 二、IP地址的分类 1、A类 …

react之 router6路由 (两种路由模式、两种路由跳转、两种传参与接收参数、嵌套路由,layout组件、路由懒加载)

目录 react路由1&#xff1a;安装和两种模式react路由2&#xff1a;两种路由跳转 &#xff08; 命令式与编程式&#xff09;2-1 路由跳转-命令式2-2 路由跳转-编程式 - 函数组件2-2-1 app.jsx2-2-2 page / Home.jsx2-2-3 page / About.jsx2-2-4 效果 react路由3&#xff1a;函数…

恒运资本:A股质押降至十年新低,高比例质押公司不足1%!

2018年&#xff0c;A股商场股权质押规划到达历史高位&#xff0c;为化解危险&#xff0c;监管层通过多种方式、多方合力给予纾解&#xff0c;取得积极成效。上篇分析了A股质押危险大幅缓解的六大原因&#xff0c;本篇从高份额质押股特征、职业、地域、企业类型等视点&#xff0…

七麦analysis参数加密分析

文章目录 1. 接口分析2. 断点调式3. 补环境重写加密4. 验证结果 1. 接口分析 目标站点&#xff1a; aHR0cHM6Ly93d3cucWltYWkuY24vcmFuay9tYXJrZXRSYW5r 先刷新网页&#xff0c;请求接口中的analysis参数本次分析的目标 2. 断点调式 打上XHR断点&#xff0c;刷新页面之后断点…

【MT32F006】MT32F006之串口

本文最后修改时间&#xff1a;2023年03月30日 一、本节简介 本文介绍如何使用MT32F006的串口。 二、实验平台 库版本&#xff1a;V1.0.0 编译软件&#xff1a;MDK5.37 硬件平台&#xff1a;MT32F006开发板&#xff08;主芯片MT32F006&#xff09; 仿真器&#xff1a;JLINK…

无涯教程-Perl - umask函数

描述 此函数为当前进程设置umask(创建文件和目录时应用的默认掩码)。 EXPR的值必须是八进制数。如果省略EXPR,则只需返回先前的值。 语法 以下是此函数的简单语法- umask EXPRumask返回值 此函数返回先前的umask值。 例 以下是显示其基本用法的示例代码- #!/usr/bin/pe…

Java学习笔记30——字节缓冲流

字节缓冲流 字节缓冲流缓冲输出流缓冲输入流 复制视频速度对比 字节缓冲流 缓冲输出流 BufferadOutputStream通过设置这样的输出流&#xff0c;应用程序可以向底层输出流写入字节&#xff0c;而不必为写入的每个细节导致底层系统的调用 构造方法 BufferadOutputStream(Outpu…

【Elsevier旗下】1区SCI,5天见刊!稳定检索36年

近日国自然即将发布&#xff0c;想必申请过国自然基金作者都知道&#xff0c;需要有研究基础&#xff0c;说白了就是需要有文章支持。那么稿子写好了&#xff0c;选择一本靠谱优质期刊也是一门学问。 以下2本Elsevier 旗下审稿快刊&#xff0c;各项指标优秀&#xff0c;实为评…

QT connect使用简单介绍

如图&#xff0c;首先 connect是线程安全的。其次它有很多重载&#xff0c;当然最重要的还是QT4连接和QT5连接的区别&#xff0c;这个函数重载表示connect函数也是支持lambda函数的。 connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)connec…

Redis——set类型详解

概要 Set&#xff08;集合&#xff09;&#xff0c;将一些有关联的数据放到一起&#xff0c;集合中的元素是无序的&#xff0c;并且集合中的元素是不能重复的 之前介绍的list就是有序的&#xff0c;对于列表来说[1, 2, 3] 和 [2, 1, 3]是两个不同的列表&#xff0c;而对于集合…

阿里Canal学习笔记

github地址 canal 使用IDEA打开&#xff0c;注意国内加载慢的问题&#xff0c;解决方式如下&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <settings xmlns"http://maven.apache.org/SETTINGS/1.0.0"xmlns:xsi"http://w…

CAS:2143933-81-5,ICG-Maleimide,吲哚菁绿马来酰亚胺,脂溶性染料试剂

今日文章关键词&#xff1a;脂溶性染料试剂,吲哚菁绿马来酰亚胺&#xff0c;CAS&#xff1a;2143933-81-5 试剂基团反应特点&#xff1a;ICG-maleimide的马来酰 亚胺可以和蛋白、多肽、小分子或抗体上的SH巯基反应连接。ICG染料的马来酰亚胺提供了开发共轭物的机会。Maleimide…

Python入门【原生字符串、边界字符、search函数、re模块中其他常用的函数 、贪婪模式和非贪婪模式、择一匹配(|)的使用、分组】(三十)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是爱敲代码的小王&#xff0c;CSDN博客博主,Python小白 &#x1f4d5;系列专栏&#xff1a;python入门到实战、Python爬虫开发、Python办公自动化、Python数据分析、Python前后端开发 &#x1f4e7;如果文章知识点有错误…

ZooKeeper的应用场景(分布式锁、分布式队列)

7 分布式锁 分布式锁是控制分布式系统之间同步访问共享资源的一种方式。如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源&#xff0c;那么访问这些资源的时候&#xff0c;往往需要通过一些互斥手段来防止彼此之间的干扰&#xff0c;以保证一致性&#xff0c;…

RuoYi项目下载及配置启动

一. 前置要求 这里假设你已经安装并配置了JDK1.8,Node,Git,IDEA,vscode,MySQL,navicat 16,Redis,RDM环境,如果没有下载安转可以参考我的博客找到对应的软件下载安装即可 二.下载源码 官网地址 方式一:直接下载ZIP压缩包 方式二:利用Git下载源码 点击复制之后,在需要下载代码…

华为昇腾助力大模型,算力国产化时代已来

去年世界人工智能大会&#xff08;WAIC 2022&#xff09;上&#xff0c;只有屈指可数的几家大厂推出大模型&#xff0c;但在科技部新一代人工智能发展研究中心5月底发布的《中国人工智能大模型地图研究报告》显示&#xff0c;我国10亿参数规模以上的大模型已发布79个&#xff0…

Do You Like Interactive Problems?

Problem - 7386 题目大意&#xff1a;给出一整数n&#xff0c;有一个隐藏的在1~n范围内的数x&#xff0c;每次随机询问一个y&#xff0c;会得知x<y,x>y,或xy&#xff0c;当可能的x唯一确定时停止询问&#xff0c;问期望的询问次数是多少 1<n<1e9 思路&#xff1…

AMEYA360邀您参加EESA第二届中国国际储能展览会暨第十届国际大会

2023年8月30日-9月1日&#xff0c;由EESA储能领跑者联盟主办的第二届中国国际储能展览会将重磅亮相苏州国际博览中心。作为目前中国乃至整个亚洲区最聚焦的储能展览会&#xff0c;本次展览会的规模高达10万平方米&#xff0c;来自全国各地超过1000多家储能核心企业将共襄盛举&a…

揭秘!体育比赛是如何快人一步购票的

最近&#xff0c;各类体育赛事正如火如荼的进行中&#xff0c;作为资深体育迷&#xff0c;看着赛场上的英雄们正在为荣誉和胜利而拼搏&#xff0c;内心也跟着激情澎湃起来。 为了享受精彩纷呈的赛事&#xff0c;越来越多体育迷选择亲临现场&#xff0c;感受更真实的比赛氛围&a…

Java:ArrayList集合、LinkedList(链表)集合的底层原理及应用场景

ArrayList集合的底层原理及应用场景 LinkedList&#xff08;链表&#xff09;集合的底层原理及应用场景 单向链表 增加数据 删除数据 双向链表 LinkedList的应用场景之一:可以用来设计队列 入队 出队 LinkedList的应用场景之一:可以用来设计栈 压栈&#xff08;push),addFirst…