1# LazyForEach:数据懒加载 2 3 4LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。 5 6 7## 接口描述 8 9 10```ts 11LazyForEach( 12 dataSource: IDataSource, // 需要进行数据迭代的数据源 13 itemGenerator: (item: Object) => void, // 需要进行数据迭代的数据源 14 keyGenerator?: (item: Object): string => string // 需要进行数据迭代的数据源 15): void 16``` 17 18**参数:** 19 20 21| 参数名 | 参数类型 | 必填 | 参数描述 | 22| ------------- | --------------------------------------- | ---- | ------------------------------------------------------------ | 23| dataSource | [IDataSource](#idatasource类型说明) | 是 | LazyForEach数据源,需要开发者实现相关接口。 | 24| itemGenerator | (item: any) => void | 是 | 子组件生成函数,为数组中的每一个数据项创建一个子组件。<br/>**说明:**<br/>itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。 | 25| keyGenerator | (item: any) => string | 否 | 键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。<br/>**说明:**<br/>数据源中的每一个数据项生成的键值不能重复。 | 26 27## IDataSource类型说明 28 29```ts 30interface IDataSource { 31 totalCount(): number; // 获得数据总数 32 getData(index: number): Object; // 获取索引值对应的数据 33 registerDataChangeListener(listener: DataChangeListener): void; // 注册数据改变的监听器 34 unregisterDataChangeListener(listener: DataChangeListener): void; // 注销数据改变的监听器 35} 36``` 37 38| 接口声明 | 参数类型 | 说明 | 39| ------------------------------------------------------------ | ------------------------------------------------- | ----------------------------------------------------------- | 40| totalCount(): number | - | 获得数据总数。 | 41| getData(index: number): any | number | 获取索引值index对应的数据。<br/>index:获取数据对应的索引值。 | 42| registerDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)): void | [DataChangeListener](#datachangelistener类型说明) | 注册数据改变的监听器。<br/>listener:数据变化监听器 | 43| unregisterDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)): void | [DataChangeListener](#datachangelistener类型说明) | 注销数据改变的监听器。<br/>listener:数据变化监听器 | 44 45## DataChangeListener类型说明 46 47```ts 48interface DataChangeListener { 49 onDataReloaded(): void; // 重新加载数据时调用 50 onDataAdded(index: number): void; // 添加数据时调用 51 onDataMoved(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换时调用 52 onDataDeleted(index: number): void; // 删除数据时调用 53 onDataChanged(index: number): void; // 改变数据时调用 54 onDataAdd(index: number): void; // 添加数据时调用 55 onDataMove(from: number, to: number): void; // 数据移动起始位置与数据移动目标位置交换时调用 56 onDataDelete(index: number): void; // 删除数据时调用 57 onDataChange(index: number): void; // 改变数据时调用 58} 59``` 60 61| 接口声明 | 参数类型 | 说明 | 62| ------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------ | 63| onDataReloaded(): void | - | 通知组件重新加载所有数据。 | 64| onDataAdd(index: number): void<sup>8+</sup> | number | 通知组件index的位置有数据添加。<br/>index:数据添加位置的索引值。 | 65| onDataMove(from: number, to: number): void<sup>8+</sup> | from: number,<br/>to: number | 通知组件数据有移动。<br/>from: 数据移动起始位置,to: 数据移动目标位置。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 | 66| onDataDelete(index: number):void<sup>8+</sup> | number | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>index:数据删除位置的索引值。<br/>**说明:** <br/>需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。 | 67| onDataChange(index: number): void<sup>8+</sup> | number | 通知组件index的位置有数据有变化。<br/>index:数据变化位置的索引值。 | 68| onDataAdded(index: number):void<sup>(deprecated)</sup> | number | 通知组件index的位置有数据添加。<br/>从API 8开始,建议使用onDataAdd。<br/>index:数据添加位置的索引值。 | 69| onDataMoved(from: number, to: number): void<sup>(deprecated)</sup> | from: number,<br/>to: number | 通知组件数据有移动。<br/>从API 8开始,建议使用onDataMove。<br/>from: 数据移动起始位置,to: 数据移动目标位置。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 | 70| onDataDeleted(index: number):void<sup>(deprecated)</sup> | number | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>从API 8开始,建议使用onDataDelete。<br/>index:数据删除位置的索引值。 | 71| onDataChanged(index: number): void<sup>(deprecated)</sup> | number | 通知组件index的位置有数据有变化。<br/>从API 8开始,建议使用onDataChange。<br/>index:数据变化监听器。 | 72 73 74## 使用限制 75 76- LazyForEach必须在容器组件内使用,仅有[List](../reference/arkui-ts/ts-container-list.md)、[Grid](../reference/arkui-ts/ts-container-grid.md)、[Swiper](../reference/arkui-ts/ts-container-swiper.md)以及[WaterFlow](../reference/arkui-ts/ts-container-waterflow.md)组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。 77 78- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。 79 80- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。 81 82- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。 83 84- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件被框架忽略,从而无法在父容器内显示。 85 86- LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。 87 88- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。 89 90- itemGenerator函数的调用顺序不一定和数据源中的数据项相同,在开发过程中不要假设itemGenerator和keyGenerator函数是否执行及其执行顺序。例如,以下示例可能无法正常运行: 91 92 93 ```ts 94 LazyForEach(dataSource, 95 (item: Object) => Text(`${item.i}. item.data.label`), 96 (item: Object): string => item.data.id.toString()) 97 ``` 98 99 100## 示例 101 102 103```ts 104// Basic implementation of IDataSource to handle data listener 105class BasicDataSource implements IDataSource { 106 private listeners: DataChangeListener[] = []; 107 108 public totalCount(): number { 109 return 0; 110 } 111 112 public getData(index: number): Object { 113 return undefined; 114 } 115 116 registerDataChangeListener(listener: DataChangeListener): void { 117 if (this.listeners.indexOf(listener) < 0) { 118 console.info('add listener'); 119 this.listeners.push(listener); 120 } 121 } 122 123 unregisterDataChangeListener(listener: DataChangeListener): void { 124 const pos = this.listeners.indexOf(listener); 125 if (pos >= 0) { 126 console.info('remove listener'); 127 this.listeners.splice(pos, 1); 128 } 129 } 130 131 notifyDataReload(): void { 132 this.listeners.forEach(listener => { 133 listener.onDataReloaded(); 134 }) 135 } 136 137 notifyDataAdd(index: number): void { 138 this.listeners.forEach(listener => { 139 listener.onDataAdd(index); 140 }) 141 } 142 143 notifyDataChange(index: number): void { 144 this.listeners.forEach(listener => { 145 listener.onDataChange(index); 146 }) 147 } 148 149 notifyDataDelete(index: number): void { 150 this.listeners.forEach(listener => { 151 listener.onDataDelete(index); 152 }) 153 } 154 155 notifyDataMove(from: number, to: number): void { 156 this.listeners.forEach(listener => { 157 listener.onDataMove(from, to); 158 }) 159 } 160} 161 162class MyDataSource extends BasicDataSource { 163 private dataArray: string[] = []; 164 165 public totalCount(): number { 166 return this.dataArray.length; 167 } 168 169 public getData(index: number): Object { 170 return this.dataArray[index]; 171 } 172 173 public addData(index: number, data: string): void { 174 this.dataArray.splice(index, 0, data); 175 this.notifyDataAdd(index); 176 } 177 178 public pushData(data: string): void { 179 this.dataArray.push(data); 180 this.notifyDataAdd(this.dataArray.length - 1); 181 } 182} 183 184@Entry 185@Component 186struct MyComponent { 187 aboutToAppear() { 188 for (let i = 100; i >= 80; i--) { 189 this.data.pushData(`Hello ${i}`) 190 } 191 } 192 193 private data: MyDataSource = new MyDataSource(); 194 195 build() { 196 List({ space: 3 }) { 197 LazyForEach(this.data, (item: string) => { 198 ListItem() { 199 Row() { 200 Text(item).fontSize(50) 201 .onAppear(() => { 202 console.info("appear:" + item) 203 }) 204 }.margin({ left: 10, right: 10 }) 205 } 206 .onClick(() => { 207 this.data.pushData(`Hello ${this.data.totalCount()}`); 208 }) 209 }, (item: string) => item) 210 }.cachedCount(5) 211 } 212} 213``` 214