渲染控制之条件渲染、循环渲染以及数据懒加载

一、概述

        在声明式描述语句中开发者除了使用系统组件外,还可以使用渲染控制语句来辅助UI的构建,这些渲染控制语句包括控制组件是否显示的条件渲染语句,基于数组数据快速生成组件的循环渲染语句以及针对大数据量场景的数据懒加载语句。

二、if/else:条件渲染

        ArkTS提供了渲染控制的能力。条件渲染可根据应用的不同状态,使用if、else和else if渲染对应状态下的UI内容。

使用规则:

  • 支持if、else和else if语句。
  • if、else if后跟随的条件语句可以使用状态变量。
  • 允许在容器组件内使用,通过条件渲染语句构建不同的子组件。
  • 条件渲染语句在涉及到组件的父子关系时是“透明”的,当父组件和子组件之间存在一个或多个if语句时,必须遵循父组件关于子组件使用的规则。
  • 每个分子内部的构建函数必须遵循构建函数的规则,并创建一个或多个组件。无法构建组件的空构建函数会产生语法错误。
  • 某些容器组件限制子组件的类型或数量,将条件渲染语句用于这些组件内时,这些限制将同样应用于条件渲染语句内创建的组件。

1、使用if进行条件渲染

@Entry
@Component
struct IfForEachPage {
  @State count: number = 0;

  build() {
    Row() {
      Column() {
        Text("count" + this.count)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .width("96%")
          .margin({ top: 12 })

        if (this.count > 0) {
          Text(`count is positive`)
            .fontColor(Color.Green)
            .margin({ top: 12 })
        }

        Button('increase count')
          .margin({ top: 12 })
          .onClick(() => {
            this.count++;
          })

        Button('decrease count')
          .margin({ top: 12 })
          .onClick(() => {
            this.count--;
          })

      }
      .width('100%')
      .height("100%")
    }
    .height('100%')
  }
}

        if语句的每个分支都包含一个构建函数。此类构建函数必须创建一个或多个子组件。在初始渲染时,if语句会执行构建函数,并将生成的子组件添加到其父组件中。

        每当if或else if条件语句中使用的状态变量发生变化时,条件语句都会更新并重新评估新的条件值。如果条件值评估发生了变化,这意味着需要构建另一个条件分支。

2、if ... else ...语句和子组件状态

@Entry
@Component
struct IfForEachPage {
  @State isCheck: boolean = true;

  build() {
    Row() {
      Column() {
        if (this.isCheck) {
          Text("CounterView #positive");
        } else {
          Text("CounterView #negative");
        }

        Button('decrease isCheck: ' + this.isCheck)
          .margin({ top: 12 })
          .onClick(() => {
            this.isCheck = !this.isCheck;
          })

      }
      .width('100%')
      .height("100%")
    }
    .height('100%')
  }
}
        Text("CounterView #positive")子组件在初次渲染时创建,当isCheck状态变量的值更改为false时,Text("CounterView #negative")创建此组件。

3、嵌套if语句

条件语句的嵌套对父组件的相关规则没有影响。

@Entry
@Component
struct IfForEachPage {
  @State toggle: boolean = false;
  @State toggleColor: boolean = false;

  build() {
    Row() {
      Column() {
        Text('Before')
          .fontSize(15)
        if (this.toggle) {
          Text('Top True, positive 1 top')
            .backgroundColor('#aaffaa').fontSize(20)
          // 内部if语句
          if (this.toggleColor) {
            Text('Top True, Nested True, positive COLOR  Nested ')
              .backgroundColor('#00aaaa').fontSize(15)
          } else {
            Text('Top True, Nested False, Negative COLOR  Nested ')
              .backgroundColor('#aaaaff').fontSize(15)
          }
        } else {
          Text('Top false, negative top level').fontSize(20)
            .backgroundColor('#ffaaaa')
          if (this.toggleColor) {
            Text('positive COLOR  Nested ')
              .backgroundColor('#00aaaa').fontSize(15)
          } else {
            Text('Negative COLOR  Nested ')
              .backgroundColor('#aaaaff').fontSize(15)
          }
        }
        Text('After')
          .fontSize(15)
        Button('Toggle Outer')
          .onClick(() => {
            this.toggle = !this.toggle;
          })
        Button('Toggle Inner')
          .onClick(() => {
            this.toggleColor = !this.toggleColor;
          })

      }
      .width('100%')
      .height("100%")
    }
    .height('100%')
  }
}

二、ForEach:循环渲染

1、描述       

        ForEach接口基于数组类型数据来进行循环渲染,需要与容器组件配合使用,且接口返回的组件应当是允许包含在ForEach父容器组件中的子组件。

2、接口

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

3、参数

参数名参数类型必填参数描述
arrArray

数据源,为Array类型的数组

说明:

1.可以设置为空数组,此时不会创建子组件。

2.可以设置返回值为数组类型的函数。

itemGenerator(item:any, index:number) => void

组件生成函数。

- 为数组中的每个元素创建对应的组件。

- item参数:arr数组中的数据项。

- index参数(可选):arr数组中的数据项索引。

keyGenerator(item: any, index?: number) => string

键值生成函数

- 为数据源arr的每个数组项生成唯一且持久的键值。函数返回值为开发者自定义的键值生成规则。

- item参数:arr数组中的数据项。- index参数(可选):arr数组中的数据项索引。

说明:

  • ForEach的itemGenerator函数可以包含if/else条件渲染逻辑。另外,也可以在if/else条件渲染语句中使用ForEach组件。
  • 在初始化渲染时,ForEach会加载数据源的所有数据,并为每个数据项创建对应的组件,然后将其挂载到渲染树上。如果数据源非常大或有特定的性能需求,建议使用LazyForEach组件。

4、示例

@Entry
@Component
struct Parent {
  @State simpleList: Array<string> = ['one', 'two', 'three'];

  build() {
    Row() {
      Column() {
        ForEach(this.simpleList, (item: string) => {
          ChildItem({ item: item })
        }, (item: string) => item)
      }
      .width('100%')
      .height('100%')
    }
    .height('100%')
    .backgroundColor(0xF1F3F5)
  }
}

@Component
struct ChildItem {
  @Prop item: string;

  build() {
    Text(this.item)
      .fontSize(50)
  }
}

5、效果图

6、ForEach数据源存在相同值案例首次渲染运行效果图

        如果数据源是:@State simpleList: Array<string> = ['one', 'two', 'two', 'three'];

        ForEach渲染相同的数据项two时,只创建了一个ChildItem组件,而没有创建多个具有相同键值的组件。

        在该示例中,最终键值生成规则为item。当ForEach遍历数据源simpleList,遍历到索引为1的two时,按照最终键值生成规则生成键值为two的组件并进行标记。当遍历到索引为2的two时,按照最终键值生成规则当前项的键值也为two,此时不再创建新的组件。

三、LazyForEach:数据懒加载

1、描述

        LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。

2、接口

LazyForEach(
    dataSource: IDataSource,             // 需要进行数据迭代的数据源
    itemGenerator: (item: any, index?: number) => void,  // 子组件生成函数
    keyGenerator?: (item: any, index?: number) => string // 键值生成函数
): void

3、参数

参数名

参数类型

必填

参数描述

dataSource

IDataSource

LazyForEach数据源,需要开发者实现相关接口。

itemGenerator

(item: any, index?:number) => void

子组件生成函数,为数组中的每一个数据项创建一个子组件。

说明:

item是当前数据项,index是数据项索引值。

itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。

keyGenerator

(item: any, index?:number) => string

键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。

说明:

item是当前数据项,index是数据项索引值。

数据源中的每一个数据项生成的键值不能重复。

4、IDataSource类型说明

interface IDataSource {
    totalCount(): number; // 获得数据总数
    getData(index: number): Object; // 获取索引值对应的数据
    registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器
    unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器
}

接口声明

参数类型

说明

totalCount(): number

-

获得数据总数。

getData(index: number): any

number

获取索引值index对应的数据。

index:获取数据对应的索引值。

registerDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注册数据改变的监听器。

listener:数据变化监听器

unregisterDataChangeListener(listener:DataChangeListener): void

DataChangeListener

注销数据改变的监听器。

listener:数据变化监听器

5、DataChangeListener类型说明

interface DataChangeListener {
    onDataReloaded(): void; // 重新加载数据完成后调用
    onDataAdded(index: number): void; // 添加数据完成后调用
    onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDeleted(index: number): void; // 删除数据完成后调用
    onDataChanged(index: number): void; // 改变数据完成后调用
    onDataAdd(index: number): void; // 添加数据完成后调用
    onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换完成后调用
    onDataDelete(index: number): void; // 删除数据完成后调用
    onDataChange(index: number): void; // 改变数据完成后调用
}

接口声明

参数类型

说明

onDataReloaded(): void

-

通知组件重新加载所有数据。

键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。

onDataAdd(index: number): void8+

number

通知组件index的位置有数据添加。

index:数据添加位置的索引值。

onDataMove(from: number, to: number): void8+

from: number,

to: number

通知组件数据有移动。

from: 数据移动起始位置,to: 数据移动目标位置。

说明:

数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。

onDataDelete(index: number):void8+

number

通知组件删除index位置的数据并刷新LazyForEach的展示内容。

index:数据删除位置的索引值。

说明:

需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。

onDataChange(index: number): void8+

number

通知组件index的位置有数据有变化。

index:数据变化位置的索引值。

onDataAdded(index: number):void(deprecated)

number

通知组件index的位置有数据添加。

从API 8开始,建议使用onDataAdd。

index:数据添加位置的索引值。

onDataMoved(from: number, to: number): void(deprecated)

from: number,

to: number

通知组件数据有移动。

从API 8开始,建议使用onDataMove。

from: 数据移动起始位置,to: 数据移动目标位置。

将from和to位置的数据进行交换。

说明:

数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。

onDataDeleted(index: number):void(deprecated)

number

通知组件删除index位置的数据并刷新LazyForEach的展示内容。

从API 8开始,建议使用onDataDelete。

index:数据删除位置的索引值。

onDataChanged(index: number): void(deprecated)

number

通知组件index的位置有数据有变化。

从API 8开始,建议使用onDataChange。

index:数据变化监听器。

6、使用限制

  • LazyForEach必须在容器组件内使用,仅有List、Grid、Swiper以及WaterFlow组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。
  • LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。
  • 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。
  • 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。
  • 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件被框架忽略,从而无法在父容器内显示。
  • LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。
  • 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。
  LazyForEach(this.data, (item: string, index: number) => {
        ListItem() {
          Row() {
            Text(item).fontSize(50)
              .onAppear(() => {
                console.info("appear:" + item)
              })
          }.margin({ left: 10, right: 10 })
        }
        .onClick(() => {
          this.data.changeData(index, item + '00');
        })
      }, (item: string) => item)

数据较大时,前端的v-for循环渲染可能会导致页面性能下降,因此需要进行优化。 一种常见的优化方法是使用虚拟滚动技术。虚拟滚动将只渲染当前可见的部分数据,而不是一次性渲染所有数据。这样可以减少渲染的负担,提高页面的加载速度和响应性能。具体实现时,可以使用库或插件,如"vue-virtual-scroller",来实现虚拟滚动效果。 另一种优化方法是使用分页加载或滚动加载。当数据较大时,可以将数据分成多个分页或进行无限滚动加载。每次只加载当前页或可见范围的数据,而不是一次性加载所有数据。这样可以减少页面渲染的时间和资源消耗。可以通过监听滚动事件或点击分页按钮来实现分页加载或滚动加载的效果。 还有一种优化方法是对数据进行缓存。当数据较大时,可以将数据缓存在本地,通过缓存机制减少数据的请求和加载时间。可以使用浏览器的缓存API,如localStorage或sessionStorage,来存储和获取数据。在第一次加载数据后,将数据保存在本地,并在后续加载页面时,首先从缓存中获取数据,而不是再次请求服务器。 此外,还可以通过其他一些常见的优化方法来提高性能,如减少不必要的计算和渲染,使用合适的数据结构和算法,合理使用异步加载等。需要根据具体情况进行综合考虑和选择合适的优化方法,以提升页面的性能和用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yyxhzdm

你的鼓励是我创作的最大动力!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值