• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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:&nbsp;any)&nbsp;=&gt;&nbsp;void   | 是   | 子组件生成函数,为数组中的每一个数据项创建一个子组件。<br/>**说明:**<br/>itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。 |
25| keyGenerator  | (item:&nbsp;any)&nbsp;=&gt;&nbsp;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():&nbsp;number                                    | -                                                 | 获得数据总数。                                              |
41| getData(index:&nbsp;number):&nbsp;any                        | number                                            | 获取索引值index对应的数据。<br/>index:获取数据对应的索引值。 |
42| registerDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)):&nbsp;void | [DataChangeListener](#datachangelistener类型说明) | 注册数据改变的监听器。<br/>listener:数据变化监听器         |
43| unregisterDataChangeListener(listener:[DataChangeListener](#datachangelistener类型说明)):&nbsp;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():&nbsp;void                                  | -                                      | 通知组件重新加载所有数据。                                   |
64| onDataAdd(index:&nbsp;number):&nbsp;void<sup>8+</sup>        | number                                 | 通知组件index的位置有数据添加。<br/>index:数据添加位置的索引值。 |
65| onDataMove(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void<sup>8+</sup> | from:&nbsp;number,<br/>to:&nbsp;number | 通知组件数据有移动。<br/>from:&nbsp;数据移动起始位置,to:&nbsp;数据移动目标位置。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 |
66| onDataDelete(index: number):void<sup>8+</sup>                | number                                 | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>index:数据删除位置的索引值。<br/>**说明:** <br/>需要保证dataSource中的对应数据已经在调用onDataDelete前删除,否则页面渲染将出现未定义的行为。 |
67| onDataChange(index:&nbsp;number):&nbsp;void<sup>8+</sup>     | number                                 | 通知组件index的位置有数据有变化。<br/>index:数据变化位置的索引值。 |
68| onDataAdded(index:&nbsp;number):void<sup>(deprecated)</sup>  | number                                 | 通知组件index的位置有数据添加。<br/>从API 8开始,建议使用onDataAdd。<br/>index:数据添加位置的索引值。 |
69| onDataMoved(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void<sup>(deprecated)</sup> | from:&nbsp;number,<br/>to:&nbsp;number | 通知组件数据有移动。<br/>从API 8开始,建议使用onDataMove。<br/>from:&nbsp;数据移动起始位置,to:&nbsp;数据移动目标位置。<br/>**说明:**<br/>数据移动前后键值要保持不变,如果键值有变化,应使用删除数据和新增数据接口。 |
70| onDataDeleted(index: number):void<sup>(deprecated)</sup>     | number                                 | 通知组件删除index位置的数据并刷新LazyForEach的展示内容。<br/>从API 8开始,建议使用onDataDelete。<br/>index:数据删除位置的索引值。 |
71| onDataChanged(index:&nbsp;number):&nbsp;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