1# LazyForEach:数据懒加载 2 3 4LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。 5 6 7## 接口描述 8 9 10```ts 11LazyForEach( 12 dataSource: IDataSource, // 需要进行数据迭代的数据源 13 itemGenerator: (item: any, index: number) => void, // 子组件生成函数 14 keyGenerator?: (item: any, index: number) => string // 键值生成函数 15): void 16``` 17 18**参数:** 19 20 21| 参数名 | 参数类型 | 必填 | 参数描述 | 22| ------------- | ------------------------------------------------------ | ---- | ------------------------------------------------------------ | 23| dataSource | [IDataSource](#idatasource类型说明) | 是 | LazyForEach数据源,需要开发者实现相关接口。 | 24| itemGenerator | (item: any, index:number) => void | 是 | 子组件生成函数,为数组中的每一个数据项创建一个子组件。<br/>**说明:**<br/>item是当前数据项,index是数据项索引值。<br/>itemGenerator的函数体必须使用大括号{...}。itemGenerator每次迭代只能并且必须生成一个子组件。itemGenerator中可以使用if语句,但是必须保证if语句每个分支都会创建一个相同类型的子组件。itemGenerator中不允许使用ForEach和LazyForEach语句。 | 25| keyGenerator | (item: any, index:number) => string | 否 | 键值生成函数,用于给数据源中的每一个数据项生成唯一且固定的键值。当数据项在数组中的位置更改时,其键值不得更改,当数组中的数据项被新项替换时,被替换项的键值和新项的键值必须不同。键值生成器的功能是可选的,但是,为了使开发框架能够更好地识别数组更改,提高性能,建议提供。如将数组反向时,如果没有提供键值生成器,则LazyForEach中的所有节点都将重建。<br/>**说明:**<br/>item是当前数据项,index是数据项索引值。<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 | - | 通知组件重新加载所有数据。<br/>键值没有变化的数据项会使用原先的子组件,键值发生变化的会重建子组件。 | 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/>将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/>将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/apis-arkui/arkui-ts/ts-container-list.md)、[Grid](../reference/apis-arkui/arkui-ts/ts-container-grid.md)、[Swiper](../reference/apis-arkui/arkui-ts/ts-container-swiper.md)以及[WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md)组件支持数据懒加载(可配置cachedCount属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。 77- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件。 78- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。 79- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。 80- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。 81- LazyForEach必须使用DataChangeListener对象来进行更新,第一个参数dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。 82- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。 83 84## 键值生成规则 85 86在`LazyForEach`循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。 87 88`LazyForEach`提供了一个名为`keyGenerator`的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义`keyGenerator`函数,则ArkUI框架会使用默认的键值生成函数,即`(item: any, index: number) => { return viewId + '-' + index.toString(); }`, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。 89 90## 组件创建规则 91 92在确定键值生成规则后,LazyForEach的第二个参数`itemGenerator`函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:[LazyForEach首次渲染](#首次渲染)和[LazyForEach非首次渲染](#非首次渲染)。 93 94### 首次渲染 95 96- ### 生成不同键值 97 98在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。 99 100```ts 101// Basic implementation of IDataSource to handle data listener 102class BasicDataSource implements IDataSource { 103 private listeners: DataChangeListener[] = []; 104 private originDataArray: string[] = []; 105 106 public totalCount(): number { 107 return 0; 108 } 109 110 public getData(index: number): string { 111 return this.originDataArray[index]; 112 } 113 114 // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 115 registerDataChangeListener(listener: DataChangeListener): void { 116 if (this.listeners.indexOf(listener) < 0) { 117 console.info('add listener'); 118 this.listeners.push(listener); 119 } 120 } 121 122 // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 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 // 通知LazyForEach组件需要重载所有子组件 132 notifyDataReload(): void { 133 this.listeners.forEach(listener => { 134 listener.onDataReloaded(); 135 }) 136 } 137 138 // 通知LazyForEach组件需要在index对应索引处添加子组件 139 notifyDataAdd(index: number): void { 140 this.listeners.forEach(listener => { 141 listener.onDataAdd(index); 142 }) 143 } 144 145 // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 146 notifyDataChange(index: number): void { 147 this.listeners.forEach(listener => { 148 listener.onDataChange(index); 149 }) 150 } 151 152 // 通知LazyForEach组件需要在index对应索引处删除该子组件 153 notifyDataDelete(index: number): void { 154 this.listeners.forEach(listener => { 155 listener.onDataDelete(index); 156 }) 157 } 158 159 // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 160 notifyDataMove(from: number, to: number): void { 161 this.listeners.forEach(listener => { 162 listener.onDataMove(from, to); 163 }) 164 } 165} 166 167class MyDataSource extends BasicDataSource { 168 private dataArray: string[] = []; 169 170 public totalCount(): number { 171 return this.dataArray.length; 172 } 173 174 public getData(index: number): string { 175 return this.dataArray[index]; 176 } 177 178 public addData(index: number, data: string): void { 179 this.dataArray.splice(index, 0, data); 180 this.notifyDataAdd(index); 181 } 182 183 public pushData(data: string): void { 184 this.dataArray.push(data); 185 this.notifyDataAdd(this.dataArray.length - 1); 186 } 187} 188 189@Entry 190@Component 191struct MyComponent { 192 private data: MyDataSource = new MyDataSource(); 193 194 aboutToAppear() { 195 for (let i = 0; i <= 20; i++) { 196 this.data.pushData(`Hello ${i}`) 197 } 198 } 199 200 build() { 201 List({ space: 3 }) { 202 LazyForEach(this.data, (item: string) => { 203 ListItem() { 204 Row() { 205 Text(item).fontSize(50) 206 .onAppear(() => { 207 console.info("appear:" + item) 208 }) 209 }.margin({ left: 10, right: 10 }) 210 } 211 }, (item: string) => item) 212 }.cachedCount(5) 213 } 214} 215``` 216 217在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在`LazyForEach`循环渲染时,其为数据源数组项依次生成键值`Hello 0`、`Hello 1` ... `Hello 20`,并创建对应的`ListItem`子组件渲染到界面上。 218 219运行效果如下图所示。 220 221**图1** LazyForEach正常首次渲染 222 223 224- ### 键值相同时错误渲染 225 226当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,`LazyForEach`渲染的数据项键值均相同,在滑动过程中,`LazyForEach`会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。 227 228 ```ts 229class BasicDataSource implements IDataSource { 230 private listeners: DataChangeListener[] = []; 231 private originDataArray: string[] = []; 232 233 public totalCount(): number { 234 return 0; 235 } 236 237 public getData(index: number): string { 238 return this.originDataArray[index]; 239 } 240 241 registerDataChangeListener(listener: DataChangeListener): void { 242 if (this.listeners.indexOf(listener) < 0) { 243 console.info('add listener'); 244 this.listeners.push(listener); 245 } 246 } 247 248 unregisterDataChangeListener(listener: DataChangeListener): void { 249 const pos = this.listeners.indexOf(listener); 250 if (pos >= 0) { 251 console.info('remove listener'); 252 this.listeners.splice(pos, 1); 253 } 254 } 255 256 notifyDataReload(): void { 257 this.listeners.forEach(listener => { 258 listener.onDataReloaded(); 259 }) 260 } 261 262 notifyDataAdd(index: number): void { 263 this.listeners.forEach(listener => { 264 listener.onDataAdd(index); 265 }) 266 } 267 268 notifyDataChange(index: number): void { 269 this.listeners.forEach(listener => { 270 listener.onDataChange(index); 271 }) 272 } 273 274 notifyDataDelete(index: number): void { 275 this.listeners.forEach(listener => { 276 listener.onDataDelete(index); 277 }) 278 } 279 280 notifyDataMove(from: number, to: number): void { 281 this.listeners.forEach(listener => { 282 listener.onDataMove(from, to); 283 }) 284 } 285} 286 287class MyDataSource extends BasicDataSource { 288 private dataArray: string[] = []; 289 290 public totalCount(): number { 291 return this.dataArray.length; 292 } 293 294 public getData(index: number): string { 295 return this.dataArray[index]; 296 } 297 298 public addData(index: number, data: string): void { 299 this.dataArray.splice(index, 0, data); 300 this.notifyDataAdd(index); 301 } 302 303 public pushData(data: string): void { 304 this.dataArray.push(data); 305 this.notifyDataAdd(this.dataArray.length - 1); 306 } 307} 308 309@Entry 310@Component 311struct MyComponent { 312 private data: MyDataSource = new MyDataSource(); 313 314 aboutToAppear() { 315 for (let i = 0; i <= 20; i++) { 316 this.data.pushData(`Hello ${i}`) 317 } 318 } 319 320 build() { 321 List({ space: 3 }) { 322 LazyForEach(this.data, (item: string) => { 323 ListItem() { 324 Row() { 325 Text(item).fontSize(50) 326 .onAppear(() => { 327 console.info("appear:" + item) 328 }) 329 }.margin({ left: 10, right: 10 }) 330 } 331 }, (item: string) => 'same key') 332 }.cachedCount(5) 333 } 334} 335 ``` 336 337运行效果如下图所示。可以看到`Hello 0`在滑动过程中被错误渲染为`Hello 13`。 338 339**图2** LazyForEach存在相同键值 340 341 342### 非首次渲染 343 344当`LazyForEach`数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用`listener`对应的接口,通知`LazyForEach`做相应的更新,各使用场景如下。 345 346- ### 添加数据 347 348```ts 349class BasicDataSource implements IDataSource { 350 private listeners: DataChangeListener[] = []; 351 private originDataArray: string[] = []; 352 353 public totalCount(): number { 354 return 0; 355 } 356 357 public getData(index: number): string { 358 return this.originDataArray[index]; 359 } 360 361 registerDataChangeListener(listener: DataChangeListener): void { 362 if (this.listeners.indexOf(listener) < 0) { 363 console.info('add listener'); 364 this.listeners.push(listener); 365 } 366 } 367 368 unregisterDataChangeListener(listener: DataChangeListener): void { 369 const pos = this.listeners.indexOf(listener); 370 if (pos >= 0) { 371 console.info('remove listener'); 372 this.listeners.splice(pos, 1); 373 } 374 } 375 376 notifyDataReload(): void { 377 this.listeners.forEach(listener => { 378 listener.onDataReloaded(); 379 }) 380 } 381 382 notifyDataAdd(index: number): void { 383 this.listeners.forEach(listener => { 384 listener.onDataAdd(index); 385 }) 386 } 387 388 notifyDataChange(index: number): void { 389 this.listeners.forEach(listener => { 390 listener.onDataChange(index); 391 }) 392 } 393 394 notifyDataDelete(index: number): void { 395 this.listeners.forEach(listener => { 396 listener.onDataDelete(index); 397 }) 398 } 399 400 notifyDataMove(from: number, to: number): void { 401 this.listeners.forEach(listener => { 402 listener.onDataMove(from, to); 403 }) 404 } 405} 406 407class MyDataSource extends BasicDataSource { 408 private dataArray: string[] = []; 409 410 public totalCount(): number { 411 return this.dataArray.length; 412 } 413 414 public getData(index: number): string { 415 return this.dataArray[index]; 416 } 417 418 public addData(index: number, data: string): void { 419 this.dataArray.splice(index, 0, data); 420 this.notifyDataAdd(index); 421 } 422 423 public pushData(data: string): void { 424 this.dataArray.push(data); 425 this.notifyDataAdd(this.dataArray.length - 1); 426 } 427} 428 429@Entry 430@Component 431struct MyComponent { 432 private data: MyDataSource = new MyDataSource(); 433 434 aboutToAppear() { 435 for (let i = 0; i <= 20; i++) { 436 this.data.pushData(`Hello ${i}`) 437 } 438 } 439 440 build() { 441 List({ space: 3 }) { 442 LazyForEach(this.data, (item: string) => { 443 ListItem() { 444 Row() { 445 Text(item).fontSize(50) 446 .onAppear(() => { 447 console.info("appear:" + item) 448 }) 449 }.margin({ left: 10, right: 10 }) 450 } 451 .onClick(() => { 452 // 点击追加子组件 453 this.data.pushData(`Hello ${this.data.totalCount()}`); 454 }) 455 }, (item: string) => item) 456 }.cachedCount(5) 457 } 458} 459``` 460 461当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`pushData`方法,该方法会在数据源末尾添加数据并调用`notifyDataAdd`方法。在`notifyDataAdd`方法内会又调用`listener.onDataAdd`方法,该方法会通知`LazyForEach`在该处有数据添加,`LazyForEach`便会在该索引处新建子组件。 462 463运行效果如下图所示。 464 465**图3** LazyForEach添加数据 466 467 468- ### 删除数据 469 470```ts 471class BasicDataSource implements IDataSource { 472 private listeners: DataChangeListener[] = []; 473 private originDataArray: string[] = []; 474 475 public totalCount(): number { 476 return 0; 477 } 478 479 public getData(index: number): string { 480 return this.originDataArray[index]; 481 } 482 483 registerDataChangeListener(listener: DataChangeListener): void { 484 if (this.listeners.indexOf(listener) < 0) { 485 console.info('add listener'); 486 this.listeners.push(listener); 487 } 488 } 489 490 unregisterDataChangeListener(listener: DataChangeListener): void { 491 const pos = this.listeners.indexOf(listener); 492 if (pos >= 0) { 493 console.info('remove listener'); 494 this.listeners.splice(pos, 1); 495 } 496 } 497 498 notifyDataReload(): void { 499 this.listeners.forEach(listener => { 500 listener.onDataReloaded(); 501 }) 502 } 503 504 notifyDataAdd(index: number): void { 505 this.listeners.forEach(listener => { 506 listener.onDataAdd(index); 507 }) 508 } 509 510 notifyDataChange(index: number): void { 511 this.listeners.forEach(listener => { 512 listener.onDataChange(index); 513 }) 514 } 515 516 notifyDataDelete(index: number): void { 517 this.listeners.forEach(listener => { 518 listener.onDataDelete(index); 519 }) 520 } 521 522 notifyDataMove(from: number, to: number): void { 523 this.listeners.forEach(listener => { 524 listener.onDataMove(from, to); 525 }) 526 } 527} 528 529class MyDataSource extends BasicDataSource { 530 dataArray: string[] = []; 531 532 public totalCount(): number { 533 return this.dataArray.length; 534 } 535 536 public getData(index: number): string { 537 return this.dataArray[index]; 538 } 539 540 public addData(index: number, data: string): void { 541 this.dataArray.splice(index, 0, data); 542 this.notifyDataAdd(index); 543 } 544 545 public pushData(data: string): void { 546 this.dataArray.push(data); 547 this.notifyDataAdd(this.dataArray.length - 1); 548 } 549 550 public deleteData(index: number): void { 551 this.dataArray.splice(index, 1); 552 this.notifyDataDelete(index); 553 } 554} 555 556@Entry 557@Component 558struct MyComponent { 559 private data: MyDataSource = new MyDataSource(); 560 561 aboutToAppear() { 562 for (let i = 0; i <= 20; i++) { 563 this.data.pushData(`Hello ${i}`) 564 } 565 } 566 567 build() { 568 List({ space: 3 }) { 569 LazyForEach(this.data, (item: string, index: number) => { 570 ListItem() { 571 Row() { 572 Text(item).fontSize(50) 573 .onAppear(() => { 574 console.info("appear:" + item) 575 }) 576 }.margin({ left: 10, right: 10 }) 577 } 578 .onClick(() => { 579 // 点击删除子组件 580 this.data.deleteData(this.data.dataArray.indexOf(item)); 581 }) 582 }, (item: string) => item) 583 }.cachedCount(5) 584 } 585} 586``` 587 588当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`deleteData`方法,该方法会删除数据源对应索引处的数据并调用`notifyDataDelete`方法。在`notifyDataDelete`方法内会又调用`listener.onDataDelete`方法,该方法会通知`LazyForEach`在该处有数据删除,`LazyForEach`便会在该索引处删除对应子组件。 589 590运行效果如下图所示。 591 592**图4** LazyForEach删除数据 593 594 595- ### 交换数据 596 597```ts 598class BasicDataSource implements IDataSource { 599 private listeners: DataChangeListener[] = []; 600 private originDataArray: string[] = []; 601 602 public totalCount(): number { 603 return 0; 604 } 605 606 public getData(index: number): string { 607 return this.originDataArray[index]; 608 } 609 610 registerDataChangeListener(listener: DataChangeListener): void { 611 if (this.listeners.indexOf(listener) < 0) { 612 console.info('add listener'); 613 this.listeners.push(listener); 614 } 615 } 616 617 unregisterDataChangeListener(listener: DataChangeListener): void { 618 const pos = this.listeners.indexOf(listener); 619 if (pos >= 0) { 620 console.info('remove listener'); 621 this.listeners.splice(pos, 1); 622 } 623 } 624 625 notifyDataReload(): void { 626 this.listeners.forEach(listener => { 627 listener.onDataReloaded(); 628 }) 629 } 630 631 notifyDataAdd(index: number): void { 632 this.listeners.forEach(listener => { 633 listener.onDataAdd(index); 634 }) 635 } 636 637 notifyDataChange(index: number): void { 638 this.listeners.forEach(listener => { 639 listener.onDataChange(index); 640 }) 641 } 642 643 notifyDataDelete(index: number): void { 644 this.listeners.forEach(listener => { 645 listener.onDataDelete(index); 646 }) 647 } 648 649 notifyDataMove(from: number, to: number): void { 650 this.listeners.forEach(listener => { 651 listener.onDataMove(from, to); 652 }) 653 } 654} 655 656class MyDataSource extends BasicDataSource { 657 dataArray: string[] = []; 658 659 public totalCount(): number { 660 return this.dataArray.length; 661 } 662 663 public getData(index: number): string { 664 return this.dataArray[index]; 665 } 666 667 public addData(index: number, data: string): void { 668 this.dataArray.splice(index, 0, data); 669 this.notifyDataAdd(index); 670 } 671 672 public pushData(data: string): void { 673 this.dataArray.push(data); 674 this.notifyDataAdd(this.dataArray.length - 1); 675 } 676 677 public deleteData(index: number): void { 678 this.dataArray.splice(index, 1); 679 this.notifyDataDelete(index); 680 } 681 682 public moveData(from: number, to: number): void { 683 let temp: string = this.dataArray[from]; 684 this.dataArray[from] = this.dataArray[to]; 685 this.dataArray[to] = temp; 686 this.notifyDataMove(from, to); 687 } 688} 689 690@Entry 691@Component 692struct MyComponent { 693 private moved: number[] = []; 694 private data: MyDataSource = new MyDataSource(); 695 696 aboutToAppear() { 697 for (let i = 0; i <= 20; i++) { 698 this.data.pushData(`Hello ${i}`) 699 } 700 } 701 702 build() { 703 List({ space: 3 }) { 704 LazyForEach(this.data, (item: string, index: number) => { 705 ListItem() { 706 Row() { 707 Text(item).fontSize(50) 708 .onAppear(() => { 709 console.info("appear:" + item) 710 }) 711 }.margin({ left: 10, right: 10 }) 712 } 713 .onClick(() => { 714 this.moved.push(this.data.dataArray.indexOf(item)); 715 if (this.moved.length === 2) { 716 // 点击移动子组件 717 this.data.moveData(this.moved[0], this.moved[1]); 718 this.moved = []; 719 } 720 }) 721 }, (item: string) => item) 722 }.cachedCount(5) 723 } 724} 725``` 726 727当我们首次点击`LazyForEach`的子组件时,在moved成员变量内存入要移动的数据索引,再次点击`LazyForEach`另一个子组件时,我们将首次点击的子组件移到此处。调用数据源`data`的`moveData`方法,该方法会将数据源对应数据移动到预期的位置并调用`notifyDatMove`方法。在`notifyDataMove`方法内会又调用`listener.onDataMove`方法,该方法通知`LazyForEach`在该处有数据需要移动,`LazyForEach`便会将`from`和`to`索引处的子组件进行位置调换。 728 729运行效果如下图所示。 730 731**图5** LazyForEach交换数据 732 733 734- ### 改变单个数据 735 736```ts 737class BasicDataSource implements IDataSource { 738 private listeners: DataChangeListener[] = []; 739 private originDataArray: string[] = []; 740 741 public totalCount(): number { 742 return 0; 743 } 744 745 public getData(index: number): string { 746 return this.originDataArray[index]; 747 } 748 749 registerDataChangeListener(listener: DataChangeListener): void { 750 if (this.listeners.indexOf(listener) < 0) { 751 console.info('add listener'); 752 this.listeners.push(listener); 753 } 754 } 755 756 unregisterDataChangeListener(listener: DataChangeListener): void { 757 const pos = this.listeners.indexOf(listener); 758 if (pos >= 0) { 759 console.info('remove listener'); 760 this.listeners.splice(pos, 1); 761 } 762 } 763 764 notifyDataReload(): void { 765 this.listeners.forEach(listener => { 766 listener.onDataReloaded(); 767 }) 768 } 769 770 notifyDataAdd(index: number): void { 771 this.listeners.forEach(listener => { 772 listener.onDataAdd(index); 773 }) 774 } 775 776 notifyDataChange(index: number): void { 777 this.listeners.forEach(listener => { 778 listener.onDataChange(index); 779 }) 780 } 781 782 notifyDataDelete(index: number): void { 783 this.listeners.forEach(listener => { 784 listener.onDataDelete(index); 785 }) 786 } 787 788 notifyDataMove(from: number, to: number): void { 789 this.listeners.forEach(listener => { 790 listener.onDataMove(from, to); 791 }) 792 } 793} 794 795class MyDataSource extends BasicDataSource { 796 private dataArray: string[] = []; 797 798 public totalCount(): number { 799 return this.dataArray.length; 800 } 801 802 public getData(index: number): string { 803 return this.dataArray[index]; 804 } 805 806 public addData(index: number, data: string): void { 807 this.dataArray.splice(index, 0, data); 808 this.notifyDataAdd(index); 809 } 810 811 public pushData(data: string): void { 812 this.dataArray.push(data); 813 this.notifyDataAdd(this.dataArray.length - 1); 814 } 815 816 public deleteData(index: number): void { 817 this.dataArray.splice(index, 1); 818 this.notifyDataDelete(index); 819 } 820 821 public changeData(index: number, data: string): void { 822 this.dataArray.splice(index, 1, data); 823 this.notifyDataChange(index); 824 } 825} 826 827@Entry 828@Component 829struct MyComponent { 830 private moved: number[] = []; 831 private data: MyDataSource = new MyDataSource(); 832 833 aboutToAppear() { 834 for (let i = 0; i <= 20; i++) { 835 this.data.pushData(`Hello ${i}`) 836 } 837 } 838 839 840 build() { 841 List({ space: 3 }) { 842 LazyForEach(this.data, (item: string, index: number) => { 843 ListItem() { 844 Row() { 845 Text(item).fontSize(50) 846 .onAppear(() => { 847 console.info("appear:" + item) 848 }) 849 }.margin({ left: 10, right: 10 }) 850 } 851 .onClick(() => { 852 this.data.changeData(index, item + '00'); 853 }) 854 }, (item: string) => item) 855 }.cachedCount(5) 856 } 857} 858``` 859 860当我们点击`LazyForEach`的子组件时,首先改变当前数据,然后调用数据源`data`的`changeData`方法,在该方法内会调用`notifyDataChange`方法。在`notifyDataChange`方法内会又调用`listener.onDataChange`方法,该方法通知`LazyForEach`组件该处有数据发生变化,`LazyForEach`便会在对应索引处重建子组件。 861 862运行效果如下图所示。 863 864**图6** LazyForEach改变单个数据 865 866 867- ### 改变多个数据 868 869```ts 870class BasicDataSource implements IDataSource { 871 private listeners: DataChangeListener[] = []; 872 private originDataArray: string[] = []; 873 874 public totalCount(): number { 875 return 0; 876 } 877 878 public getData(index: number): string { 879 return this.originDataArray[index]; 880 } 881 882 registerDataChangeListener(listener: DataChangeListener): void { 883 if (this.listeners.indexOf(listener) < 0) { 884 console.info('add listener'); 885 this.listeners.push(listener); 886 } 887 } 888 889 unregisterDataChangeListener(listener: DataChangeListener): void { 890 const pos = this.listeners.indexOf(listener); 891 if (pos >= 0) { 892 console.info('remove listener'); 893 this.listeners.splice(pos, 1); 894 } 895 } 896 897 notifyDataReload(): void { 898 this.listeners.forEach(listener => { 899 listener.onDataReloaded(); 900 }) 901 } 902 903 notifyDataAdd(index: number): void { 904 this.listeners.forEach(listener => { 905 listener.onDataAdd(index); 906 }) 907 } 908 909 notifyDataChange(index: number): void { 910 this.listeners.forEach(listener => { 911 listener.onDataChange(index); 912 }) 913 } 914 915 notifyDataDelete(index: number): void { 916 this.listeners.forEach(listener => { 917 listener.onDataDelete(index); 918 }) 919 } 920 921 notifyDataMove(from: number, to: number): void { 922 this.listeners.forEach(listener => { 923 listener.onDataMove(from, to); 924 }) 925 } 926} 927 928class MyDataSource extends BasicDataSource { 929 private dataArray: string[] = []; 930 931 public totalCount(): number { 932 return this.dataArray.length; 933 } 934 935 public getData(index: number): string { 936 return this.dataArray[index]; 937 } 938 939 public addData(index: number, data: string): void { 940 this.dataArray.splice(index, 0, data); 941 this.notifyDataAdd(index); 942 } 943 944 public pushData(data: string): void { 945 this.dataArray.push(data); 946 this.notifyDataAdd(this.dataArray.length - 1); 947 } 948 949 public deleteData(index: number): void { 950 this.dataArray.splice(index, 1); 951 this.notifyDataDelete(index); 952 } 953 954 public changeData(index: number): void { 955 this.notifyDataChange(index); 956 } 957 958 public reloadData(): void { 959 this.notifyDataReload(); 960 } 961 962 public modifyAllData(): void { 963 this.dataArray = this.dataArray.map((item: string) => { 964 return item + '0'; 965 }) 966 } 967} 968 969@Entry 970@Component 971struct MyComponent { 972 private moved: number[] = []; 973 private data: MyDataSource = new MyDataSource(); 974 975 aboutToAppear() { 976 for (let i = 0; i <= 20; i++) { 977 this.data.pushData(`Hello ${i}`) 978 } 979 } 980 981 build() { 982 List({ space: 3 }) { 983 LazyForEach(this.data, (item: string, index: number) => { 984 ListItem() { 985 Row() { 986 Text(item).fontSize(50) 987 .onAppear(() => { 988 console.info("appear:" + item) 989 }) 990 }.margin({ left: 10, right: 10 }) 991 } 992 .onClick(() => { 993 this.data.modifyAllData(); 994 this.data.reloadData(); 995 }) 996 }, (item: string) => item) 997 }.cachedCount(5) 998 } 999} 1000``` 1001 1002当我们点击`LazyForEach`的子组件时,首先调用`data`的`modifyAllData`方法改变了数据源中的所有数据,然后调用数据源的`reloadData`方法,在该方法内会调用`notifyDataReload`方法。在`notifyDataReload`方法内会又调用`listener.onDataReloaded`方法,通知`LazyForEach`需要重建所有子节点。`LazyForEach`会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。 1003 1004运行效果如下图所示。 1005 1006**图7** LazyForEach改变多个数据 1007 1008 1009- ### 改变数据子属性 1010 1011若仅靠`LazyForEach`的刷新机制,当`item`变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了`@Observed`与@`ObjectLink`机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。 1012 1013```ts 1014class BasicDataSource implements IDataSource { 1015 private listeners: DataChangeListener[] = []; 1016 private originDataArray: StringData[] = []; 1017 1018 public totalCount(): number { 1019 return 0; 1020 } 1021 1022 public getData(index: number): StringData { 1023 return this.originDataArray[index]; 1024 } 1025 1026 registerDataChangeListener(listener: DataChangeListener): void { 1027 if (this.listeners.indexOf(listener) < 0) { 1028 console.info('add listener'); 1029 this.listeners.push(listener); 1030 } 1031 } 1032 1033 unregisterDataChangeListener(listener: DataChangeListener): void { 1034 const pos = this.listeners.indexOf(listener); 1035 if (pos >= 0) { 1036 console.info('remove listener'); 1037 this.listeners.splice(pos, 1); 1038 } 1039 } 1040 1041 notifyDataReload(): void { 1042 this.listeners.forEach(listener => { 1043 listener.onDataReloaded(); 1044 }) 1045 } 1046 1047 notifyDataAdd(index: number): void { 1048 this.listeners.forEach(listener => { 1049 listener.onDataAdd(index); 1050 }) 1051 } 1052 1053 notifyDataChange(index: number): void { 1054 this.listeners.forEach(listener => { 1055 listener.onDataChange(index); 1056 }) 1057 } 1058 1059 notifyDataDelete(index: number): void { 1060 this.listeners.forEach(listener => { 1061 listener.onDataDelete(index); 1062 }) 1063 } 1064 1065 notifyDataMove(from: number, to: number): void { 1066 this.listeners.forEach(listener => { 1067 listener.onDataMove(from, to); 1068 }) 1069 } 1070} 1071 1072class MyDataSource extends BasicDataSource { 1073 private dataArray: StringData[] = []; 1074 1075 public totalCount(): number { 1076 return this.dataArray.length; 1077 } 1078 1079 public getData(index: number): StringData { 1080 return this.dataArray[index]; 1081 } 1082 1083 public addData(index: number, data: StringData): void { 1084 this.dataArray.splice(index, 0, data); 1085 this.notifyDataAdd(index); 1086 } 1087 1088 public pushData(data: StringData): void { 1089 this.dataArray.push(data); 1090 this.notifyDataAdd(this.dataArray.length - 1); 1091 } 1092} 1093 1094@Observed 1095class StringData { 1096 message: string; 1097 constructor(message: string) { 1098 this.message = message; 1099 } 1100} 1101 1102@Entry 1103@Component 1104struct MyComponent { 1105 private moved: number[] = []; 1106 @State data: MyDataSource = new MyDataSource(); 1107 1108 aboutToAppear() { 1109 for (let i = 0; i <= 20; i++) { 1110 this.data.pushData(new StringData(`Hello ${i}`)); 1111 } 1112 } 1113 1114 build() { 1115 List({ space: 3 }) { 1116 LazyForEach(this.data, (item: StringData, index: number) => { 1117 ListItem() { 1118 ChildComponent({data: item}) 1119 } 1120 .onClick(() => { 1121 item.message += '0'; 1122 }) 1123 }, (item: StringData, index: number) => index.toString()) 1124 }.cachedCount(5) 1125 } 1126} 1127 1128@Component 1129struct ChildComponent { 1130 @ObjectLink data: StringData 1131 build() { 1132 Row() { 1133 Text(this.data.message).fontSize(50) 1134 .onAppear(() => { 1135 console.info("appear:" + this.data.message) 1136 }) 1137 }.margin({ left: 10, right: 10 }) 1138 } 1139} 1140``` 1141 1142此时点击`LazyForEach`子组件改变`item.message`时,重渲染依赖的是`ChildComponent`的`@ObjectLink`成员变量对其子属性的监听,此时框架只会刷新`Text(this.data.message)`,不会去重建整个`ListItem`子组件。 1143 1144**图8** LazyForEach改变数据子属性 1145 1146 1147## 常见使用问题 1148 1149- ### 渲染结果非预期 1150 1151 ```ts 1152 class BasicDataSource implements IDataSource { 1153 private listeners: DataChangeListener[] = []; 1154 private originDataArray: string[] = []; 1155 1156 public totalCount(): number { 1157 return 0; 1158 } 1159 1160 public getData(index: number): string { 1161 return this.originDataArray[index]; 1162 } 1163 1164 registerDataChangeListener(listener: DataChangeListener): void { 1165 if (this.listeners.indexOf(listener) < 0) { 1166 console.info('add listener'); 1167 this.listeners.push(listener); 1168 } 1169 } 1170 1171 unregisterDataChangeListener(listener: DataChangeListener): void { 1172 const pos = this.listeners.indexOf(listener); 1173 if (pos >= 0) { 1174 console.info('remove listener'); 1175 this.listeners.splice(pos, 1); 1176 } 1177 } 1178 1179 notifyDataReload(): void { 1180 this.listeners.forEach(listener => { 1181 listener.onDataReloaded(); 1182 }) 1183 } 1184 1185 notifyDataAdd(index: number): void { 1186 this.listeners.forEach(listener => { 1187 listener.onDataAdd(index); 1188 }) 1189 } 1190 1191 notifyDataChange(index: number): void { 1192 this.listeners.forEach(listener => { 1193 listener.onDataChange(index); 1194 }) 1195 } 1196 1197 notifyDataDelete(index: number): void { 1198 this.listeners.forEach(listener => { 1199 listener.onDataDelete(index); 1200 }) 1201 } 1202 1203 notifyDataMove(from: number, to: number): void { 1204 this.listeners.forEach(listener => { 1205 listener.onDataMove(from, to); 1206 }) 1207 } 1208 } 1209 1210 class MyDataSource extends BasicDataSource { 1211 private dataArray: string[] = []; 1212 1213 public totalCount(): number { 1214 return this.dataArray.length; 1215 } 1216 1217 public getData(index: number): string { 1218 return this.dataArray[index]; 1219 } 1220 1221 public addData(index: number, data: string): void { 1222 this.dataArray.splice(index, 0, data); 1223 this.notifyDataAdd(index); 1224 } 1225 1226 public pushData(data: string): void { 1227 this.dataArray.push(data); 1228 this.notifyDataAdd(this.dataArray.length - 1); 1229 } 1230 1231 public deleteData(index: number): void { 1232 this.dataArray.splice(index, 1); 1233 this.notifyDataDelete(index); 1234 } 1235 } 1236 1237 @Entry 1238 @Component 1239 struct MyComponent { 1240 private data: MyDataSource = new MyDataSource(); 1241 1242 aboutToAppear() { 1243 for (let i = 0; i <= 20; i++) { 1244 this.data.pushData(`Hello ${i}`) 1245 } 1246 } 1247 1248 build() { 1249 List({ space: 3 }) { 1250 LazyForEach(this.data, (item: string, index: number) => { 1251 ListItem() { 1252 Row() { 1253 Text(item).fontSize(50) 1254 .onAppear(() => { 1255 console.info("appear:" + item) 1256 }) 1257 }.margin({ left: 10, right: 10 }) 1258 } 1259 .onClick(() => { 1260 // 点击删除子组件 1261 this.data.deleteData(index); 1262 }) 1263 }, (item: string) => item) 1264 }.cachedCount(5) 1265 } 1266 } 1267 ``` 1268 1269 **图9** LazyForEach删除数据非预期 1270  1271 1272 当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其`index`均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的`index`,其`itemGenerator`中的`index`并没有发生变化,所以删除结果和预期不符。 1273 1274 修复代码如下所示。 1275 1276 ```ts 1277 class BasicDataSource implements IDataSource { 1278 private listeners: DataChangeListener[] = []; 1279 private originDataArray: string[] = []; 1280 1281 public totalCount(): number { 1282 return 0; 1283 } 1284 1285 public getData(index: number): string { 1286 return this.originDataArray[index]; 1287 } 1288 1289 registerDataChangeListener(listener: DataChangeListener): void { 1290 if (this.listeners.indexOf(listener) < 0) { 1291 console.info('add listener'); 1292 this.listeners.push(listener); 1293 } 1294 } 1295 1296 unregisterDataChangeListener(listener: DataChangeListener): void { 1297 const pos = this.listeners.indexOf(listener); 1298 if (pos >= 0) { 1299 console.info('remove listener'); 1300 this.listeners.splice(pos, 1); 1301 } 1302 } 1303 1304 notifyDataReload(): void { 1305 this.listeners.forEach(listener => { 1306 listener.onDataReloaded(); 1307 }) 1308 } 1309 1310 notifyDataAdd(index: number): void { 1311 this.listeners.forEach(listener => { 1312 listener.onDataAdd(index); 1313 }) 1314 } 1315 1316 notifyDataChange(index: number): void { 1317 this.listeners.forEach(listener => { 1318 listener.onDataChange(index); 1319 }) 1320 } 1321 1322 notifyDataDelete(index: number): void { 1323 this.listeners.forEach(listener => { 1324 listener.onDataDelete(index); 1325 }) 1326 } 1327 1328 notifyDataMove(from: number, to: number): void { 1329 this.listeners.forEach(listener => { 1330 listener.onDataMove(from, to); 1331 }) 1332 } 1333 } 1334 1335 class MyDataSource extends BasicDataSource { 1336 private dataArray: string[] = []; 1337 1338 public totalCount(): number { 1339 return this.dataArray.length; 1340 } 1341 1342 public getData(index: number): string { 1343 return this.dataArray[index]; 1344 } 1345 1346 public addData(index: number, data: string): void { 1347 this.dataArray.splice(index, 0, data); 1348 this.notifyDataAdd(index); 1349 } 1350 1351 public pushData(data: string): void { 1352 this.dataArray.push(data); 1353 this.notifyDataAdd(this.dataArray.length - 1); 1354 } 1355 1356 public deleteData(index: number): void { 1357 this.dataArray.splice(index, 1); 1358 this.notifyDataDelete(index); 1359 } 1360 1361 public reloadData(): void { 1362 this.notifyDataReload(); 1363 } 1364 } 1365 1366 @Entry 1367 @Component 1368 struct MyComponent { 1369 private data: MyDataSource = new MyDataSource(); 1370 1371 aboutToAppear() { 1372 for (let i = 0; i <= 20; i++) { 1373 this.data.pushData(`Hello ${i}`) 1374 } 1375 } 1376 1377 build() { 1378 List({ space: 3 }) { 1379 LazyForEach(this.data, (item: string, index: number) => { 1380 ListItem() { 1381 Row() { 1382 Text(item).fontSize(50) 1383 .onAppear(() => { 1384 console.info("appear:" + item) 1385 }) 1386 }.margin({ left: 10, right: 10 }) 1387 } 1388 .onClick(() => { 1389 // 点击删除子组件 1390 this.data.deleteData(index); 1391 // 重置所有子组件的index索引 1392 this.data.reloadData(); 1393 }) 1394 }, (item: string, index: number) => item + index.toString()) 1395 }.cachedCount(5) 1396 } 1397 } 1398 ``` 1399 1400 在删除一个数据项后调用`reloadData`方法,重建后面的数据项,以达到更新`index`索引的目的。 1401 1402 **图10** 修复LazyForEach删除数据非预期 1403  1404 1405- ### 重渲染时图片闪烁 1406 1407 ```ts 1408 class BasicDataSource implements IDataSource { 1409 private listeners: DataChangeListener[] = []; 1410 private originDataArray: StringData[] = []; 1411 1412 public totalCount(): number { 1413 return 0; 1414 } 1415 1416 public getData(index: number): StringData { 1417 return this.originDataArray[index]; 1418 } 1419 1420 registerDataChangeListener(listener: DataChangeListener): void { 1421 if (this.listeners.indexOf(listener) < 0) { 1422 console.info('add listener'); 1423 this.listeners.push(listener); 1424 } 1425 } 1426 1427 unregisterDataChangeListener(listener: DataChangeListener): void { 1428 const pos = this.listeners.indexOf(listener); 1429 if (pos >= 0) { 1430 console.info('remove listener'); 1431 this.listeners.splice(pos, 1); 1432 } 1433 } 1434 1435 notifyDataReload(): void { 1436 this.listeners.forEach(listener => { 1437 listener.onDataReloaded(); 1438 }) 1439 } 1440 1441 notifyDataAdd(index: number): void { 1442 this.listeners.forEach(listener => { 1443 listener.onDataAdd(index); 1444 }) 1445 } 1446 1447 notifyDataChange(index: number): void { 1448 this.listeners.forEach(listener => { 1449 listener.onDataChange(index); 1450 }) 1451 } 1452 1453 notifyDataDelete(index: number): void { 1454 this.listeners.forEach(listener => { 1455 listener.onDataDelete(index); 1456 }) 1457 } 1458 1459 notifyDataMove(from: number, to: number): void { 1460 this.listeners.forEach(listener => { 1461 listener.onDataMove(from, to); 1462 }) 1463 } 1464 } 1465 1466 class MyDataSource extends BasicDataSource { 1467 private dataArray: StringData[] = []; 1468 1469 public totalCount(): number { 1470 return this.dataArray.length; 1471 } 1472 1473 public getData(index: number): StringData { 1474 return this.dataArray[index]; 1475 } 1476 1477 public addData(index: number, data: StringData): void { 1478 this.dataArray.splice(index, 0, data); 1479 this.notifyDataAdd(index); 1480 } 1481 1482 public pushData(data: StringData): void { 1483 this.dataArray.push(data); 1484 this.notifyDataAdd(this.dataArray.length - 1); 1485 } 1486 1487 public reloadData(): void { 1488 this.notifyDataReload(); 1489 } 1490 } 1491 1492 class StringData { 1493 message: string; 1494 imgSrc: Resource; 1495 constructor(message: string, imgSrc: Resource) { 1496 this.message = message; 1497 this.imgSrc = imgSrc; 1498 } 1499 } 1500 1501 @Entry 1502 @Component 1503 struct MyComponent { 1504 private moved: number[] = []; 1505 private data: MyDataSource = new MyDataSource(); 1506 1507 aboutToAppear() { 1508 for (let i = 0; i <= 20; i++) { 1509 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1510 } 1511 } 1512 1513 build() { 1514 List({ space: 3 }) { 1515 LazyForEach(this.data, (item: StringData, index: number) => { 1516 ListItem() { 1517 Column() { 1518 Text(item.message).fontSize(50) 1519 .onAppear(() => { 1520 console.info("appear:" + item.message) 1521 }) 1522 Image(item.imgSrc) 1523 .width(500) 1524 .height(200) 1525 }.margin({ left: 10, right: 10 }) 1526 } 1527 .onClick(() => { 1528 item.message += '00'; 1529 this.data.reloadData(); 1530 }) 1531 }, (item: StringData, index: number) => JSON.stringify(item)) 1532 }.cachedCount(5) 1533 } 1534 } 1535 ``` 1536 1537 **图11** LazyForEach仅改变文字但是图片闪烁问题 1538  1539 1540 在我们点击`ListItem`子组件时,我们只改变了数据项的`message`属性,但是`LazyForEach`的刷新机制会导致整个`ListItem`被重建。由于`Image`组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用`@ObjectLink`和`@Observed`去单独刷新使用了`item.message`的`Text`组件。 1541 1542 修复代码如下所示。 1543 1544 ```ts 1545 class BasicDataSource implements IDataSource { 1546 private listeners: DataChangeListener[] = []; 1547 private originDataArray: StringData[] = []; 1548 1549 public totalCount(): number { 1550 return 0; 1551 } 1552 1553 public getData(index: number): StringData { 1554 return this.originDataArray[index]; 1555 } 1556 1557 registerDataChangeListener(listener: DataChangeListener): void { 1558 if (this.listeners.indexOf(listener) < 0) { 1559 console.info('add listener'); 1560 this.listeners.push(listener); 1561 } 1562 } 1563 1564 unregisterDataChangeListener(listener: DataChangeListener): void { 1565 const pos = this.listeners.indexOf(listener); 1566 if (pos >= 0) { 1567 console.info('remove listener'); 1568 this.listeners.splice(pos, 1); 1569 } 1570 } 1571 1572 notifyDataReload(): void { 1573 this.listeners.forEach(listener => { 1574 listener.onDataReloaded(); 1575 }) 1576 } 1577 1578 notifyDataAdd(index: number): void { 1579 this.listeners.forEach(listener => { 1580 listener.onDataAdd(index); 1581 }) 1582 } 1583 1584 notifyDataChange(index: number): void { 1585 this.listeners.forEach(listener => { 1586 listener.onDataChange(index); 1587 }) 1588 } 1589 1590 notifyDataDelete(index: number): void { 1591 this.listeners.forEach(listener => { 1592 listener.onDataDelete(index); 1593 }) 1594 } 1595 1596 notifyDataMove(from: number, to: number): void { 1597 this.listeners.forEach(listener => { 1598 listener.onDataMove(from, to); 1599 }) 1600 } 1601 } 1602 1603 class MyDataSource extends BasicDataSource { 1604 private dataArray: StringData[] = []; 1605 1606 public totalCount(): number { 1607 return this.dataArray.length; 1608 } 1609 1610 public getData(index: number): StringData { 1611 return this.dataArray[index]; 1612 } 1613 1614 public addData(index: number, data: StringData): void { 1615 this.dataArray.splice(index, 0, data); 1616 this.notifyDataAdd(index); 1617 } 1618 1619 public pushData(data: StringData): void { 1620 this.dataArray.push(data); 1621 this.notifyDataAdd(this.dataArray.length - 1); 1622 } 1623 } 1624 1625 @Observed 1626 class StringData { 1627 message: string; 1628 imgSrc: Resource; 1629 constructor(message: string, imgSrc: Resource) { 1630 this.message = message; 1631 this.imgSrc = imgSrc; 1632 } 1633 } 1634 1635 @Entry 1636 @Component 1637 struct MyComponent { 1638 @State data: MyDataSource = new MyDataSource(); 1639 1640 aboutToAppear() { 1641 for (let i = 0; i <= 20; i++) { 1642 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1643 } 1644 } 1645 1646 build() { 1647 List({ space: 3 }) { 1648 LazyForEach(this.data, (item: StringData, index: number) => { 1649 ListItem() { 1650 ChildComponent({data: item}) 1651 } 1652 .onClick(() => { 1653 item.message += '0'; 1654 }) 1655 }, (item: StringData, index: number) => index.toString()) 1656 }.cachedCount(5) 1657 } 1658 } 1659 1660 @Component 1661 struct ChildComponent { 1662 @ObjectLink data: StringData 1663 build() { 1664 Column() { 1665 Text(this.data.message).fontSize(50) 1666 .onAppear(() => { 1667 console.info("appear:" + this.data.message) 1668 }) 1669 Image(this.data.imgSrc) 1670 .width(500) 1671 .height(200) 1672 }.margin({ left: 10, right: 10 }) 1673 } 1674 } 1675 ``` 1676 1677 **图12** 修复LazyForEach仅改变文字但是图片闪烁问题 1678  1679 1680- ### @ObjectLink属性变化UI未更新 1681 1682 ```ts 1683 class BasicDataSource implements IDataSource { 1684 private listeners: DataChangeListener[] = []; 1685 private originDataArray: StringData[] = []; 1686 1687 public totalCount(): number { 1688 return 0; 1689 } 1690 1691 public getData(index: number): StringData { 1692 return this.originDataArray[index]; 1693 } 1694 1695 registerDataChangeListener(listener: DataChangeListener): void { 1696 if (this.listeners.indexOf(listener) < 0) { 1697 console.info('add listener'); 1698 this.listeners.push(listener); 1699 } 1700 } 1701 1702 unregisterDataChangeListener(listener: DataChangeListener): void { 1703 const pos = this.listeners.indexOf(listener); 1704 if (pos >= 0) { 1705 console.info('remove listener'); 1706 this.listeners.splice(pos, 1); 1707 } 1708 } 1709 1710 notifyDataReload(): void { 1711 this.listeners.forEach(listener => { 1712 listener.onDataReloaded(); 1713 }) 1714 } 1715 1716 notifyDataAdd(index: number): void { 1717 this.listeners.forEach(listener => { 1718 listener.onDataAdd(index); 1719 }) 1720 } 1721 1722 notifyDataChange(index: number): void { 1723 this.listeners.forEach(listener => { 1724 listener.onDataChange(index); 1725 }) 1726 } 1727 1728 notifyDataDelete(index: number): void { 1729 this.listeners.forEach(listener => { 1730 listener.onDataDelete(index); 1731 }) 1732 } 1733 1734 notifyDataMove(from: number, to: number): void { 1735 this.listeners.forEach(listener => { 1736 listener.onDataMove(from, to); 1737 }) 1738 } 1739 } 1740 1741 class MyDataSource extends BasicDataSource { 1742 private dataArray: StringData[] = []; 1743 1744 public totalCount(): number { 1745 return this.dataArray.length; 1746 } 1747 1748 public getData(index: number): StringData { 1749 return this.dataArray[index]; 1750 } 1751 1752 public addData(index: number, data: StringData): void { 1753 this.dataArray.splice(index, 0, data); 1754 this.notifyDataAdd(index); 1755 } 1756 1757 public pushData(data: StringData): void { 1758 this.dataArray.push(data); 1759 this.notifyDataAdd(this.dataArray.length - 1); 1760 } 1761 } 1762 1763 @Observed 1764 class StringData { 1765 message: NestedString; 1766 constructor(message: NestedString) { 1767 this.message = message; 1768 } 1769 } 1770 1771 @Observed 1772 class NestedString { 1773 message: string; 1774 constructor(message: string) { 1775 this.message = message; 1776 } 1777 } 1778 1779 @Entry 1780 @Component 1781 struct MyComponent { 1782 private moved: number[] = []; 1783 @State data: MyDataSource = new MyDataSource(); 1784 1785 aboutToAppear() { 1786 for (let i = 0; i <= 20; i++) { 1787 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 1788 } 1789 } 1790 1791 build() { 1792 List({ space: 3 }) { 1793 LazyForEach(this.data, (item: StringData, index: number) => { 1794 ListItem() { 1795 ChildComponent({data: item}) 1796 } 1797 .onClick(() => { 1798 item.message.message += '0'; 1799 }) 1800 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 1801 }.cachedCount(5) 1802 } 1803 } 1804 1805 @Component 1806 struct ChildComponent { 1807 @ObjectLink data: StringData 1808 build() { 1809 Row() { 1810 Text(this.data.message.message).fontSize(50) 1811 .onAppear(() => { 1812 console.info("appear:" + this.data.message.message) 1813 }) 1814 }.margin({ left: 10, right: 10 }) 1815 } 1816 } 1817 ``` 1818 1819 **图13** ObjectLink属性变化后UI未更新 1820  1821 1822 @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体[请查看@ObjectLink与@Observed的详细使用方法和限制条件](./arkts-observed-and-objectlink.md)。 1823 1824 修复代码如下所示。 1825 1826 ```ts 1827 class BasicDataSource implements IDataSource { 1828 private listeners: DataChangeListener[] = []; 1829 private originDataArray: StringData[] = []; 1830 1831 public totalCount(): number { 1832 return 0; 1833 } 1834 1835 public getData(index: number): StringData { 1836 return this.originDataArray[index]; 1837 } 1838 1839 registerDataChangeListener(listener: DataChangeListener): void { 1840 if (this.listeners.indexOf(listener) < 0) { 1841 console.info('add listener'); 1842 this.listeners.push(listener); 1843 } 1844 } 1845 1846 unregisterDataChangeListener(listener: DataChangeListener): void { 1847 const pos = this.listeners.indexOf(listener); 1848 if (pos >= 0) { 1849 console.info('remove listener'); 1850 this.listeners.splice(pos, 1); 1851 } 1852 } 1853 1854 notifyDataReload(): void { 1855 this.listeners.forEach(listener => { 1856 listener.onDataReloaded(); 1857 }) 1858 } 1859 1860 notifyDataAdd(index: number): void { 1861 this.listeners.forEach(listener => { 1862 listener.onDataAdd(index); 1863 }) 1864 } 1865 1866 notifyDataChange(index: number): void { 1867 this.listeners.forEach(listener => { 1868 listener.onDataChange(index); 1869 }) 1870 } 1871 1872 notifyDataDelete(index: number): void { 1873 this.listeners.forEach(listener => { 1874 listener.onDataDelete(index); 1875 }) 1876 } 1877 1878 notifyDataMove(from: number, to: number): void { 1879 this.listeners.forEach(listener => { 1880 listener.onDataMove(from, to); 1881 }) 1882 } 1883 } 1884 1885 class MyDataSource extends BasicDataSource { 1886 private dataArray: StringData[] = []; 1887 1888 public totalCount(): number { 1889 return this.dataArray.length; 1890 } 1891 1892 public getData(index: number): StringData { 1893 return this.dataArray[index]; 1894 } 1895 1896 public addData(index: number, data: StringData): void { 1897 this.dataArray.splice(index, 0, data); 1898 this.notifyDataAdd(index); 1899 } 1900 1901 public pushData(data: StringData): void { 1902 this.dataArray.push(data); 1903 this.notifyDataAdd(this.dataArray.length - 1); 1904 } 1905 } 1906 1907 @Observed 1908 class StringData { 1909 message: NestedString; 1910 constructor(message: NestedString) { 1911 this.message = message; 1912 } 1913 } 1914 1915 @Observed 1916 class NestedString { 1917 message: string; 1918 constructor(message: string) { 1919 this.message = message; 1920 } 1921 } 1922 1923 @Entry 1924 @Component 1925 struct MyComponent { 1926 private moved: number[] = []; 1927 @State data: MyDataSource = new MyDataSource(); 1928 1929 aboutToAppear() { 1930 for (let i = 0; i <= 20; i++) { 1931 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 1932 } 1933 } 1934 1935 build() { 1936 List({ space: 3 }) { 1937 LazyForEach(this.data, (item: StringData, index: number) => { 1938 ListItem() { 1939 ChildComponent({data: item}) 1940 } 1941 .onClick(() => { 1942 item.message = new NestedString(item.message.message + '0'); 1943 }) 1944 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 1945 }.cachedCount(5) 1946 } 1947 } 1948 1949 @Component 1950 struct ChildComponent { 1951 @ObjectLink data: StringData 1952 build() { 1953 Row() { 1954 Text(this.data.message.message).fontSize(50) 1955 .onAppear(() => { 1956 console.info("appear:" + this.data.message.message) 1957 }) 1958 }.margin({ left: 10, right: 10 }) 1959 } 1960 } 1961 ``` 1962 1963 **图14** 修复ObjectLink属性变化后UI更新 1964  1965