1# LazyForEach:数据懒加载 2 3API参数说明见:[LazyForEach API参数说明](../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md) 4 5LazyForEach从提供的数据源中按需迭代数据,并在每次迭代过程中创建相应的组件。当在滚动容器中使用了LazyForEach,框架会根据滚动容器可视区域按需创建组件,当组件滑出可视区域外时,框架会进行组件销毁回收以降低内存占用。 6 7## 使用限制 8 9- 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属性,即只加载可视部分以及其前后少量数据用于缓冲),其他组件仍然是一次性加载所有的数据。 10- 容器组件內使用LazyForEach的时候,只能包含一个LazyForEach。以List为例,同时包含ListItem、ForEach、LazyForEach的情形是不推荐的;同时包含多个LazyForEach也是不推荐的。 11- LazyForEach在每次迭代中,必须创建且只允许创建一个子组件;即LazyForEach的子组件生成函数有且只有一个根组件。 12- 生成的子组件必须是允许包含在LazyForEach父容器组件中的子组件。 13- 允许LazyForEach包含在if/else条件渲染语句中,也允许LazyForEach中出现if/else条件渲染语句。 14- 键值生成器必须针对每个数据生成唯一的值,如果键值相同,将导致键值相同的UI组件渲染出现问题。 15- LazyForEach必须使用DataChangeListener对象进行更新,对第一个参数dataSource重新赋值会异常;dataSource使用状态变量时,状态变量改变不会触发LazyForEach的UI刷新。 16- 为了高性能渲染,通过DataChangeListener对象的onDataChange方法来更新UI时,需要生成不同于原来的键值来触发组件刷新。 17- LazyForEach必须和[@Reusable](https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-component-reuse-V5#section5601835174020)装饰器一起使用才能触发节点复用。使用方法:将@Reusable装饰在LazyForEach列表的组件上,见[使用规则](https://developer.huawei.com/consumer/cn/doc/best-practices-V5/bpta-component-reuse-V5#section5923195311402)。 18 19## 键值生成规则 20 21在`LazyForEach`循环渲染过程中,系统会为每个item生成一个唯一且持久的键值,用于标识对应的组件。当这个键值变化时,ArkUI框架将视为该数组元素已被替换或修改,并会基于新的键值创建一个新的组件。 22 23`LazyForEach`提供了一个名为`keyGenerator`的参数,这是一个函数,开发者可以通过它自定义键值的生成规则。如果开发者没有定义`keyGenerator`函数,则ArkUI框架会使用默认的键值生成函数,即`(item: Object, index: number) => { return viewId + '-' + index.toString(); }`, viewId在编译器转换过程中生成,同一个LazyForEach组件内其viewId是一致的。 24 25## 组件创建规则 26 27在确定键值生成规则后,LazyForEach的第二个参数`itemGenerator`函数会根据键值生成规则为数据源的每个数组项创建组件。组件的创建包括两种情况:[LazyForEach首次渲染](#首次渲染)和[LazyForEach非首次渲染](#非首次渲染)。 28 29### 首次渲染 30 31#### 生成不同键值 32 33在LazyForEach首次渲染时,会根据上述键值生成规则为数据源的每个数组项生成唯一键值,并创建相应的组件。 34 35```ts 36// Basic implementation of IDataSource to handle data listener 37class BasicDataSource implements IDataSource { 38 private listeners: DataChangeListener[] = []; 39 private originDataArray: string[] = []; 40 41 public totalCount(): number { 42 return 0; 43 } 44 45 public getData(index: number): string { 46 return this.originDataArray[index]; 47 } 48 49 // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 50 registerDataChangeListener(listener: DataChangeListener): void { 51 if (this.listeners.indexOf(listener) < 0) { 52 console.info('add listener'); 53 this.listeners.push(listener); 54 } 55 } 56 57 // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 58 unregisterDataChangeListener(listener: DataChangeListener): void { 59 const pos = this.listeners.indexOf(listener); 60 if (pos >= 0) { 61 console.info('remove listener'); 62 this.listeners.splice(pos, 1); 63 } 64 } 65 66 // 通知LazyForEach组件需要重载所有子组件 67 notifyDataReload(): void { 68 this.listeners.forEach(listener => { 69 listener.onDataReloaded(); 70 }) 71 } 72 73 // 通知LazyForEach组件需要在index对应索引处添加子组件 74 notifyDataAdd(index: number): void { 75 this.listeners.forEach(listener => { 76 listener.onDataAdd(index); 77 }) 78 } 79 80 // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 81 notifyDataChange(index: number): void { 82 this.listeners.forEach(listener => { 83 listener.onDataChange(index); 84 }) 85 } 86 87 // 通知LazyForEach组件需要在index对应索引处删除该子组件 88 notifyDataDelete(index: number): void { 89 this.listeners.forEach(listener => { 90 listener.onDataDelete(index); 91 }) 92 } 93 94 // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 95 notifyDataMove(from: number, to: number): void { 96 this.listeners.forEach(listener => { 97 listener.onDataMove(from, to); 98 }) 99 } 100} 101 102class MyDataSource extends BasicDataSource { 103 private dataArray: string[] = []; 104 105 public totalCount(): number { 106 return this.dataArray.length; 107 } 108 109 public getData(index: number): string { 110 return this.dataArray[index]; 111 } 112 113 public addData(index: number, data: string): void { 114 this.dataArray.splice(index, 0, data); 115 this.notifyDataAdd(index); 116 } 117 118 public pushData(data: string): void { 119 this.dataArray.push(data); 120 this.notifyDataAdd(this.dataArray.length - 1); 121 } 122} 123 124@Entry 125@Component 126struct MyComponent { 127 private data: MyDataSource = new MyDataSource(); 128 129 aboutToAppear() { 130 for (let i = 0; i <= 20; i++) { 131 this.data.pushData(`Hello ${i}`) 132 } 133 } 134 135 build() { 136 List({ space: 3 }) { 137 LazyForEach(this.data, (item: string) => { 138 ListItem() { 139 Row() { 140 Text(item).fontSize(50) 141 .onAppear(() => { 142 console.info("appear:" + item) 143 }) 144 }.margin({ left: 10, right: 10 }) 145 } 146 }, (item: string) => item) 147 }.cachedCount(5) 148 } 149} 150``` 151 152在上述代码中,键值生成规则是`keyGenerator`函数的返回值`item`。在`LazyForEach`循环渲染时,其为数据源数组项依次生成键值`Hello 0`、`Hello 1` ... `Hello 20`,并创建对应的`ListItem`子组件渲染到界面上。 153 154运行效果如下图所示。 155 156**图1** LazyForEach正常首次渲染 157 158 159#### 键值相同时错误渲染 160 161当不同数据项生成的键值相同时,框架的行为是不可预测的。例如,在以下代码中,`LazyForEach`渲染的数据项键值均相同,在滑动过程中,`LazyForEach`会对划入划出当前页面的子组件进行预加载,而新建的子组件和销毁的原子组件具有相同的键值,框架可能存在取用缓存错误的情况,导致子组件渲染有问题。 162 163 ```ts 164class BasicDataSource implements IDataSource { 165 private listeners: DataChangeListener[] = []; 166 private originDataArray: string[] = []; 167 168 public totalCount(): number { 169 return 0; 170 } 171 172 public getData(index: number): string { 173 return this.originDataArray[index]; 174 } 175 176 registerDataChangeListener(listener: DataChangeListener): void { 177 if (this.listeners.indexOf(listener) < 0) { 178 console.info('add listener'); 179 this.listeners.push(listener); 180 } 181 } 182 183 unregisterDataChangeListener(listener: DataChangeListener): void { 184 const pos = this.listeners.indexOf(listener); 185 if (pos >= 0) { 186 console.info('remove listener'); 187 this.listeners.splice(pos, 1); 188 } 189 } 190 191 notifyDataReload(): void { 192 this.listeners.forEach(listener => { 193 listener.onDataReloaded(); 194 }) 195 } 196 197 notifyDataAdd(index: number): void { 198 this.listeners.forEach(listener => { 199 listener.onDataAdd(index); 200 }) 201 } 202 203 notifyDataChange(index: number): void { 204 this.listeners.forEach(listener => { 205 listener.onDataChange(index); 206 }) 207 } 208 209 notifyDataDelete(index: number): void { 210 this.listeners.forEach(listener => { 211 listener.onDataDelete(index); 212 }) 213 } 214 215 notifyDataMove(from: number, to: number): void { 216 this.listeners.forEach(listener => { 217 listener.onDataMove(from, to); 218 }) 219 } 220} 221 222class MyDataSource extends BasicDataSource { 223 private dataArray: string[] = []; 224 225 public totalCount(): number { 226 return this.dataArray.length; 227 } 228 229 public getData(index: number): string { 230 return this.dataArray[index]; 231 } 232 233 public addData(index: number, data: string): void { 234 this.dataArray.splice(index, 0, data); 235 this.notifyDataAdd(index); 236 } 237 238 public pushData(data: string): void { 239 this.dataArray.push(data); 240 this.notifyDataAdd(this.dataArray.length - 1); 241 } 242} 243 244@Entry 245@Component 246struct MyComponent { 247 private data: MyDataSource = new MyDataSource(); 248 249 aboutToAppear() { 250 for (let i = 0; i <= 20; i++) { 251 this.data.pushData(`Hello ${i}`) 252 } 253 } 254 255 build() { 256 List({ space: 3 }) { 257 LazyForEach(this.data, (item: string) => { 258 ListItem() { 259 Row() { 260 Text(item).fontSize(50) 261 .onAppear(() => { 262 console.info("appear:" + item) 263 }) 264 }.margin({ left: 10, right: 10 }) 265 } 266 }, (item: string) => 'same key') 267 }.cachedCount(5) 268 } 269} 270 ``` 271 272运行效果如下图所示。 273 274**图2** LazyForEach存在相同键值 275 276 277### 非首次渲染 278 279当`LazyForEach`数据源发生变化,需要再次渲染时,开发者应根据数据源的变化情况调用`listener`对应的接口,通知`LazyForEach`做相应的更新,各使用场景如下。 280 281#### 添加数据 282 283```ts 284class BasicDataSource implements IDataSource { 285 private listeners: DataChangeListener[] = []; 286 private originDataArray: string[] = []; 287 288 public totalCount(): number { 289 return 0; 290 } 291 292 public getData(index: number): string { 293 return this.originDataArray[index]; 294 } 295 296 registerDataChangeListener(listener: DataChangeListener): void { 297 if (this.listeners.indexOf(listener) < 0) { 298 console.info('add listener'); 299 this.listeners.push(listener); 300 } 301 } 302 303 unregisterDataChangeListener(listener: DataChangeListener): void { 304 const pos = this.listeners.indexOf(listener); 305 if (pos >= 0) { 306 console.info('remove listener'); 307 this.listeners.splice(pos, 1); 308 } 309 } 310 311 notifyDataReload(): void { 312 this.listeners.forEach(listener => { 313 listener.onDataReloaded(); 314 }) 315 } 316 317 notifyDataAdd(index: number): void { 318 this.listeners.forEach(listener => { 319 listener.onDataAdd(index); 320 // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); 321 }) 322 } 323 324 notifyDataChange(index: number): void { 325 this.listeners.forEach(listener => { 326 listener.onDataChange(index); 327 }) 328 } 329 330 notifyDataDelete(index: number): void { 331 this.listeners.forEach(listener => { 332 listener.onDataDelete(index); 333 }) 334 } 335 336 notifyDataMove(from: number, to: number): void { 337 this.listeners.forEach(listener => { 338 listener.onDataMove(from, to); 339 }) 340 } 341} 342 343class MyDataSource extends BasicDataSource { 344 private dataArray: string[] = []; 345 346 public totalCount(): number { 347 return this.dataArray.length; 348 } 349 350 public getData(index: number): string { 351 return this.dataArray[index]; 352 } 353 354 public addData(index: number, data: string): void { 355 this.dataArray.splice(index, 0, data); 356 this.notifyDataAdd(index); 357 } 358 359 public pushData(data: string): void { 360 this.dataArray.push(data); 361 this.notifyDataAdd(this.dataArray.length - 1); 362 } 363} 364 365@Entry 366@Component 367struct MyComponent { 368 private data: MyDataSource = new MyDataSource(); 369 370 aboutToAppear() { 371 for (let i = 0; i <= 20; i++) { 372 this.data.pushData(`Hello ${i}`) 373 } 374 } 375 376 build() { 377 List({ space: 3 }) { 378 LazyForEach(this.data, (item: string) => { 379 ListItem() { 380 Row() { 381 Text(item).fontSize(50) 382 .onAppear(() => { 383 console.info("appear:" + item) 384 }) 385 }.margin({ left: 10, right: 10 }) 386 } 387 .onClick(() => { 388 // 点击追加子组件 389 this.data.pushData(`Hello ${this.data.totalCount()}`); 390 }) 391 }, (item: string) => item) 392 }.cachedCount(5) 393 } 394} 395``` 396 397当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`pushData`方法,该方法会在数据源末尾添加数据并调用`notifyDataAdd`方法。在`notifyDataAdd`方法内会又调用`listener.onDataAdd`方法,该方法会通知`LazyForEach`在该处有数据添加,`LazyForEach`便会在该索引处新建子组件。 398 399运行效果如下图所示。 400 401**图3** LazyForEach添加数据 402 403 404#### 删除数据 405 406```ts 407class BasicDataSource implements IDataSource { 408 private listeners: DataChangeListener[] = []; 409 private originDataArray: string[] = []; 410 411 public totalCount(): number { 412 return 0; 413 } 414 415 public getData(index: number): string { 416 return this.originDataArray[index]; 417 } 418 419 registerDataChangeListener(listener: DataChangeListener): void { 420 if (this.listeners.indexOf(listener) < 0) { 421 console.info('add listener'); 422 this.listeners.push(listener); 423 } 424 } 425 426 unregisterDataChangeListener(listener: DataChangeListener): void { 427 const pos = this.listeners.indexOf(listener); 428 if (pos >= 0) { 429 console.info('remove listener'); 430 this.listeners.splice(pos, 1); 431 } 432 } 433 434 notifyDataReload(): void { 435 this.listeners.forEach(listener => { 436 listener.onDataReloaded(); 437 }) 438 } 439 440 notifyDataAdd(index: number): void { 441 this.listeners.forEach(listener => { 442 listener.onDataAdd(index); 443 }) 444 } 445 446 notifyDataChange(index: number): void { 447 this.listeners.forEach(listener => { 448 listener.onDataChange(index); 449 }) 450 } 451 452 notifyDataDelete(index: number): void { 453 this.listeners.forEach(listener => { 454 listener.onDataDelete(index); 455 // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); 456 }) 457 } 458 459 notifyDataMove(from: number, to: number): void { 460 this.listeners.forEach(listener => { 461 listener.onDataMove(from, to); 462 }) 463 } 464} 465 466class MyDataSource extends BasicDataSource { 467 dataArray: string[] = []; 468 469 public totalCount(): number { 470 return this.dataArray.length; 471 } 472 473 public getData(index: number): string { 474 return this.dataArray[index]; 475 } 476 477 public addData(index: number, data: string): void { 478 this.dataArray.splice(index, 0, data); 479 this.notifyDataAdd(index); 480 } 481 482 public pushData(data: string): void { 483 this.dataArray.push(data); 484 } 485 486 public deleteData(index: number): void { 487 this.dataArray.splice(index, 1); 488 this.notifyDataDelete(index); 489 } 490} 491 492@Entry 493@Component 494struct MyComponent { 495 private data: MyDataSource = new MyDataSource(); 496 497 aboutToAppear() { 498 for (let i = 0; i <= 20; i++) { 499 this.data.pushData(`Hello ${i}`) 500 } 501 } 502 503 build() { 504 List({ space: 3 }) { 505 LazyForEach(this.data, (item: string, index: number) => { 506 ListItem() { 507 Row() { 508 Text(item).fontSize(50) 509 .onAppear(() => { 510 console.info("appear:" + item) 511 }) 512 }.margin({ left: 10, right: 10 }) 513 } 514 .onClick(() => { 515 // 点击删除子组件 516 this.data.deleteData(this.data.dataArray.indexOf(item)); 517 }) 518 }, (item: string) => item) 519 }.cachedCount(5) 520 } 521} 522``` 523 524当我们点击`LazyForEach`的子组件时,首先调用数据源`data`的`deleteData`方法,该方法会删除数据源对应索引处的数据并调用`notifyDataDelete`方法。在`notifyDataDelete`方法内会又调用`listener.onDataDelete`方法,该方法会通知`LazyForEach`在该处有数据删除,`LazyForEach`便会在该索引处删除对应子组件。 525 526运行效果如下图所示。 527 528**图4** LazyForEach删除数据 529 530 531#### 交换数据 532 533```ts 534class BasicDataSource implements IDataSource { 535 private listeners: DataChangeListener[] = []; 536 private originDataArray: string[] = []; 537 538 public totalCount(): number { 539 return 0; 540 } 541 542 public getData(index: number): string { 543 return this.originDataArray[index]; 544 } 545 546 registerDataChangeListener(listener: DataChangeListener): void { 547 if (this.listeners.indexOf(listener) < 0) { 548 console.info('add listener'); 549 this.listeners.push(listener); 550 } 551 } 552 553 unregisterDataChangeListener(listener: DataChangeListener): void { 554 const pos = this.listeners.indexOf(listener); 555 if (pos >= 0) { 556 console.info('remove listener'); 557 this.listeners.splice(pos, 1); 558 } 559 } 560 561 notifyDataReload(): void { 562 this.listeners.forEach(listener => { 563 listener.onDataReloaded(); 564 }) 565 } 566 567 notifyDataAdd(index: number): void { 568 this.listeners.forEach(listener => { 569 listener.onDataAdd(index); 570 }) 571 } 572 573 notifyDataChange(index: number): void { 574 this.listeners.forEach(listener => { 575 listener.onDataChange(index); 576 }) 577 } 578 579 notifyDataDelete(index: number): void { 580 this.listeners.forEach(listener => { 581 listener.onDataDelete(index); 582 }) 583 } 584 585 notifyDataMove(from: number, to: number): void { 586 this.listeners.forEach(listener => { 587 listener.onDataMove(from, to); 588 // 写法2:listener.onDatasetChange( 589 // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); 590 }) 591 } 592} 593 594class MyDataSource extends BasicDataSource { 595 dataArray: string[] = []; 596 597 public totalCount(): number { 598 return this.dataArray.length; 599 } 600 601 public getData(index: number): string { 602 return this.dataArray[index]; 603 } 604 605 public addData(index: number, data: string): void { 606 this.dataArray.splice(index, 0, data); 607 this.notifyDataAdd(index); 608 } 609 610 public pushData(data: string): void { 611 this.dataArray.push(data); 612 } 613 614 public deleteData(index: number): void { 615 this.dataArray.splice(index, 1); 616 this.notifyDataDelete(index); 617 } 618 619 public moveData(from: number, to: number): void { 620 let temp: string = this.dataArray[from]; 621 this.dataArray[from] = this.dataArray[to]; 622 this.dataArray[to] = temp; 623 this.notifyDataMove(from, to); 624 } 625} 626 627@Entry 628@Component 629struct MyComponent { 630 private moved: number[] = []; 631 private data: MyDataSource = new MyDataSource(); 632 633 aboutToAppear() { 634 for (let i = 0; i <= 20; i++) { 635 this.data.pushData(`Hello ${i}`) 636 } 637 } 638 639 build() { 640 List({ space: 3 }) { 641 LazyForEach(this.data, (item: string, index: number) => { 642 ListItem() { 643 Row() { 644 Text(item).fontSize(50) 645 .onAppear(() => { 646 console.info("appear:" + item) 647 }) 648 }.margin({ left: 10, right: 10 }) 649 } 650 .onClick(() => { 651 this.moved.push(this.data.dataArray.indexOf(item)); 652 if (this.moved.length === 2) { 653 // 点击交换子组件 654 this.data.moveData(this.moved[0], this.moved[1]); 655 this.moved = []; 656 } 657 }) 658 }, (item: string) => item) 659 }.cachedCount(5) 660 } 661} 662``` 663 664当我们首次点击`LazyForEach`的子组件时,在moved成员变量内存入要移动的数据索引,再次点击`LazyForEach`另一个子组件时,我们将首次点击的子组件移到此处。调用数据源`data`的`moveData`方法,该方法会将数据源对应数据移动到预期的位置并调用`notifyDataMove`方法。在`notifyDataMove`方法内会又调用`listener.onDataMove`方法,该方法通知`LazyForEach`在该处有数据需要移动,`LazyForEach`便会将`from`和`to`索引处的子组件进行位置调换。 665 666运行效果如下图所示。 667 668**图5** LazyForEach交换数据 669 670 671#### 改变单个数据 672 673```ts 674class BasicDataSource implements IDataSource { 675 private listeners: DataChangeListener[] = []; 676 private originDataArray: string[] = []; 677 678 public totalCount(): number { 679 return 0; 680 } 681 682 public getData(index: number): string { 683 return this.originDataArray[index]; 684 } 685 686 registerDataChangeListener(listener: DataChangeListener): void { 687 if (this.listeners.indexOf(listener) < 0) { 688 console.info('add listener'); 689 this.listeners.push(listener); 690 } 691 } 692 693 unregisterDataChangeListener(listener: DataChangeListener): void { 694 const pos = this.listeners.indexOf(listener); 695 if (pos >= 0) { 696 console.info('remove listener'); 697 this.listeners.splice(pos, 1); 698 } 699 } 700 701 notifyDataReload(): void { 702 this.listeners.forEach(listener => { 703 listener.onDataReloaded(); 704 }) 705 } 706 707 notifyDataAdd(index: number): void { 708 this.listeners.forEach(listener => { 709 listener.onDataAdd(index); 710 }) 711 } 712 713 notifyDataChange(index: number): void { 714 this.listeners.forEach(listener => { 715 listener.onDataChange(index); 716 // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); 717 }) 718 } 719 720 notifyDataDelete(index: number): void { 721 this.listeners.forEach(listener => { 722 listener.onDataDelete(index); 723 }) 724 } 725 726 notifyDataMove(from: number, to: number): void { 727 this.listeners.forEach(listener => { 728 listener.onDataMove(from, to); 729 }) 730 } 731} 732 733class MyDataSource extends BasicDataSource { 734 private dataArray: string[] = []; 735 736 public totalCount(): number { 737 return this.dataArray.length; 738 } 739 740 public getData(index: number): string { 741 return this.dataArray[index]; 742 } 743 744 public addData(index: number, data: string): void { 745 this.dataArray.splice(index, 0, data); 746 this.notifyDataAdd(index); 747 } 748 749 public pushData(data: string): void { 750 this.dataArray.push(data); 751 } 752 753 public deleteData(index: number): void { 754 this.dataArray.splice(index, 1); 755 this.notifyDataDelete(index); 756 } 757 758 public changeData(index: number, data: string): void { 759 this.dataArray.splice(index, 1, data); 760 this.notifyDataChange(index); 761 } 762} 763 764@Entry 765@Component 766struct MyComponent { 767 private moved: number[] = []; 768 private data: MyDataSource = new MyDataSource(); 769 770 aboutToAppear() { 771 for (let i = 0; i <= 20; i++) { 772 this.data.pushData(`Hello ${i}`) 773 } 774 } 775 776 777 build() { 778 List({ space: 3 }) { 779 LazyForEach(this.data, (item: string, index: number) => { 780 ListItem() { 781 Row() { 782 Text(item).fontSize(50) 783 .onAppear(() => { 784 console.info("appear:" + item) 785 }) 786 }.margin({ left: 10, right: 10 }) 787 } 788 .onClick(() => { 789 this.data.changeData(index, item + '00'); 790 }) 791 }, (item: string) => item) 792 }.cachedCount(5) 793 } 794} 795``` 796 797当我们点击`LazyForEach`的子组件时,首先改变当前数据,然后调用数据源`data`的`changeData`方法,在该方法内会调用`notifyDataChange`方法。在`notifyDataChange`方法内会又调用`listener.onDataChange`方法,该方法通知`LazyForEach`组件该处有数据发生变化,`LazyForEach`便会在对应索引处重建子组件。 798 799运行效果如下图所示。 800 801**图6** LazyForEach改变单个数据 802 803 804#### 改变多个数据 805 806```ts 807class BasicDataSource implements IDataSource { 808 private listeners: DataChangeListener[] = []; 809 private originDataArray: string[] = []; 810 811 public totalCount(): number { 812 return 0; 813 } 814 815 public getData(index: number): string { 816 return this.originDataArray[index]; 817 } 818 819 registerDataChangeListener(listener: DataChangeListener): void { 820 if (this.listeners.indexOf(listener) < 0) { 821 console.info('add listener'); 822 this.listeners.push(listener); 823 } 824 } 825 826 unregisterDataChangeListener(listener: DataChangeListener): void { 827 const pos = this.listeners.indexOf(listener); 828 if (pos >= 0) { 829 console.info('remove listener'); 830 this.listeners.splice(pos, 1); 831 } 832 } 833 834 notifyDataReload(): void { 835 this.listeners.forEach(listener => { 836 listener.onDataReloaded(); 837 // 写法2:listener.onDatasetChange([{type: DataOperationType.RELOAD}]); 838 }) 839 } 840 841 notifyDataAdd(index: number): void { 842 this.listeners.forEach(listener => { 843 listener.onDataAdd(index); 844 }) 845 } 846 847 notifyDataChange(index: number): void { 848 this.listeners.forEach(listener => { 849 listener.onDataChange(index); 850 }) 851 } 852 853 notifyDataDelete(index: number): void { 854 this.listeners.forEach(listener => { 855 listener.onDataDelete(index); 856 }) 857 } 858 859 notifyDataMove(from: number, to: number): void { 860 this.listeners.forEach(listener => { 861 listener.onDataMove(from, to); 862 }) 863 } 864} 865 866class MyDataSource extends BasicDataSource { 867 private dataArray: string[] = []; 868 869 public totalCount(): number { 870 return this.dataArray.length; 871 } 872 873 public getData(index: number): string { 874 return this.dataArray[index]; 875 } 876 877 public addData(index: number, data: string): void { 878 this.dataArray.splice(index, 0, data); 879 this.notifyDataAdd(index); 880 } 881 882 public pushData(data: string): void { 883 this.dataArray.push(data); 884 } 885 886 public deleteData(index: number): void { 887 this.dataArray.splice(index, 1); 888 this.notifyDataDelete(index); 889 } 890 891 public changeData(index: number): void { 892 this.notifyDataChange(index); 893 } 894 895 public reloadData(): void { 896 this.notifyDataReload(); 897 } 898 899 public modifyAllData(): void { 900 this.dataArray = this.dataArray.map((item: string) => { 901 return item + '0'; 902 }) 903 } 904} 905 906@Entry 907@Component 908struct MyComponent { 909 private moved: number[] = []; 910 private data: MyDataSource = new MyDataSource(); 911 912 aboutToAppear() { 913 for (let i = 0; i <= 20; i++) { 914 this.data.pushData(`Hello ${i}`) 915 } 916 } 917 918 build() { 919 List({ space: 3 }) { 920 LazyForEach(this.data, (item: string, index: number) => { 921 ListItem() { 922 Row() { 923 Text(item).fontSize(50) 924 .onAppear(() => { 925 console.info("appear:" + item) 926 }) 927 }.margin({ left: 10, right: 10 }) 928 } 929 .onClick(() => { 930 this.data.modifyAllData(); 931 this.data.reloadData(); 932 }) 933 }, (item: string) => item) 934 }.cachedCount(5) 935 } 936} 937``` 938 939当我们点击`LazyForEach`的子组件时,首先调用`data`的`modifyAllData`方法改变了数据源中的所有数据,然后调用数据源的`reloadData`方法,在该方法内会调用`notifyDataReload`方法。在`notifyDataReload`方法内会又调用`listener.onDataReloaded`方法,通知`LazyForEach`需要重建所有子节点。`LazyForEach`会将原所有数据项和新所有数据项一一做键值比对,若有相同键值则使用缓存,若键值不同则重新构建。 940 941运行效果如下图所示。 942 943**图7** LazyForEach改变多个数据 944 945 946#### 精准批量修改数据 947 948```ts 949class BasicDataSource implements IDataSource { 950 private listeners: DataChangeListener[] = []; 951 private originDataArray: string[] = []; 952 953 public totalCount(): number { 954 return 0; 955 } 956 957 public getData(index: number): string { 958 return this.originDataArray[index]; 959 } 960 961 registerDataChangeListener(listener: DataChangeListener): void { 962 if (this.listeners.indexOf(listener) < 0) { 963 console.info('add listener'); 964 this.listeners.push(listener); 965 } 966 } 967 968 unregisterDataChangeListener(listener: DataChangeListener): void { 969 const pos = this.listeners.indexOf(listener); 970 if (pos >= 0) { 971 console.info('remove listener'); 972 this.listeners.splice(pos, 1); 973 } 974 } 975 976 notifyDatasetChange(operations: DataOperation[]): void { 977 this.listeners.forEach(listener => { 978 listener.onDatasetChange(operations); 979 }) 980 } 981} 982 983class MyDataSource extends BasicDataSource { 984 private dataArray: string[] = []; 985 986 public totalCount(): number { 987 return this.dataArray.length; 988 } 989 990 public getData(index: number): string { 991 return this.dataArray[index]; 992 } 993 994 public operateData(): void { 995 console.info(JSON.stringify(this.dataArray)); 996 this.dataArray.splice(4, 0, this.dataArray[1]); 997 this.dataArray.splice(1, 1); 998 let temp = this.dataArray[4]; 999 this.dataArray[4] = this.dataArray[6]; 1000 this.dataArray[6] = temp 1001 this.dataArray.splice(8, 0, 'Hello 1', 'Hello 2'); 1002 this.dataArray.splice(12, 2); 1003 console.info(JSON.stringify(this.dataArray)); 1004 this.notifyDatasetChange([ 1005 { type: DataOperationType.MOVE, index: { from: 1, to: 3 } }, 1006 { type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } }, 1007 { type: DataOperationType.ADD, index: 8, count: 2 }, 1008 { type: DataOperationType.DELETE, index: 10, count: 2 }]); 1009 } 1010 1011 public init(): void { 1012 this.dataArray.splice(0, 0, 'Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h', 1013 'Hello i', 'Hello j', 'Hello k', 'Hello l', 'Hello m', 'Hello n', 'Hello o', 'Hello p', 'Hello q', 'Hello r'); 1014 } 1015} 1016 1017@Entry 1018@Component 1019struct MyComponent { 1020 private data: MyDataSource = new MyDataSource(); 1021 1022 aboutToAppear() { 1023 this.data.init() 1024 } 1025 1026 build() { 1027 Column() { 1028 Text('第二项数据移动到第四项处,第五项数据和第七项数据交换,第九项开始添加数据 "Hello 1" "Hello 2", 第十一项开始删除两个数据') 1029 .fontSize(10) 1030 .backgroundColor(Color.Blue) 1031 .fontColor(Color.White) 1032 .borderRadius(50) 1033 .padding(5) 1034 .onClick(() => { 1035 this.data.operateData(); 1036 }) 1037 List({ space: 3 }) { 1038 LazyForEach(this.data, (item: string, index: number) => { 1039 ListItem() { 1040 Row() { 1041 Text(item).fontSize(35) 1042 .onAppear(() => { 1043 console.info("appear:" + item) 1044 }) 1045 }.margin({ left: 10, right: 10 }) 1046 } 1047 1048 }, (item: string) => item + new Date().getTime()) 1049 }.cachedCount(5) 1050 } 1051 } 1052} 1053``` 1054 1055onDatasetChange接口由开发者一次性通知LazyForEach应该做哪些操作。上述例子展示了LazyForEach同时进行数据添加、删除、移动、交换的操作。 1056 1057**图8** LazyForEach改变多个数据 1058 1059 1060 1061第二个例子,直接给数组赋值,不涉及 splice 操作。operations直接从比较原数组和新数组得到。 1062```ts 1063class BasicDataSource implements IDataSource { 1064 private listeners: DataChangeListener[] = []; 1065 private originDataArray: string[] = []; 1066 1067 public totalCount(): number { 1068 return 0; 1069 } 1070 1071 public getData(index: number): string { 1072 return this.originDataArray[index]; 1073 } 1074 1075 registerDataChangeListener(listener: DataChangeListener): void { 1076 if (this.listeners.indexOf(listener) < 0) { 1077 console.info('add listener'); 1078 this.listeners.push(listener); 1079 } 1080 } 1081 1082 unregisterDataChangeListener(listener: DataChangeListener): void { 1083 const pos = this.listeners.indexOf(listener); 1084 if (pos >= 0) { 1085 console.info('remove listener'); 1086 this.listeners.splice(pos, 1); 1087 } 1088 } 1089 1090 notifyDatasetChange(operations: DataOperation[]): void { 1091 this.listeners.forEach(listener => { 1092 listener.onDatasetChange(operations); 1093 }) 1094 } 1095} 1096 1097class MyDataSource extends BasicDataSource { 1098 private dataArray: string[] = []; 1099 1100 public totalCount(): number { 1101 return this.dataArray.length; 1102 } 1103 1104 public getData(index: number): string { 1105 return this.dataArray[index]; 1106 } 1107 1108 public operateData(): void { 1109 this.dataArray = 1110 ['Hello x', 'Hello 1', 'Hello 2', 'Hello b', 'Hello c', 'Hello e', 'Hello d', 'Hello f', 'Hello g', 'Hello h'] 1111 this.notifyDatasetChange([ 1112 { type: DataOperationType.CHANGE, index: 0 }, 1113 { type: DataOperationType.ADD, index: 1, count: 2 }, 1114 { type: DataOperationType.EXCHANGE, index: { start: 3, end: 4 } }, 1115 ]); 1116 } 1117 1118 public init(): void { 1119 this.dataArray = ['Hello a', 'Hello b', 'Hello c', 'Hello d', 'Hello e', 'Hello f', 'Hello g', 'Hello h']; 1120 } 1121} 1122 1123@Entry 1124@Component 1125struct MyComponent { 1126 private data: MyDataSource = new MyDataSource(); 1127 1128 aboutToAppear() { 1129 this.data.init() 1130 } 1131 1132 build() { 1133 Column() { 1134 Text('Multi-Data Change') 1135 .fontSize(10) 1136 .backgroundColor(Color.Blue) 1137 .fontColor(Color.White) 1138 .borderRadius(50) 1139 .padding(5) 1140 .onClick(() => { 1141 this.data.operateData(); 1142 }) 1143 List({ space: 3 }) { 1144 LazyForEach(this.data, (item: string, index: number) => { 1145 ListItem() { 1146 Row() { 1147 Text(item).fontSize(35) 1148 .onAppear(() => { 1149 console.info("appear:" + item) 1150 }) 1151 }.margin({ left: 10, right: 10 }) 1152 } 1153 1154 }, (item: string) => item + new Date().getTime()) 1155 }.cachedCount(5) 1156 } 1157 } 1158} 1159``` 1160**图9** LazyForEach改变多个数据 1161 1162 1163 1164使用该接口时有如下注意事项。 1165 11661. onDatasetChange与其它操作数据的接口不能混用。 11672. 传入onDatasetChange的operations,其中每一项operation的index均从修改前的原数组内寻找。因此,operations中的index跟操作Datasource中的index不总是一一对应的,而且不能是负数。 1168第一个例子清楚地显示了这一点: 1169```ts 1170// 修改之前的数组 1171["Hello a","Hello b","Hello c","Hello d","Hello e","Hello f","Hello g","Hello h","Hello i","Hello j","Hello k","Hello l","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"] 1172// 修改之后的数组 1173["Hello a","Hello c","Hello d","Hello b","Hello g","Hello f","Hello e","Hello h","Hello 1","Hello 2","Hello i","Hello j","Hello m","Hello n","Hello o","Hello p","Hello q","Hello r"] 1174``` 1175"Hello b" 从第2项变成第4项,因此第一个 operation 为 `{ type: DataOperationType.MOVE, index: { from: 1, to: 3 } }` 1176"Hello e" 跟 "Hello g" 对调了,而 "Hello e" 在修改前的原数组中的 index=4,"Hello g" 在修改前的原数组中的 index=6, 因此第二个 operation 为 `{ type: DataOperationType.EXCHANGE, index: { start: 4, end: 6 } }` 1177"Hello 1","Hello 2" 在 "Hello h" 之后插入,而 "Hello h" 在修改前的原数组中的 index=7,因此第三个 operation 为 `{ type: DataOperationType.ADD, index: 8, count: 2 }` 1178"Hello k","Hello l" 被删除了,而 "Hello k" 在原数组中的 index=10,因此第四个 operation 为 `{ type: DataOperationType.DELETE, index: 10, count: 2 }` 1179 11803. 调用一次onDatasetChange,一个index对应的数据只能被操作一次,若被操作多次,LazyForEach仅使第一个操作生效。 11814. 部分操作可以由开发者传入键值,LazyForEach不会再去重复调用keygenerator获取键值,需要开发者保证传入的键值的正确性。 11825. 若本次操作集合中有RELOAD操作,则其余操作全不生效。 1183 1184 1185 1186- ### 改变数据子属性 1187 1188若仅靠`LazyForEach`的刷新机制,当`item`变化时若想更新子组件,需要将原来的子组件全部销毁再重新构建,在子组件结构较为复杂的情况下,靠改变键值去刷新渲染性能较低。因此框架提供了`@Observed`与@`ObjectLink`机制进行深度观测,可以做到仅刷新使用了该属性的组件,提高渲染性能。开发者可根据其自身业务特点选择使用哪种刷新方式。 1189 1190```ts 1191class BasicDataSource implements IDataSource { 1192 private listeners: DataChangeListener[] = []; 1193 private originDataArray: StringData[] = []; 1194 1195 public totalCount(): number { 1196 return 0; 1197 } 1198 1199 public getData(index: number): StringData { 1200 return this.originDataArray[index]; 1201 } 1202 1203 registerDataChangeListener(listener: DataChangeListener): void { 1204 if (this.listeners.indexOf(listener) < 0) { 1205 console.info('add listener'); 1206 this.listeners.push(listener); 1207 } 1208 } 1209 1210 unregisterDataChangeListener(listener: DataChangeListener): void { 1211 const pos = this.listeners.indexOf(listener); 1212 if (pos >= 0) { 1213 console.info('remove listener'); 1214 this.listeners.splice(pos, 1); 1215 } 1216 } 1217 1218 notifyDataReload(): void { 1219 this.listeners.forEach(listener => { 1220 listener.onDataReloaded(); 1221 }) 1222 } 1223 1224 notifyDataAdd(index: number): void { 1225 this.listeners.forEach(listener => { 1226 listener.onDataAdd(index); 1227 }) 1228 } 1229 1230 notifyDataChange(index: number): void { 1231 this.listeners.forEach(listener => { 1232 listener.onDataChange(index); 1233 }) 1234 } 1235 1236 notifyDataDelete(index: number): void { 1237 this.listeners.forEach(listener => { 1238 listener.onDataDelete(index); 1239 }) 1240 } 1241 1242 notifyDataMove(from: number, to: number): void { 1243 this.listeners.forEach(listener => { 1244 listener.onDataMove(from, to); 1245 }) 1246 } 1247} 1248 1249class MyDataSource extends BasicDataSource { 1250 private dataArray: StringData[] = []; 1251 1252 public totalCount(): number { 1253 return this.dataArray.length; 1254 } 1255 1256 public getData(index: number): StringData { 1257 return this.dataArray[index]; 1258 } 1259 1260 public addData(index: number, data: StringData): void { 1261 this.dataArray.splice(index, 0, data); 1262 this.notifyDataAdd(index); 1263 } 1264 1265 public pushData(data: StringData): void { 1266 this.dataArray.push(data); 1267 this.notifyDataAdd(this.dataArray.length - 1); 1268 } 1269} 1270 1271@Observed 1272class StringData { 1273 message: string; 1274 constructor(message: string) { 1275 this.message = message; 1276 } 1277} 1278 1279@Entry 1280@Component 1281struct MyComponent { 1282 private moved: number[] = []; 1283 @State data: MyDataSource = new MyDataSource(); 1284 1285 aboutToAppear() { 1286 for (let i = 0; i <= 20; i++) { 1287 this.data.pushData(new StringData(`Hello ${i}`)); 1288 } 1289 } 1290 1291 build() { 1292 List({ space: 3 }) { 1293 LazyForEach(this.data, (item: StringData, index: number) => { 1294 ListItem() { 1295 ChildComponent({data: item}) 1296 } 1297 .onClick(() => { 1298 item.message += '0'; 1299 }) 1300 }, (item: StringData, index: number) => index.toString()) 1301 }.cachedCount(5) 1302 } 1303} 1304 1305@Component 1306struct ChildComponent { 1307 @ObjectLink data: StringData 1308 build() { 1309 Row() { 1310 Text(this.data.message).fontSize(50) 1311 .onAppear(() => { 1312 console.info("appear:" + this.data.message) 1313 }) 1314 }.margin({ left: 10, right: 10 }) 1315 } 1316} 1317``` 1318 1319此时点击`LazyForEach`子组件改变`item.message`时,重渲染依赖的是`ChildComponent`的`@ObjectLink`成员变量对其子属性的监听,此时框架只会刷新`Text(this.data.message)`,不会去重建整个`ListItem`子组件。 1320 1321**图10** LazyForEach改变数据子属性 1322 1323 1324## 拖拽排序 1325当LazyForEach在List组件下使用,并且设置了onMove事件,可以使能拖拽排序。拖拽排序离手后,如果数据位置发生变化,则会触发onMove事件,上报数据移动原始索引号和目标索引号。在onMove事件中,需要根据上报的起始索引号和目标索引号修改数据源。onMove中修改数据源不需要调用DataChangeListener中接口通知数据源变化。 1326 1327```ts 1328class BasicDataSource implements IDataSource { 1329 private listeners: DataChangeListener[] = []; 1330 private originDataArray: string[] = []; 1331 1332 public totalCount(): number { 1333 return 0; 1334 } 1335 1336 public getData(index: number): string { 1337 return this.originDataArray[index]; 1338 } 1339 1340 registerDataChangeListener(listener: DataChangeListener): void { 1341 if (this.listeners.indexOf(listener) < 0) { 1342 console.info('add listener'); 1343 this.listeners.push(listener); 1344 } 1345 } 1346 1347 unregisterDataChangeListener(listener: DataChangeListener): void { 1348 const pos = this.listeners.indexOf(listener); 1349 if (pos >= 0) { 1350 console.info('remove listener'); 1351 this.listeners.splice(pos, 1); 1352 } 1353 } 1354 1355 notifyDataReload(): void { 1356 this.listeners.forEach(listener => { 1357 listener.onDataReloaded(); 1358 }) 1359 } 1360 1361 notifyDataAdd(index: number): void { 1362 this.listeners.forEach(listener => { 1363 listener.onDataAdd(index); 1364 }) 1365 } 1366 1367 notifyDataChange(index: number): void { 1368 this.listeners.forEach(listener => { 1369 listener.onDataChange(index); 1370 }) 1371 } 1372 1373 notifyDataDelete(index: number): void { 1374 this.listeners.forEach(listener => { 1375 listener.onDataDelete(index); 1376 }) 1377 } 1378 1379 notifyDataMove(from: number, to: number): void { 1380 this.listeners.forEach(listener => { 1381 listener.onDataMove(from, to); 1382 }) 1383 } 1384} 1385 1386class MyDataSource extends BasicDataSource { 1387 private dataArray: string[] = []; 1388 1389 public totalCount(): number { 1390 return this.dataArray.length; 1391 } 1392 1393 public getData(index: number): string { 1394 return this.dataArray[index]; 1395 } 1396 1397 public addData(index: number, data: string): void { 1398 this.dataArray.splice(index, 0, data); 1399 this.notifyDataAdd(index); 1400 } 1401 1402 public moveDataWithoutNotify(from: number, to: number): void { 1403 let tmp = this.dataArray.splice(from, 1); 1404 this.dataArray.splice(to, 0, tmp[0]) 1405 } 1406 1407 public pushData(data: string): void { 1408 this.dataArray.push(data); 1409 this.notifyDataAdd(this.dataArray.length - 1); 1410 } 1411 1412 public deleteData(index: number): void { 1413 this.dataArray.splice(index, 1); 1414 this.notifyDataDelete(index); 1415 } 1416} 1417 1418@Entry 1419@Component 1420struct Parent { 1421 private data: MyDataSource = new MyDataSource(); 1422 1423 build() { 1424 Row() { 1425 List() { 1426 LazyForEach(this.data, (item: string) => { 1427 ListItem() { 1428 Text(item.toString()) 1429 .fontSize(16) 1430 .textAlign(TextAlign.Center) 1431 .size({height: 100, width: "100%"}) 1432 }.margin(10) 1433 .borderRadius(10) 1434 .backgroundColor("#FFFFFFFF") 1435 }, (item: string) => item) 1436 .onMove((from:number, to:number)=>{ 1437 this.data.moveDataWithoutNotify(from, to) 1438 }) 1439 } 1440 .width('100%') 1441 .height('100%') 1442 .backgroundColor("#FFDCDCDC") 1443 } 1444 } 1445 aboutToAppear(): void { 1446 for (let i = 0; i < 100; i++) { 1447 this.data.pushData(i.toString()) 1448 } 1449 } 1450} 1451``` 1452 1453**图11** LazyForEach拖拽排序效果图 1454 1455 1456## 常见使用问题 1457 1458### 渲染结果非预期 1459 1460 ```ts 1461 class BasicDataSource implements IDataSource { 1462 private listeners: DataChangeListener[] = []; 1463 private originDataArray: string[] = []; 1464 1465 public totalCount(): number { 1466 return 0; 1467 } 1468 1469 public getData(index: number): string { 1470 return this.originDataArray[index]; 1471 } 1472 1473 registerDataChangeListener(listener: DataChangeListener): void { 1474 if (this.listeners.indexOf(listener) < 0) { 1475 console.info('add listener'); 1476 this.listeners.push(listener); 1477 } 1478 } 1479 1480 unregisterDataChangeListener(listener: DataChangeListener): void { 1481 const pos = this.listeners.indexOf(listener); 1482 if (pos >= 0) { 1483 console.info('remove listener'); 1484 this.listeners.splice(pos, 1); 1485 } 1486 } 1487 1488 notifyDataReload(): void { 1489 this.listeners.forEach(listener => { 1490 listener.onDataReloaded(); 1491 }) 1492 } 1493 1494 notifyDataAdd(index: number): void { 1495 this.listeners.forEach(listener => { 1496 listener.onDataAdd(index); 1497 }) 1498 } 1499 1500 notifyDataChange(index: number): void { 1501 this.listeners.forEach(listener => { 1502 listener.onDataChange(index); 1503 }) 1504 } 1505 1506 notifyDataDelete(index: number): void { 1507 this.listeners.forEach(listener => { 1508 listener.onDataDelete(index); 1509 }) 1510 } 1511 1512 notifyDataMove(from: number, to: number): void { 1513 this.listeners.forEach(listener => { 1514 listener.onDataMove(from, to); 1515 }) 1516 } 1517 } 1518 1519 class MyDataSource extends BasicDataSource { 1520 private dataArray: string[] = []; 1521 1522 public totalCount(): number { 1523 return this.dataArray.length; 1524 } 1525 1526 public getData(index: number): string { 1527 return this.dataArray[index]; 1528 } 1529 1530 public addData(index: number, data: string): void { 1531 this.dataArray.splice(index, 0, data); 1532 this.notifyDataAdd(index); 1533 } 1534 1535 public pushData(data: string): void { 1536 this.dataArray.push(data); 1537 this.notifyDataAdd(this.dataArray.length - 1); 1538 } 1539 1540 public deleteData(index: number): void { 1541 this.dataArray.splice(index, 1); 1542 this.notifyDataDelete(index); 1543 } 1544 } 1545 1546 @Entry 1547 @Component 1548 struct MyComponent { 1549 private data: MyDataSource = new MyDataSource(); 1550 1551 aboutToAppear() { 1552 for (let i = 0; i <= 20; i++) { 1553 this.data.pushData(`Hello ${i}`) 1554 } 1555 } 1556 1557 build() { 1558 List({ space: 3 }) { 1559 LazyForEach(this.data, (item: string, index: number) => { 1560 ListItem() { 1561 Row() { 1562 Text(item).fontSize(50) 1563 .onAppear(() => { 1564 console.info("appear:" + item) 1565 }) 1566 }.margin({ left: 10, right: 10 }) 1567 } 1568 .onClick(() => { 1569 // 点击删除子组件 1570 this.data.deleteData(index); 1571 }) 1572 }, (item: string) => item) 1573 }.cachedCount(5) 1574 } 1575 } 1576 ``` 1577 1578 **图12** LazyForEach删除数据非预期 1579  1580 1581 当我们多次点击子组件时,会发现删除的并不一定是我们点击的那个子组件。原因是当我们删除了某一个子组件后,位于该子组件对应的数据项之后的各数据项,其`index`均应减1,但实际上后续的数据项对应的子组件仍然使用的是最初分配的`index`,其`itemGenerator`中的`index`并没有发生变化,所以删除结果和预期不符。 1582 1583 修复代码如下所示。 1584 1585 ```ts 1586 class BasicDataSource implements IDataSource { 1587 private listeners: DataChangeListener[] = []; 1588 private originDataArray: string[] = []; 1589 1590 public totalCount(): number { 1591 return 0; 1592 } 1593 1594 public getData(index: number): string { 1595 return this.originDataArray[index]; 1596 } 1597 1598 registerDataChangeListener(listener: DataChangeListener): void { 1599 if (this.listeners.indexOf(listener) < 0) { 1600 console.info('add listener'); 1601 this.listeners.push(listener); 1602 } 1603 } 1604 1605 unregisterDataChangeListener(listener: DataChangeListener): void { 1606 const pos = this.listeners.indexOf(listener); 1607 if (pos >= 0) { 1608 console.info('remove listener'); 1609 this.listeners.splice(pos, 1); 1610 } 1611 } 1612 1613 notifyDataReload(): void { 1614 this.listeners.forEach(listener => { 1615 listener.onDataReloaded(); 1616 }) 1617 } 1618 1619 notifyDataAdd(index: number): void { 1620 this.listeners.forEach(listener => { 1621 listener.onDataAdd(index); 1622 }) 1623 } 1624 1625 notifyDataChange(index: number): void { 1626 this.listeners.forEach(listener => { 1627 listener.onDataChange(index); 1628 }) 1629 } 1630 1631 notifyDataDelete(index: number): void { 1632 this.listeners.forEach(listener => { 1633 listener.onDataDelete(index); 1634 }) 1635 } 1636 1637 notifyDataMove(from: number, to: number): void { 1638 this.listeners.forEach(listener => { 1639 listener.onDataMove(from, to); 1640 }) 1641 } 1642 } 1643 1644 class MyDataSource extends BasicDataSource { 1645 private dataArray: string[] = []; 1646 1647 public totalCount(): number { 1648 return this.dataArray.length; 1649 } 1650 1651 public getData(index: number): string { 1652 return this.dataArray[index]; 1653 } 1654 1655 public addData(index: number, data: string): void { 1656 this.dataArray.splice(index, 0, data); 1657 this.notifyDataAdd(index); 1658 } 1659 1660 public pushData(data: string): void { 1661 this.dataArray.push(data); 1662 this.notifyDataAdd(this.dataArray.length - 1); 1663 } 1664 1665 public deleteData(index: number): void { 1666 this.dataArray.splice(index, 1); 1667 this.notifyDataDelete(index); 1668 } 1669 1670 public reloadData(): void { 1671 this.notifyDataReload(); 1672 } 1673 } 1674 1675 @Entry 1676 @Component 1677 struct MyComponent { 1678 private data: MyDataSource = new MyDataSource(); 1679 1680 aboutToAppear() { 1681 for (let i = 0; i <= 20; i++) { 1682 this.data.pushData(`Hello ${i}`) 1683 } 1684 } 1685 1686 build() { 1687 List({ space: 3 }) { 1688 LazyForEach(this.data, (item: string, index: number) => { 1689 ListItem() { 1690 Row() { 1691 Text(item).fontSize(50) 1692 .onAppear(() => { 1693 console.info("appear:" + item) 1694 }) 1695 }.margin({ left: 10, right: 10 }) 1696 } 1697 .onClick(() => { 1698 // 点击删除子组件 1699 this.data.deleteData(index); 1700 // 重置所有子组件的index索引 1701 this.data.reloadData(); 1702 }) 1703 }, (item: string, index: number) => item + index.toString()) 1704 }.cachedCount(5) 1705 } 1706 } 1707 ``` 1708 1709 在删除一个数据项后调用`reloadData`方法,重建后面的数据项,以达到更新`index`索引的目的。要保证`reloadData`方法重建数据项,必须保证数据项能生成新的key。这里用了`item + index.toString()`保证被删除数据项后面的数据项都被重建。如果用`item + Data.now().toString()`替代,那么所有数据项都生成新的key,导致所有数据项都被重建。这种方法,效果是一样的,只是性能略差。 1710 1711 **图13** 修复LazyForEach删除数据非预期 1712  1713 1714### 重渲染时图片闪烁 1715 1716 ```ts 1717 class BasicDataSource implements IDataSource { 1718 private listeners: DataChangeListener[] = []; 1719 private originDataArray: StringData[] = []; 1720 1721 public totalCount(): number { 1722 return 0; 1723 } 1724 1725 public getData(index: number): StringData { 1726 return this.originDataArray[index]; 1727 } 1728 1729 registerDataChangeListener(listener: DataChangeListener): void { 1730 if (this.listeners.indexOf(listener) < 0) { 1731 console.info('add listener'); 1732 this.listeners.push(listener); 1733 } 1734 } 1735 1736 unregisterDataChangeListener(listener: DataChangeListener): void { 1737 const pos = this.listeners.indexOf(listener); 1738 if (pos >= 0) { 1739 console.info('remove listener'); 1740 this.listeners.splice(pos, 1); 1741 } 1742 } 1743 1744 notifyDataReload(): void { 1745 this.listeners.forEach(listener => { 1746 listener.onDataReloaded(); 1747 }) 1748 } 1749 1750 notifyDataAdd(index: number): void { 1751 this.listeners.forEach(listener => { 1752 listener.onDataAdd(index); 1753 }) 1754 } 1755 1756 notifyDataChange(index: number): void { 1757 this.listeners.forEach(listener => { 1758 listener.onDataChange(index); 1759 }) 1760 } 1761 1762 notifyDataDelete(index: number): void { 1763 this.listeners.forEach(listener => { 1764 listener.onDataDelete(index); 1765 }) 1766 } 1767 1768 notifyDataMove(from: number, to: number): void { 1769 this.listeners.forEach(listener => { 1770 listener.onDataMove(from, to); 1771 }) 1772 } 1773 } 1774 1775 class MyDataSource extends BasicDataSource { 1776 private dataArray: StringData[] = []; 1777 1778 public totalCount(): number { 1779 return this.dataArray.length; 1780 } 1781 1782 public getData(index: number): StringData { 1783 return this.dataArray[index]; 1784 } 1785 1786 public addData(index: number, data: StringData): void { 1787 this.dataArray.splice(index, 0, data); 1788 this.notifyDataAdd(index); 1789 } 1790 1791 public pushData(data: StringData): void { 1792 this.dataArray.push(data); 1793 this.notifyDataAdd(this.dataArray.length - 1); 1794 } 1795 1796 public reloadData(): void { 1797 this.notifyDataReload(); 1798 } 1799 } 1800 1801 class StringData { 1802 message: string; 1803 imgSrc: Resource; 1804 constructor(message: string, imgSrc: Resource) { 1805 this.message = message; 1806 this.imgSrc = imgSrc; 1807 } 1808 } 1809 1810 @Entry 1811 @Component 1812 struct MyComponent { 1813 private moved: number[] = []; 1814 private data: MyDataSource = new MyDataSource(); 1815 1816 aboutToAppear() { 1817 for (let i = 0; i <= 20; i++) { 1818 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1819 } 1820 } 1821 1822 build() { 1823 List({ space: 3 }) { 1824 LazyForEach(this.data, (item: StringData, index: number) => { 1825 ListItem() { 1826 Column() { 1827 Text(item.message).fontSize(50) 1828 .onAppear(() => { 1829 console.info("appear:" + item.message) 1830 }) 1831 Image(item.imgSrc) 1832 .width(500) 1833 .height(200) 1834 }.margin({ left: 10, right: 10 }) 1835 } 1836 .onClick(() => { 1837 item.message += '00'; 1838 this.data.reloadData(); 1839 }) 1840 }, (item: StringData, index: number) => JSON.stringify(item)) 1841 }.cachedCount(5) 1842 } 1843 } 1844 ``` 1845 1846 **图14** LazyForEach仅改变文字但是图片闪烁问题 1847  1848 1849 在我们点击`ListItem`子组件时,我们只改变了数据项的`message`属性,但是`LazyForEach`的刷新机制会导致整个`ListItem`被重建。由于`Image`组件是异步刷新,所以视觉上图片会发生闪烁。为了解决这种情况我们应该使用`@ObjectLink`和`@Observed`去单独刷新使用了`item.message`的`Text`组件。 1850 1851 修复代码如下所示。 1852 1853 ```ts 1854 class BasicDataSource implements IDataSource { 1855 private listeners: DataChangeListener[] = []; 1856 private originDataArray: StringData[] = []; 1857 1858 public totalCount(): number { 1859 return 0; 1860 } 1861 1862 public getData(index: number): StringData { 1863 return this.originDataArray[index]; 1864 } 1865 1866 registerDataChangeListener(listener: DataChangeListener): void { 1867 if (this.listeners.indexOf(listener) < 0) { 1868 console.info('add listener'); 1869 this.listeners.push(listener); 1870 } 1871 } 1872 1873 unregisterDataChangeListener(listener: DataChangeListener): void { 1874 const pos = this.listeners.indexOf(listener); 1875 if (pos >= 0) { 1876 console.info('remove listener'); 1877 this.listeners.splice(pos, 1); 1878 } 1879 } 1880 1881 notifyDataReload(): void { 1882 this.listeners.forEach(listener => { 1883 listener.onDataReloaded(); 1884 }) 1885 } 1886 1887 notifyDataAdd(index: number): void { 1888 this.listeners.forEach(listener => { 1889 listener.onDataAdd(index); 1890 }) 1891 } 1892 1893 notifyDataChange(index: number): void { 1894 this.listeners.forEach(listener => { 1895 listener.onDataChange(index); 1896 }) 1897 } 1898 1899 notifyDataDelete(index: number): void { 1900 this.listeners.forEach(listener => { 1901 listener.onDataDelete(index); 1902 }) 1903 } 1904 1905 notifyDataMove(from: number, to: number): void { 1906 this.listeners.forEach(listener => { 1907 listener.onDataMove(from, to); 1908 }) 1909 } 1910 } 1911 1912 class MyDataSource extends BasicDataSource { 1913 private dataArray: StringData[] = []; 1914 1915 public totalCount(): number { 1916 return this.dataArray.length; 1917 } 1918 1919 public getData(index: number): StringData { 1920 return this.dataArray[index]; 1921 } 1922 1923 public addData(index: number, data: StringData): void { 1924 this.dataArray.splice(index, 0, data); 1925 this.notifyDataAdd(index); 1926 } 1927 1928 public pushData(data: StringData): void { 1929 this.dataArray.push(data); 1930 this.notifyDataAdd(this.dataArray.length - 1); 1931 } 1932 } 1933 1934 // @Observed类装饰器 和 @ObjectLink 用于在涉及嵌套对象或数组的场景中进行双向数据同步 1935 @Observed 1936 class StringData { 1937 message: string; 1938 imgSrc: Resource; 1939 constructor(message: string, imgSrc: Resource) { 1940 this.message = message; 1941 this.imgSrc = imgSrc; 1942 } 1943 } 1944 1945 @Entry 1946 @Component 1947 struct MyComponent { 1948 // 用状态变量来驱动UI刷新,而不是通过Lazyforeach的api来驱动UI刷新 1949 @State data: MyDataSource = new MyDataSource(); 1950 1951 aboutToAppear() { 1952 for (let i = 0; i <= 20; i++) { 1953 this.data.pushData(new StringData(`Hello ${i}`, $r('app.media.img'))); 1954 } 1955 } 1956 1957 build() { 1958 List({ space: 3 }) { 1959 LazyForEach(this.data, (item: StringData, index: number) => { 1960 ListItem() { 1961 ChildComponent({data: item}) 1962 } 1963 .onClick(() => { 1964 item.message += '0'; 1965 }) 1966 }, (item: StringData, index: number) => index.toString()) 1967 }.cachedCount(5) 1968 } 1969 } 1970 1971 @Component 1972 struct ChildComponent { 1973 @ObjectLink data: StringData 1974 build() { 1975 Column() { 1976 Text(this.data.message).fontSize(50) 1977 .onAppear(() => { 1978 console.info("appear:" + this.data.message) 1979 }) 1980 Image(this.data.imgSrc) 1981 .width(500) 1982 .height(200) 1983 }.margin({ left: 10, right: 10 }) 1984 } 1985 } 1986 ``` 1987 1988 **图15** 修复LazyForEach仅改变文字但是图片闪烁问题 1989  1990 1991### @ObjectLink属性变化UI未更新 1992 1993 ```ts 1994 class BasicDataSource implements IDataSource { 1995 private listeners: DataChangeListener[] = []; 1996 private originDataArray: StringData[] = []; 1997 1998 public totalCount(): number { 1999 return 0; 2000 } 2001 2002 public getData(index: number): StringData { 2003 return this.originDataArray[index]; 2004 } 2005 2006 registerDataChangeListener(listener: DataChangeListener): void { 2007 if (this.listeners.indexOf(listener) < 0) { 2008 console.info('add listener'); 2009 this.listeners.push(listener); 2010 } 2011 } 2012 2013 unregisterDataChangeListener(listener: DataChangeListener): void { 2014 const pos = this.listeners.indexOf(listener); 2015 if (pos >= 0) { 2016 console.info('remove listener'); 2017 this.listeners.splice(pos, 1); 2018 } 2019 } 2020 2021 notifyDataReload(): void { 2022 this.listeners.forEach(listener => { 2023 listener.onDataReloaded(); 2024 }) 2025 } 2026 2027 notifyDataAdd(index: number): void { 2028 this.listeners.forEach(listener => { 2029 listener.onDataAdd(index); 2030 }) 2031 } 2032 2033 notifyDataChange(index: number): void { 2034 this.listeners.forEach(listener => { 2035 listener.onDataChange(index); 2036 }) 2037 } 2038 2039 notifyDataDelete(index: number): void { 2040 this.listeners.forEach(listener => { 2041 listener.onDataDelete(index); 2042 }) 2043 } 2044 2045 notifyDataMove(from: number, to: number): void { 2046 this.listeners.forEach(listener => { 2047 listener.onDataMove(from, to); 2048 }) 2049 } 2050 } 2051 2052 class MyDataSource extends BasicDataSource { 2053 private dataArray: StringData[] = []; 2054 2055 public totalCount(): number { 2056 return this.dataArray.length; 2057 } 2058 2059 public getData(index: number): StringData { 2060 return this.dataArray[index]; 2061 } 2062 2063 public addData(index: number, data: StringData): void { 2064 this.dataArray.splice(index, 0, data); 2065 this.notifyDataAdd(index); 2066 } 2067 2068 public pushData(data: StringData): void { 2069 this.dataArray.push(data); 2070 this.notifyDataAdd(this.dataArray.length - 1); 2071 } 2072 } 2073 2074 @Observed 2075 class StringData { 2076 message: NestedString; 2077 constructor(message: NestedString) { 2078 this.message = message; 2079 } 2080 } 2081 2082 @Observed 2083 class NestedString { 2084 message: string; 2085 constructor(message: string) { 2086 this.message = message; 2087 } 2088 } 2089 2090 @Entry 2091 @Component 2092 struct MyComponent { 2093 private moved: number[] = []; 2094 @State data: MyDataSource = new MyDataSource(); 2095 2096 aboutToAppear() { 2097 for (let i = 0; i <= 20; i++) { 2098 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 2099 } 2100 } 2101 2102 build() { 2103 List({ space: 3 }) { 2104 LazyForEach(this.data, (item: StringData, index: number) => { 2105 ListItem() { 2106 ChildComponent({data: item}) 2107 } 2108 .onClick(() => { 2109 item.message.message += '0'; 2110 }) 2111 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 2112 }.cachedCount(5) 2113 } 2114 } 2115 2116 @Component 2117 struct ChildComponent { 2118 @ObjectLink data: StringData 2119 build() { 2120 Row() { 2121 Text(this.data.message.message).fontSize(50) 2122 .onAppear(() => { 2123 console.info("appear:" + this.data.message.message) 2124 }) 2125 }.margin({ left: 10, right: 10 }) 2126 } 2127 } 2128 ``` 2129 2130 **图16** ObjectLink属性变化后UI未更新 2131  2132 2133 @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到了,因此我们只能改变它的子属性去通知对应组件重新渲染,具体[请查看@ObjectLink与@Observed的详细使用方法和限制条件](./arkts-observed-and-objectlink.md)。 2134 2135 修复代码如下所示。 2136 2137 ```ts 2138 class BasicDataSource implements IDataSource { 2139 private listeners: DataChangeListener[] = []; 2140 private originDataArray: StringData[] = []; 2141 2142 public totalCount(): number { 2143 return 0; 2144 } 2145 2146 public getData(index: number): StringData { 2147 return this.originDataArray[index]; 2148 } 2149 2150 registerDataChangeListener(listener: DataChangeListener): void { 2151 if (this.listeners.indexOf(listener) < 0) { 2152 console.info('add listener'); 2153 this.listeners.push(listener); 2154 } 2155 } 2156 2157 unregisterDataChangeListener(listener: DataChangeListener): void { 2158 const pos = this.listeners.indexOf(listener); 2159 if (pos >= 0) { 2160 console.info('remove listener'); 2161 this.listeners.splice(pos, 1); 2162 } 2163 } 2164 2165 notifyDataReload(): void { 2166 this.listeners.forEach(listener => { 2167 listener.onDataReloaded(); 2168 }) 2169 } 2170 2171 notifyDataAdd(index: number): void { 2172 this.listeners.forEach(listener => { 2173 listener.onDataAdd(index); 2174 }) 2175 } 2176 2177 notifyDataChange(index: number): void { 2178 this.listeners.forEach(listener => { 2179 listener.onDataChange(index); 2180 }) 2181 } 2182 2183 notifyDataDelete(index: number): void { 2184 this.listeners.forEach(listener => { 2185 listener.onDataDelete(index); 2186 }) 2187 } 2188 2189 notifyDataMove(from: number, to: number): void { 2190 this.listeners.forEach(listener => { 2191 listener.onDataMove(from, to); 2192 }) 2193 } 2194 } 2195 2196 class MyDataSource extends BasicDataSource { 2197 private dataArray: StringData[] = []; 2198 2199 public totalCount(): number { 2200 return this.dataArray.length; 2201 } 2202 2203 public getData(index: number): StringData { 2204 return this.dataArray[index]; 2205 } 2206 2207 public addData(index: number, data: StringData): void { 2208 this.dataArray.splice(index, 0, data); 2209 this.notifyDataAdd(index); 2210 } 2211 2212 public pushData(data: StringData): void { 2213 this.dataArray.push(data); 2214 this.notifyDataAdd(this.dataArray.length - 1); 2215 } 2216 } 2217 2218 @Observed 2219 class StringData { 2220 message: NestedString; 2221 constructor(message: NestedString) { 2222 this.message = message; 2223 } 2224 } 2225 2226 @Observed 2227 class NestedString { 2228 message: string; 2229 constructor(message: string) { 2230 this.message = message; 2231 } 2232 } 2233 2234 @Entry 2235 @Component 2236 struct MyComponent { 2237 private moved: number[] = []; 2238 @State data: MyDataSource = new MyDataSource(); 2239 2240 aboutToAppear() { 2241 for (let i = 0; i <= 20; i++) { 2242 this.data.pushData(new StringData(new NestedString(`Hello ${i}`))); 2243 } 2244 } 2245 2246 build() { 2247 List({ space: 3 }) { 2248 LazyForEach(this.data, (item: StringData, index: number) => { 2249 ListItem() { 2250 ChildComponent({data: item}) 2251 } 2252 .onClick(() => { 2253 // @ObjectLink装饰的成员变量仅能监听到其子属性的变化,再深入嵌套的属性便无法观测到 2254 item.message = new NestedString(item.message.message + '0'); 2255 }) 2256 }, (item: StringData, index: number) => JSON.stringify(item) + index.toString()) 2257 }.cachedCount(5) 2258 } 2259 } 2260 2261 @Component 2262 struct ChildComponent { 2263 @ObjectLink data: StringData 2264 build() { 2265 Row() { 2266 Text(this.data.message.message).fontSize(50) 2267 .onAppear(() => { 2268 console.info("appear:" + this.data.message.message) 2269 }) 2270 }.margin({ left: 10, right: 10 }) 2271 } 2272 } 2273 ``` 2274 2275 **图17** 修复ObjectLink属性变化后UI更新 2276  2277 2278### 在List内使用屏幕闪烁 2279在List的onScrollIndex方法中调用onDataReloaded有产生屏幕闪烁的风险。 2280 2281```ts 2282class BasicDataSource implements IDataSource { 2283 private listeners: DataChangeListener[] = []; 2284 private originDataArray: string[] = []; 2285 2286 public totalCount(): number { 2287 return 0; 2288 } 2289 2290 public getData(index: number): string { 2291 return this.originDataArray[index]; 2292 } 2293 2294 registerDataChangeListener(listener: DataChangeListener): void { 2295 if (this.listeners.indexOf(listener) < 0) { 2296 console.info('add listener'); 2297 this.listeners.push(listener); 2298 } 2299 } 2300 2301 unregisterDataChangeListener(listener: DataChangeListener): void { 2302 const pos = this.listeners.indexOf(listener); 2303 if (pos >= 0) { 2304 console.info('remove listener'); 2305 this.listeners.splice(pos, 1); 2306 } 2307 } 2308 2309 notifyDataReload(): void { 2310 this.listeners.forEach(listener => { 2311 listener.onDataReloaded(); 2312 }) 2313 } 2314 2315 notifyDataAdd(index: number): void { 2316 this.listeners.forEach(listener => { 2317 listener.onDataAdd(index); 2318 }) 2319 } 2320 2321 notifyDataChange(index: number): void { 2322 this.listeners.forEach(listener => { 2323 listener.onDataChange(index); 2324 }) 2325 } 2326 2327 notifyDataDelete(index: number): void { 2328 this.listeners.forEach(listener => { 2329 listener.onDataDelete(index); 2330 }) 2331 } 2332 2333 notifyDataMove(from: number, to: number): void { 2334 this.listeners.forEach(listener => { 2335 listener.onDataMove(from, to); 2336 }) 2337 } 2338 2339 notifyDatasetChange(operations: DataOperation[]):void{ 2340 this.listeners.forEach(listener => { 2341 listener.onDatasetChange(operations); 2342 }) 2343 } 2344} 2345 2346class MyDataSource extends BasicDataSource { 2347 private dataArray: string[] = []; 2348 2349 public totalCount(): number { 2350 return this.dataArray.length; 2351 } 2352 2353 public getData(index: number): string { 2354 return this.dataArray[index]; 2355 } 2356 2357 public addData(index: number, data: string): void { 2358 this.dataArray.splice(index, 0, data); 2359 this.notifyDataAdd(index); 2360 } 2361 2362 public pushData(data: string): void { 2363 this.dataArray.push(data); 2364 this.notifyDataAdd(this.dataArray.length - 1); 2365 } 2366 2367 public deleteData(index: number): void { 2368 this.dataArray.splice(index, 1); 2369 this.notifyDataDelete(index); 2370 } 2371 2372 public changeData(index: number): void { 2373 this.notifyDataChange(index); 2374 } 2375 2376 operateData():void { 2377 const totalCount = this.dataArray.length; 2378 const batch=5; 2379 for (let i = totalCount; i < totalCount + batch; i++) { 2380 this.dataArray.push(`Hello ${i}`) 2381 } 2382 this.notifyDataReload(); 2383 } 2384} 2385 2386@Entry 2387@Component 2388struct MyComponent { 2389 private moved: number[] = []; 2390 private data: MyDataSource = new MyDataSource(); 2391 2392 aboutToAppear() { 2393 for (let i = 0; i <= 10; i++) { 2394 this.data.pushData(`Hello ${i}`) 2395 } 2396 } 2397 2398 build() { 2399 List({ space: 3 }) { 2400 LazyForEach(this.data, (item: string, index: number) => { 2401 ListItem() { 2402 Row() { 2403 Text(item) 2404 .width('100%') 2405 .height(80) 2406 .backgroundColor(Color.Gray) 2407 .onAppear(() => { 2408 console.info("appear:" + item) 2409 }) 2410 }.margin({ left: 10, right: 10 }) 2411 } 2412 }, (item: string) => item) 2413 }.cachedCount(10) 2414 .onScrollIndex((start, end, center) => { 2415 if (end === this.data.totalCount() - 1) { 2416 console.log('scroll to end') 2417 this.data.operateData(); 2418 } 2419 }) 2420 } 2421} 2422``` 2423 2424当List下拉到底的时候,屏闪效果如下图 2425 2426 2427用onDatasetChange代替onDataReloaded,不仅可以修复闪屏的问题,还能提升加载性能。 2428 2429```ts 2430class BasicDataSource implements IDataSource { 2431 private listeners: DataChangeListener[] = []; 2432 private originDataArray: string[] = []; 2433 2434 public totalCount(): number { 2435 return 0; 2436 } 2437 2438 public getData(index: number): string { 2439 return this.originDataArray[index]; 2440 } 2441 2442 registerDataChangeListener(listener: DataChangeListener): void { 2443 if (this.listeners.indexOf(listener) < 0) { 2444 console.info('add listener'); 2445 this.listeners.push(listener); 2446 } 2447 } 2448 2449 unregisterDataChangeListener(listener: DataChangeListener): void { 2450 const pos = this.listeners.indexOf(listener); 2451 if (pos >= 0) { 2452 console.info('remove listener'); 2453 this.listeners.splice(pos, 1); 2454 } 2455 } 2456 2457 notifyDataReload(): void { 2458 this.listeners.forEach(listener => { 2459 listener.onDataReloaded(); 2460 }) 2461 } 2462 2463 notifyDataAdd(index: number): void { 2464 this.listeners.forEach(listener => { 2465 listener.onDataAdd(index); 2466 }) 2467 } 2468 2469 notifyDataChange(index: number): void { 2470 this.listeners.forEach(listener => { 2471 listener.onDataChange(index); 2472 }) 2473 } 2474 2475 notifyDataDelete(index: number): void { 2476 this.listeners.forEach(listener => { 2477 listener.onDataDelete(index); 2478 }) 2479 } 2480 2481 notifyDataMove(from: number, to: number): void { 2482 this.listeners.forEach(listener => { 2483 listener.onDataMove(from, to); 2484 }) 2485 } 2486 2487 notifyDatasetChange(operations: DataOperation[]):void{ 2488 this.listeners.forEach(listener => { 2489 listener.onDatasetChange(operations); 2490 }) 2491 } 2492} 2493 2494class MyDataSource extends BasicDataSource { 2495 private dataArray: string[] = []; 2496 2497 public totalCount(): number { 2498 return this.dataArray.length; 2499 } 2500 2501 public getData(index: number): string { 2502 return this.dataArray[index]; 2503 } 2504 2505 public addData(index: number, data: string): void { 2506 this.dataArray.splice(index, 0, data); 2507 this.notifyDataAdd(index); 2508 } 2509 2510 public pushData(data: string): void { 2511 this.dataArray.push(data); 2512 this.notifyDataAdd(this.dataArray.length - 1); 2513 } 2514 2515 public deleteData(index: number): void { 2516 this.dataArray.splice(index, 1); 2517 this.notifyDataDelete(index); 2518 } 2519 2520 public changeData(index: number): void { 2521 this.notifyDataChange(index); 2522 } 2523 2524 operateData():void { 2525 const totalCount = this.dataArray.length; 2526 const batch=5; 2527 for (let i = totalCount; i < totalCount + batch; i++) { 2528 this.dataArray.push(`Hello ${i}`) 2529 } 2530 // 替换 notifyDataReload 2531 this.notifyDatasetChange([{type:DataOperationType.ADD, index: totalCount-1, count:batch}]) 2532 } 2533} 2534 2535@Entry 2536@Component 2537struct MyComponent { 2538 private moved: number[] = []; 2539 private data: MyDataSource = new MyDataSource(); 2540 2541 aboutToAppear() { 2542 for (let i = 0; i <= 10; i++) { 2543 this.data.pushData(`Hello ${i}`) 2544 } 2545 } 2546 2547 build() { 2548 List({ space: 3 }) { 2549 LazyForEach(this.data, (item: string, index: number) => { 2550 ListItem() { 2551 Row() { 2552 Text(item) 2553 .width('100%') 2554 .height(80) 2555 .backgroundColor(Color.Gray) 2556 .onAppear(() => { 2557 console.info("appear:" + item) 2558 }) 2559 }.margin({ left: 10, right: 10 }) 2560 } 2561 }, (item: string) => item) 2562 }.cachedCount(10) 2563 .onScrollIndex((start, end, center) => { 2564 if (end === this.data.totalCount() - 1) { 2565 console.log('scroll to end') 2566 this.data.operateData(); 2567 } 2568 }) 2569 } 2570} 2571``` 2572 2573修复后的效果如下图 2574