1# LazyForEach迁移Repeat指导文档 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @maorh--> 5<!--Designer: @keerecles--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9[Repeat](./arkts-new-rendering-control-repeat.md)是ArkUI在API version 12中新引入的循环渲染组件,相比[LazyForEach](./arkts-rendering-control-lazyforeach.md)具有更简洁的API、更丰富的功能以及更强的性能优化能力。本指南帮助开发者将LazyForEach平滑地迁移到Repeat。 10 11## 基础用法迁移 12 13### 数据首次渲染 14 15**LazyForEach用例** 16 17LazyForEach根据数据源循环渲染子组件。 18 19示例1中,在容器组件[List](../arkts-layout-development-create-list.md)中使用LazyForEach,并基于数据源循环渲染出了一系列[Text](../arkts-common-components-text-display.md)子组件。 20 21**示例1 - 迁移前** 22 23```ts 24/** BasicDataSource代码见文档末尾BasicDataSource示例代码: string类型数组的BasicDataSource代码 **/ 25 26class MyDataSource extends BasicDataSource { 27 private dataArray: string[] = []; 28 29 public totalCount(): number { 30 return this.dataArray.length; 31 } 32 33 public getData(index: number): string { 34 return this.dataArray[index]; 35 } 36 37 public pushData(data: string): void { 38 this.dataArray.push(data); 39 this.notifyDataAdd(this.dataArray.length - 1); 40 } 41} 42 43@Entry 44@Component 45struct MyComponent { 46 private data: MyDataSource = new MyDataSource(); 47 48 aboutToAppear() { 49 for (let i = 0; i <= 20; i++) { 50 this.data.pushData(`Hello ${i}`); 51 } 52 } 53 54 build() { 55 List({ space: 3 }) { 56 LazyForEach(this.data, (item: string) => { 57 ListItem() { 58 Row() { 59 Text(item).fontSize(50) 60 .onAppear(() => { 61 console.info(`appear: ${item}`); 62 }) 63 }.margin({ left: 10, right: 10 }) 64 } 65 }, (item: string) => item) 66 }.cachedCount(5) 67 } 68} 69``` 70 71以上是一个典型的使用LazyForEach循环渲染子组件的场景,下面将介绍如何将此示例迁移至Repeat。 72 73**迁移步骤** 74 751. 使用状态管理V2装饰器。 76 77 Repeat推荐和状态管理V2装饰器配合使用([懒加载](./arkts-new-rendering-control-repeat.md#循环渲染能力说明)模式下只支持和状态管理V2装饰器配合使用)。如果之前使用的是状态管理V1装饰器,需要修改为状态管理V2装饰器。 78 79 ```ts 80 // 迁移前 - LazyForEach 81 @Component // 状态管理V1 82 struct MyComponent { 83 build() { 84 // ... 85 LazyForEach(...) 86 // ... 87 } 88 // ...其他属性、方法 89 } 90 91 // 迁移后 - Repeat 92 @ComponentV2 // 状态管理V2 93 struct MyComponent { 94 build() { 95 // ... 96 Repeat(...) 97 // ... 98 } 99 // ...其他属性、方法 100 } 101 ``` 102 1032. 迁移数据源。 104 105 LazyForEach使用专用的数据结构[IDataSource](../../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md#idatasource)作为数据源。迁移至Repeat后,不再使用IDataSource作为数据源,而是使用状态管理V2装饰的数组作为数据源。 106 107 ```ts 108 // 迁移前 - LazyForEach 109 class MyDataSource implements IDataSource { 110 private dataArray: string[] = []; 111 112 public totalCount(): number { 113 return this.dataArray.length; 114 } 115 116 public getData(index: number): string { 117 return this.dataArray[index]; 118 } 119 120 // ...其他方法 121 } 122 123 // 迁移后 - Repeat 124 @Local data: Array<string> = []; 125 ``` 126 1273. 迁移组件生成函数和键值生成函数。 128 129 LazyForEach与Repeat均通过组件生成函数,为每一项数据创建一个子组件;通过键值生成函数,为每一项数据生成一个唯一的键值。</br> 130 从LazyForEach迁移至Repeat时,两者的语法存在差异。Repeat需要在[`.each()`](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#each)或[`.template()`](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#template)中设置组件生成函数,在[`.key()`](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#key)中设置键值生成函数。 131 132 ```ts 133 // 迁移前 - LazyForEach 134 List() { 135 LazyForEach( 136 this.data, // 数据源 137 (item: string, index: number) => { // 组件生成函数 138 ListItem() { 139 Text(item) 140 } 141 }, 142 (item: string, index: number) => item // 键值生成函数 143 ) 144 } 145 146 // 迁移后 - Repeat 147 List() { 148 Repeat<string>(this.data) // 数据源 149 .each((repeatItem: RepeatItem<string>) => { // 组件生成函数 150 ListItem() { 151 Text(repeatItem.item) 152 } 153 }) 154 .key((item: string, index: number) => item) // 键值生成函数 155 } 156 ``` 157 1584. 配置懒加载功能。 159 160 Repeat具有[懒加载](./arkts-new-rendering-control-repeat.md#循环渲染能力说明)和[全量加载](./arkts-new-rendering-control-repeat.md#关闭懒加载)两种模式。 161 162 - 全量加载模式渲染所有子节点(对标[ForEach](./arkts-rendering-control-foreach.md))。 163 - 懒加载模式动态渲染屏幕区域和预加载区域内的子节点(需要与容器组件配合使用,对标LazyForEach)。 164 165 从LazyForEach迁移至Repeat时,需要调用[virtualScroll](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#virtualscroll))属性,使能懒加载。 166 167 ```ts 168 // 迁移前 - LazyForEach 169 LazyForEach(data, (item) => {...}, (item) => item) 170 171 // 迁移后 - Repeat 172 Repeat(data) 173 .virtualScroll() // 使能懒加载 174 ``` 175 176**迁移后代码** 177 178通过以上步骤,可以将示例1从LazyForEach迁移至Repeat,迁移后的完整示例如下所示。 179 180**示例1 - 迁移后** 181 182```ts 183@Entry 184@ComponentV2 // 使用状态管理V2 185struct MyComponent { 186 @Local data: Array<string> = []; // 数据源为状态管理V2装饰的数组 187 188 aboutToAppear() { 189 for (let i = 0; i <= 20; i++) { 190 this.data.push(`Hello ${i}`); 191 } 192 } 193 194 build() { 195 List({ space: 3 }) { 196 Repeat(this.data) // 使用Repeat 197 .each((repeatItem: RepeatItem<string>) => { // 组件生成函数 198 ListItem() { 199 Row() { 200 Text(repeatItem.item).fontSize(50) 201 .onAppear(() => { 202 console.info(`appear: ${repeatItem.item}`); 203 }) 204 }.margin({ left: 10, right: 10 }) 205 } 206 }) 207 .key((item: string) => item) // 键值生成函数 208 .virtualScroll() // 使能懒加载 209 }.cachedCount(5) 210 } 211} 212``` 213 214运行后界面如下图所示。 215 216 217 218### 数据更新操作 219 220**LazyForEach用例** 221 222当LazyForEach的数据源发生变化时,开发者需要根据数据源的变化情况调用[DataChangeListener](../../reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md#datachangelistener)对应的接口,通知LazyForEach做相应的更新。主要的数据操作包括:添加数据、删除数据、交换数据、修改单个数据、修改多个数据、精准批量修改数据。 223 224示例2演示了主要的数据操作。 225 226**示例2 - 迁移前** 227 228```ts 229/** BasicDataSource代码见文档末尾BasicDataSource示例代码: string类型数组的BasicDataSource代码 **/ 230 231class MyDataSource extends BasicDataSource { 232 private dataArray: string[] = []; 233 234 public totalCount(): number { 235 return this.dataArray.length; 236 } 237 238 public getData(index: number): string { 239 return this.dataArray[index]; 240 } 241 242 // 添加数据 243 public pushData(data: string): void { 244 this.dataArray.push(data); 245 this.notifyDataAdd(this.dataArray.length - 1); 246 } 247 248 // 删除数据 249 public deleteData(index: number): void { 250 this.dataArray.splice(index, 1); 251 this.notifyDataDelete(index); 252 } 253 254 // 交换数据 255 public moveData(from: number, to: number): void { 256 let temp: string = this.dataArray[from]; 257 this.dataArray[from] = this.dataArray[to]; 258 this.dataArray[to] = temp; 259 this.notifyDataMove(from, to); 260 } 261 262 // 修改单个数据 263 public changeData(index: number, data: string): void { 264 this.dataArray.splice(index, 1, data); 265 this.notifyDataChange(index); 266 } 267 268 // 修改多个数据 269 public modifyAllData(): void { 270 this.dataArray = this.dataArray.map((item: string) => { 271 return 'Changed ' + item; 272 }); 273 this.notifyDataReload(); 274 } 275} 276 277@Entry 278@Component 279struct MyComponent { 280 private data: MyDataSource = new MyDataSource(); 281 private count: number = 0; 282 283 aboutToAppear() { 284 for (let i = 0; i <= 10; i++) { 285 this.data.pushData(`Hello ${i}`); 286 } 287 } 288 289 build() { 290 Column({ space: 3 }) { 291 // 点击追加子组件 292 Button('Add new item') 293 .onClick(() => { 294 this.data.pushData(`New item ${this.count++}`); 295 }) 296 // 点击删除子组件 297 Button('Delete item 0') 298 .onClick(() => { 299 this.data.deleteData(0); 300 }) 301 // 点击交换子组件 302 Button('Swap item 0 and item 1') 303 .onClick(() => { 304 this.data.moveData(0, 1); 305 }) 306 // 点击修改单个子组件 307 Button('Change item 0') 308 .onClick(() => { 309 this.data.changeData(0, `Changed item ${this.count++}`); 310 }) 311 // 点击修改多个子组件 312 Button('Change all items') 313 .onClick(() => { 314 this.data.modifyAllData(); 315 }) 316 List({ space: 3 }) { 317 LazyForEach(this.data, (item: string) => { 318 ListItem() { 319 Row() { 320 Text(item).fontSize(25) 321 } 322 } 323 }, (item: string) => item) 324 }.cachedCount(5) 325 } 326 } 327} 328``` 329 330以上是一个典型的更新数据后LazyForEach重新渲染子组件的场景,下面将介绍如何将此示例迁移至Repeat。 331 332**迁移步骤** 333 3341. 迁移准备。 335 336 根据数据首次渲染小节中的步骤,将LazyForEach替换为Repeat。 337 1. 使用状态管理V2装饰器。 338 2. 迁移数据源。 339 3. 迁移组件生成函数与键值生成函数。 340 4. 使能懒加载。 341 3422. 迁移数据源修改方式。 343 344 - 对于LazyForEach,在修改数据源后需要调用对应的接口通知其更新。 345 - 对于Repeat,由状态管理V2监听其数据源变化,并触发更新。因此,开发者直接修改数据源即可,无需其他额外操作。 346 347 ```ts 348 // 以修改单个数据为例 349 // 迁移前 - LazyForEach 350 class MyDataSource implements IDataSource { 351 private dataArray: string[] = []; 352 353 public changeData(index: number, newData: string): void { 354 this.dataArray.splice(index, 1, data); 355 this.notifyDataChange(index); 356 } 357 358 // ...其他方法 359 } 360 361 // 迁移后 - Repeat 362 this.data.splice(index, 1, data); 363 ``` 364 365 其他数据更新操作,如添加数据、删除数据、交换数据等,与以上方法类似,可通过直接修改数据源数组实现。 366 367**迁移后代码** 368 369迁移后的完整示例如下。 370 371**示例2 - 迁移后** 372 373```ts 374@Entry 375@ComponentV2 376struct MyComponent { 377 @Local data: Array<string> = []; 378 private count: number = 0; 379 380 aboutToAppear() { 381 for (let i = 0; i <= 10; i++) { 382 this.data.push(`Hello ${i}`); 383 } 384 } 385 386 build() { 387 Column({ space: 3 }) { 388 // 点击追加子组件 389 Button('Add new item') 390 .onClick(() => { this.data.push(`New item ${this.count++}`); }) 391 // 点击删除子组件 392 Button('Delete item 0') 393 .onClick(() => { this.data.splice(0, 1); }) 394 // 点击交换子组件 395 Button('Swap item 0 and item 1') 396 .onClick(() => { let temp: string = this.data[0]; 397 this.data[0] = this.data[1]; 398 this.data[1] = temp; }) 399 // 点击修改单个子组件 400 Button('Change item 0') 401 .onClick(() => { this.data.splice(0, 1, `Changed item ${this.count++}`); }) 402 // 点击修改多个子组件 403 Button('Change all items') 404 .onClick(() => { this.data = this.data.map((item: string) => { return 'Changed ' + item; }); }) 405 List({ space: 3 }) { 406 Repeat(this.data) 407 .each((repeatItem: RepeatItem<string>) => { 408 ListItem() { 409 Row() { 410 Text(repeatItem.item).fontSize(25) 411 } 412 } 413 }) 414 .key((item: string) => item) 415 .virtualScroll() 416 }.cachedCount(5) 417 } 418 } 419} 420``` 421 422运行后界面如下图所示。 423 424 425 426## 典型场景迁移 427 428### 修改数据子属性 429 430**LazyForEach用例** 431 432LazyForEach可以使用[@Observed与@ObjectLink](./arkts-observed-and-objectlink.md)装饰器实现对数据子属性的观测。当有数据子属性发生变化时,仅更新使用了该子属性的组件,从而提高性能。 433 434示例3演示了对子属性的观测。 435 436**示例3 - 迁移前** 437 438```ts 439/** BasicDataSource代码见文档末尾BasicDataSource示例代码: StringData类型数组的BasicDataSource代码 **/ 440 441class MyDataSource extends BasicDataSource { 442 private dataArray: StringData[] = []; 443 444 public totalCount(): number { 445 return this.dataArray.length; 446 } 447 448 public getData(index: number): StringData { 449 return this.dataArray[index]; 450 } 451 452 public pushData(data: StringData): void { 453 this.dataArray.push(data); 454 this.notifyDataAdd(this.dataArray.length - 1); 455 } 456} 457 458@Observed 459class StringData { 460 message: string; 461 462 constructor(message: string) { 463 this.message = message; 464 } 465} 466 467@Entry 468@Component 469struct MyComponent { 470 private data: MyDataSource = new MyDataSource(); 471 472 aboutToAppear() { 473 for (let i = 0; i <= 20; i++) { 474 this.data.pushData(new StringData(`Hello ${i}`)); 475 } 476 } 477 478 build() { 479 List({ space: 3 }) { 480 LazyForEach(this.data, (item: StringData, index: number) => { 481 ListItem() { 482 ChildComponent({ data: item }) 483 } 484 .onClick(() => { 485 item.message += '0'; 486 }) 487 }, (item: StringData, index: number) => index.toString()) 488 }.cachedCount(5) 489 } 490} 491 492@Component 493struct ChildComponent { 494 @ObjectLink data: StringData; 495 496 build() { 497 Row() { 498 Text(this.data.message).fontSize(50) 499 .onAppear(() => { 500 console.info(`appear: ${this.data.message}`); 501 }) 502 }.margin({ left: 10, right: 10 }) 503 } 504} 505``` 506 507**迁移Repeat** 508 509Repeat需要和状态管理V2一起使用,状态管理V2提供了[@ObserveV2和@Trace](./arkts-new-observedV2-and-trace.md)装饰器对子属性进行深度观测。迁移时,需要将@Observe和@ObjectLink装饰器迁移至@ObserveV2和@Trace装饰器。 510 511迁移后的示例如下所示。 512 513**示例3 - 迁移后** 514 515```ts 516@ObservedV2 517class StringData { 518 @Trace message: string; // 观测子属性 519 520 constructor(message: string) { 521 this.message = message; 522 } 523} 524 525@Entry 526@ComponentV2 527struct MyComponent { 528 @Local data: StringData[] = []; 529 530 aboutToAppear() { 531 for (let i = 0; i <= 20; i++) { 532 this.data.push(new StringData(`Hello ${i}`)); 533 } 534 } 535 536 build() { 537 List({ space: 3 }) { 538 Repeat(this.data) 539 .each((repeatItem) => { 540 ListItem() { 541 Text(repeatItem.item.message).fontSize(50) 542 .onAppear(() => { 543 console.info(`appear: ${repeatItem.item.message}`); 544 }) 545 } 546 .onClick(() => { 547 repeatItem.item.message += '0'; 548 }) 549 }) 550 .key((item: StringData, index: number) => index.toString()) 551 .virtualScroll() 552 }.cachedCount(5) 553 } 554} 555``` 556 557运行后界面如下图所示。 558 559 560 561### 状态管理V2观测组件内部状态 562 563**LazyForEach用例** 564 565状态管理V2的[@Local](./arkts-new-local.md)装饰器提供了观测自定义组件内部变量的能力。被@Local装饰的变量发生变化时,会通知LazyForEach更新对应的组件。 566 567示例4演示了在LazyForEach中使用@Local装饰器观测数据变化,触发组件更新。 568 569**示例4 - 迁移前** 570 571```ts 572/** BasicDataSource代码见文档末尾BasicDataSource示例代码: StringData类型数组的BasicDataSource代码 **/ 573 574class MyDataSource extends BasicDataSource { 575 private dataArray: StringData[] = []; 576 577 public totalCount(): number { 578 return this.dataArray.length; 579 } 580 581 public getData(index: number): StringData { 582 return this.dataArray[index]; 583 } 584 585 public pushData(data: StringData): void { 586 this.dataArray.push(data); 587 this.notifyDataAdd(this.dataArray.length - 1); 588 } 589} 590 591@ObservedV2 592class StringData { 593 @Trace message: string; 594 595 constructor(message: string) { 596 this.message = message; 597 } 598} 599 600@Entry 601@ComponentV2 602struct MyComponent { 603 data: MyDataSource = new MyDataSource(); 604 605 aboutToAppear() { 606 for (let i = 0; i <= 20; i++) { 607 this.data.pushData(new StringData(`Hello ${i}`)); 608 } 609 } 610 611 build() { 612 List({ space: 3 }) { 613 LazyForEach(this.data, (item: StringData, index: number) => { 614 ListItem() { 615 Row() { 616 Text(item.message).fontSize(50) 617 .onClick(() => { 618 // 修改@ObservedV2装饰类中@Trace装饰的变量,触发刷新此处Text组件 619 item.message += '!'; 620 }) 621 ChildComponent() 622 } 623 } 624 }, (item: StringData, index: number) => index.toString()) 625 }.cachedCount(5) 626 } 627} 628 629@ComponentV2 630struct ChildComponent { 631 @Local message: string = '?'; 632 633 build() { 634 Row() { 635 Text(this.message).fontSize(50) 636 .onClick(() => { 637 // 修改@Local装饰的变量,触发刷新此处Text组件 638 this.message += '?'; 639 }) 640 } 641 } 642} 643``` 644 645**迁移Repeat** 646 647Repeat本身支持与状态管理V2联合使用,将LazyForEach相关代码修改为Repeat后,即可实现对组件内部状态变量的观测。 648 649迁移后的示例如下所示。 650 651**示例4 - 迁移后** 652 653```ts 654@ObservedV2 655class StringData { 656 @Trace message: string; 657 658 constructor(message: string) { 659 this.message = message; 660 } 661} 662 663@Entry 664@ComponentV2 665struct MyComponent { 666 @Local data: StringData[] = []; 667 668 aboutToAppear() { 669 for (let i = 0; i <= 20; i++) { 670 this.data.push(new StringData(`Hello ${i}`)); 671 } 672 } 673 674 build() { 675 List({ space: 3 }) { 676 Repeat(this.data) 677 .each((repeatItem) => { 678 ListItem() { 679 Row() { 680 Text(repeatItem.item.message).fontSize(50) 681 .onClick(() => { 682 // 修改@ObservedV2装饰类中@Trace装饰的变量,触发刷新此处Text组件 683 repeatItem.item.message += '!'; 684 }) 685 ChildComponent() 686 } 687 } 688 }) 689 .key((item: StringData, index: number) => index.toString()) 690 .virtualScroll() 691 }.cachedCount(5) 692 } 693} 694 695@ComponentV2 696struct ChildComponent { 697 @Local message: string = '?'; 698 699 build() { 700 Row() { 701 Text(this.message).fontSize(50) 702 .onClick(() => { 703 // 修改@Local装饰的变量,触发刷新此处Text组件 704 this.message += '?'; 705 }) 706 } 707 } 708} 709``` 710 711运行后界面如下图所示。 712 713 714 715### 状态管理V2观测组件外部输入 716 717**LazyForEach用例** 718 719状态管理V2的[@Param](./arkts-new-param.md)装饰器提供了观测自定义组件外部输入变量的能力,可以实现父子组件间的数据同步。将父组件的变量传递给子组件,并用@Param装饰,当父组件变量发生变化时,会通知对应的组件更新。 720 721示例5演示了在LazyForEach中使用@Param装饰器观测数据变化,触发组件更新。 722 723 724**示例5 - 迁移前** 725 726```ts 727/** BasicDataSource代码见文档末尾BasicDataSource示例代码: StringData类型数组的BasicDataSource代码 **/ 728 729class MyDataSource extends BasicDataSource { 730 private dataArray: StringData[] = []; 731 732 public totalCount(): number { 733 return this.dataArray.length; 734 } 735 736 public getData(index: number): StringData { 737 return this.dataArray[index]; 738 } 739 740 public pushData(data: StringData): void { 741 this.dataArray.push(data); 742 this.notifyDataAdd(this.dataArray.length - 1); 743 } 744} 745 746@ObservedV2 747class StringData { 748 @Trace message: string; 749 750 constructor(message: string) { 751 this.message = message; 752 } 753} 754 755@Entry 756@ComponentV2 757struct MyComponent { 758 data: MyDataSource = new MyDataSource(); 759 760 aboutToAppear() { 761 for (let i = 0; i <= 20; i++) { 762 this.data.pushData(new StringData(`Hello ${i}`)); 763 } 764 } 765 766 build() { 767 List({ space: 3 }) { 768 LazyForEach(this.data, (item: StringData, index: number) => { 769 ListItem() { 770 ChildComponent({ data: item.message }) // 向自定义组件内传入变量 771 .onClick(() => { 772 item.message += '!'; 773 }) 774 } 775 }, (item: StringData, index: number) => index.toString()) 776 }.cachedCount(5) 777 } 778} 779 780@ComponentV2 781struct ChildComponent { 782 @Param @Require data: string = ''; // 接收来自外部的变量 783 784 build() { 785 Row() { 786 Text(this.data).fontSize(50) 787 } 788 } 789} 790``` 791 792**迁移Repeat** 793 794Repeat本身支持与状态管理V2联合使用,将LazyForEach相关代码修改为Repeat后,即可实现对组件外部输入状态变量的观测。 795 796迁移后的示例如下所示。 797 798**示例5 - 迁移后** 799 800```ts 801@ObservedV2 802class StringData { 803 @Trace message: string; 804 805 constructor(message: string) { 806 this.message = message; 807 } 808} 809 810@Entry 811@ComponentV2 812struct MyComponent { 813 @Local data: StringData[] = []; 814 815 aboutToAppear() { 816 for (let i = 0; i <= 20; i++) { 817 this.data.push(new StringData(`Hello ${i}`)); 818 } 819 } 820 821 build() { 822 List({ space: 3 }) { 823 Repeat(this.data) 824 .each((repeatItem) => { 825 ListItem() { 826 ChildComponent({ data: repeatItem.item.message }) // 向自定义组件内传入变量 827 .onClick(() => { 828 repeatItem.item.message += '!'; 829 }) 830 } 831 }) 832 .key((item: StringData, index: number) => index.toString()) 833 .virtualScroll() 834 }.cachedCount(5) 835 } 836} 837 838@ComponentV2 839struct ChildComponent { 840 @Param @Require data: string = ''; // 接收来自外部的变量 841 842 build() { 843 Row() { 844 Text(this.data).fontSize(50) 845 } 846 } 847} 848``` 849 850运行后界面如下图所示。 851 852 853 854### 拖拽排序 855 856**LazyForEach用例** 857 858LazyForEach的[onMove](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-drag-sorting.md#onmove)属性提供了拖拽排序能力。 859 860示例6为典型用例。 861 862**示例6 - 迁移前** 863 864```ts 865/** BasicDataSource代码见文档末尾BasicDataSource示例代码: string类型数组的BasicDataSource代码 **/ 866 867class MyDataSource extends BasicDataSource { 868 private dataArray: string[] = []; 869 870 public totalCount(): number { 871 return this.dataArray.length; 872 } 873 874 public getData(index: number): string { 875 return this.dataArray[index]; 876 } 877 878 public moveDataWithoutNotify(from: number, to: number): void { 879 let tmp = this.dataArray.splice(from, 1); 880 this.dataArray.splice(to, 0, tmp[0]); 881 } 882 883 public pushData(data: string): void { 884 this.dataArray.push(data); 885 this.notifyDataAdd(this.dataArray.length - 1); 886 } 887} 888 889@Entry 890@Component 891struct Parent { 892 private data: MyDataSource = new MyDataSource(); 893 894 aboutToAppear(): void { 895 for (let i = 0; i < 100; i++) { 896 this.data.pushData(i.toString()); 897 } 898 } 899 900 build() { 901 Row() { 902 List() { 903 LazyForEach(this.data, (item: string) => { 904 ListItem() { 905 Text(item.toString()) 906 .fontSize(16) 907 .textAlign(TextAlign.Center) 908 .size({ height: 100, width: '100%' }) 909 }.margin(10) 910 .borderRadius(10) 911 .backgroundColor('#FFFFFFFF') 912 }, (item: string) => item) 913 .onMove((from: number, to: number) => { // 实现拖拽排序 914 this.data.moveDataWithoutNotify(from, to); 915 }) 916 } 917 .width('100%') 918 .height('100%') 919 .backgroundColor('#FFDCDCDC') 920 } 921 } 922} 923``` 924 925**迁移Repeat** 926 927Repeat具有与LazyForEach相同的onMove属性。将LazyForEach相关代码修改为Repeat后,即可实现拖拽排序。 928 929迁移后的示例如下所示。 930 931**示例6 - 迁移后** 932 933```ts 934@Entry 935@ComponentV2 936struct Parent { 937 @Local data: string[] = []; 938 939 aboutToAppear(): void { 940 for (let i = 0; i < 100; i++) { 941 this.data.push(i.toString()); 942 } 943 } 944 945 moveData(from: number, to: number) { 946 let tmp = this.data.splice(from, 1); 947 this.data.splice(to, 0, tmp[0]); 948 } 949 950 build() { 951 Row() { 952 List() { 953 Repeat(this.data) 954 .each((repeatItem) => { 955 ListItem() { 956 Text(repeatItem.item.toString()) 957 .fontSize(16) 958 .textAlign(TextAlign.Center) 959 .size({ height: 100, width: '100%' }) 960 }.margin(10) 961 .borderRadius(10) 962 .backgroundColor('#FFFFFFFF') 963 }) 964 .key((item: string) => item) 965 .virtualScroll() 966 .onMove((from: number, to: number) => { // 实现拖拽排序 967 this.moveData(from, to); 968 }) 969 } 970 .width('100%') 971 .height('100%') 972 .backgroundColor('#FFDCDCDC') 973 } 974 } 975} 976``` 977 978运行后界面如下图所示。 979 980 981 982### 组件复用 983 984**LazyForEach用例** 985 986LazyForEach自身并不具备组件复用能力,为实现组件复用,需要与[@Reusable](./arkts-reusable.md)装饰器配合使用(被@Reusable装饰的自定义组件具有复用能力)。 987 988示例7演示了组件复用的典型场景。 989 990**示例7 - 迁移前** 991 992```ts 993/** BasicDataSource代码见文档末尾BasicDataSource示例代码: StringData类型数组的BasicDataSource代码 **/ 994 995class MyDataSource extends BasicDataSource { 996 private dataArray: StringData[] = []; 997 998 public totalCount(): number { 999 return this.dataArray.length; 1000 } 1001 1002 public getData(index: number): StringData { 1003 return this.dataArray[index]; 1004 } 1005 1006 public pushData(data: StringData): void { 1007 this.dataArray.push(data); 1008 this.notifyDataAdd(this.dataArray.length - 1); 1009 } 1010} 1011 1012class StringData { 1013 message: string; 1014 1015 constructor(message: string) { 1016 this.message = message; 1017 } 1018} 1019 1020@Entry 1021@Component 1022struct MyComponent { 1023 data: MyDataSource = new MyDataSource(); 1024 1025 aboutToAppear() { 1026 for (let i = 0; i <= 30; i++) { 1027 this.data.pushData(new StringData(`Hello${i}`)); 1028 } 1029 } 1030 1031 build() { 1032 List({ space: 3 }) { 1033 LazyForEach(this.data, (item: StringData, index: number) => { 1034 ListItem() { 1035 ChildComponent({ data: item }) 1036 .onAppear(() => { 1037 console.info(`onAppear: ${item.message}`); 1038 }) 1039 } 1040 }, (item: StringData, index: number) => index.toString()) 1041 }.cachedCount(5) 1042 } 1043} 1044 1045@Reusable 1046@Component 1047struct ChildComponent { 1048 @State data: StringData = new StringData(''); 1049 1050 aboutToAppear(): void { 1051 console.info(`aboutToAppear: ${this.data.message}`); 1052 } 1053 1054 aboutToRecycle(): void { 1055 console.info(`aboutToRecycle: ${this.data.message}`); 1056 } 1057 1058 // 对复用的组件进行数据更新 1059 aboutToReuse(params: Record<string, ESObject>): void { 1060 this.data = params.data as StringData; 1061 console.info(`aboutToReuse: ${this.data.message}`); 1062 } 1063 1064 build() { 1065 Row() { 1066 Text(this.data.message).fontSize(50) 1067 } 1068 } 1069} 1070``` 1071 1072**迁移Repeat** 1073 1074Repeat本身具备组件复用能力,同时也支持与状态管理V2的[@ReusableV2](./arkts-new-reusableV2.md)装饰器联合使用。因此,迁移至Repeat后,其组件复用具有两种实现方案。 1075 10761. 直接使用Repeat自身的复用能力。 10772. 使用@ReusableV2装饰器提供的复用能力。 1078 1079需要注意的是,Repeat默认使能自身的复用能力,且优先级高于@ReusableV2装饰器。若要使用@ReusableV2装饰器,需要先手动关闭Repeat自身的复用能力(@ReusableV2装饰器从API version 18开始支持,Repeat从API version 19开始支持关闭自身复用能力)。 1080 1081**示例7 - 迁移方案1:使用Repeat自身的复用能力** 1082 1083Repeat本身具备复用能力,且默认开启。将LazyForEach相关代码迁移至Repeat后,便已经具备了复用能力。 1084 1085修改后的示例如下。 1086 1087```ts 1088class StringData { 1089 message: string; 1090 1091 constructor(message: string) { 1092 this.message = message; 1093 } 1094} 1095 1096@Entry 1097@ComponentV2 1098struct MyComponent { 1099 @Local data: StringData[] = []; 1100 1101 aboutToAppear() { 1102 for (let i = 0; i <= 30; i++) { 1103 this.data.push(new StringData(`Hello${i}`)); 1104 } 1105 } 1106 1107 build() { 1108 List({ space: 3 }) { 1109 Repeat(this.data) // Repeat自身具备复用功能 1110 .each((repeatItem) => { 1111 ListItem() { 1112 Text(repeatItem.item.message).fontSize(50) 1113 } 1114 }) 1115 .key((item: StringData, index: number) => index.toString()) 1116 .virtualScroll() 1117 }.cachedCount(5) 1118 } 1119} 1120``` 1121 1122**示例7 - 迁移方案2:使用@ReusableV2装饰器** 1123 1124若要使用@ReusableV2装饰器,首先需要通过`.virtualScroll({ reusable: false })`关闭Repeat自身的复用功能,再用@ReusableV2装饰需要复用的自定义组件。 1125 1126相较于Repeat自身的复用,@ReusableV2装饰的自定义组件在回收和复用时,会触发aboutToRecycle和aboutToReuse两个生命周期。 1127 1128使用@ReusableV2装饰器的迁移示例如下所示。 1129 1130```ts 1131class StringData { 1132 message: string; 1133 1134 constructor(message: string) { 1135 this.message = message; 1136 } 1137} 1138 1139@Entry 1140@ComponentV2 1141struct MyComponent { 1142 @Local data: StringData[] = []; 1143 1144 aboutToAppear() { 1145 for (let i = 0; i <= 30; i++) { 1146 this.data.push(new StringData(`Hello${i}`)); 1147 } 1148 } 1149 1150 build() { 1151 List({ space: 3 }) { 1152 Repeat(this.data) 1153 .each((repeatItem) => { 1154 ListItem() { 1155 ChildComponent({ data: repeatItem.item }) 1156 .onAppear(() => { 1157 console.info(`onAppear: ${repeatItem.item.message}`); 1158 }) 1159 } 1160 }) 1161 .key((item: StringData, index: number) => index.toString()) 1162 .virtualScroll({ reusable: false }) // 关闭Repeat自身的复用功能(API 19) 1163 }.cachedCount(5) 1164 } 1165} 1166 1167// 使用@ReusableV2实现组件复用(API 18) 1168@ReusableV2 1169@ComponentV2 1170struct ChildComponent { 1171 @Param data: StringData = new StringData(''); 1172 1173 aboutToAppear(): void { 1174 console.info(`aboutToAppear: ${this.data.message}`); 1175 } 1176 1177 aboutToRecycle(): void { 1178 console.info(`aboutToRecycle: ${this.data.message}`); 1179 } 1180 1181 aboutToReuse(): void { 1182 console.info(`aboutToReuse: ${this.data.message}`); 1183 } 1184 1185 build() { 1186 Row() { 1187 Text(this.data.message).fontSize(50) 1188 } 1189 } 1190} 1191``` 1192 1193运行后界面如下图所示。 1194 1195 1196 1197### 模板渲染 1198 1199**LazyForEach用例** 1200 1201LazyForEach自身并不具备模板渲染能力。为实现模板渲染能力,需要开发者自己实现逻辑判断,为不同的数据项选择不同的渲染模板。 1202 1203示例8演示了模板渲染的典型场景。 1204 1205**示例8 - 迁移前** 1206 1207```ts 1208/** BasicDataSource代码见文档末尾BasicDataSource示例代码: StringData类型数组的BasicDataSource代码 **/ 1209 1210class MyDataSource extends BasicDataSource { 1211 private dataArray: StringData[] = []; 1212 1213 public totalCount(): number { 1214 return this.dataArray.length; 1215 } 1216 1217 public getData(index: number): StringData { 1218 return this.dataArray[index]; 1219 } 1220 1221 public pushData(data: StringData): void { 1222 this.dataArray.push(data); 1223 this.notifyDataAdd(this.dataArray.length - 1); 1224 } 1225} 1226 1227class StringData { 1228 message: string; 1229 type: number; 1230 1231 constructor(message: string, type: number) { 1232 this.message = message; 1233 this.type = type; 1234 } 1235 1236 getType(): number { 1237 if (this.type >= 1) { 1238 return 1; 1239 } else { 1240 return 0; 1241 } 1242 } 1243} 1244 1245@Entry 1246@Component 1247struct MyComponent { 1248 data: MyDataSource = new MyDataSource(); 1249 1250 aboutToAppear() { 1251 for (let i = 0; i <= 200; i++) { 1252 this.data.pushData(new StringData(`Hello${i}`, i % 2)); 1253 } 1254 } 1255 1256 build() { 1257 List({ space: 3 }) { 1258 LazyForEach(this.data, (item: StringData, index: number) => { 1259 ListItem() { 1260 // 开发者自己实现逻辑判断,为不同的数据项选择不同的渲染模板 1261 if (item.getType() == 0) { 1262 // 模板A 1263 ChildComponentA({ data: item }) 1264 .onAppear(() => { 1265 console.info(`type A onAppear: ${item.message}`); 1266 }) 1267 } else { 1268 // 模板B 1269 ChildComponentB({ data: item }) 1270 .onAppear(() => { 1271 console.info(`type B onAppear: ${item.message}`); 1272 }) 1273 } 1274 } 1275 }, (item: StringData, index: number) => index.toString()) 1276 }.cachedCount(5) 1277 } 1278} 1279 1280// 使用@Reusable实现组件复用 1281@Reusable 1282@Component 1283struct ChildComponentA { 1284 @State data: StringData = new StringData('', 0); 1285 1286 aboutToAppear(): void { 1287 console.info(`type A aboutToAppear: ${this.data.message}`); 1288 } 1289 1290 aboutToRecycle(): void { 1291 console.info(`type A aboutToRecycle: ${this.data.message}`); 1292 } 1293 1294 aboutToReuse(params: Record<string, ESObject>): void { 1295 this.data = params.data as StringData; 1296 console.info(`type A aboutToReuse: ${this.data.message}`); 1297 } 1298 1299 build() { 1300 Row() { 1301 Text(this.data.message).fontSize(50) 1302 Button('Type A') 1303 } 1304 } 1305} 1306 1307@Reusable 1308@Component 1309struct ChildComponentB { 1310 @State data: StringData = new StringData('', 0); 1311 1312 aboutToAppear(): void { 1313 console.info(`type B aboutToAppear: ${this.data.message}`); 1314 } 1315 1316 aboutToRecycle(): void { 1317 console.info(`type B aboutToRecycle: ${this.data.message}`); 1318 } 1319 1320 aboutToReuse(params: Record<string, ESObject>): void { 1321 this.data = params.data as StringData; 1322 console.info(`type B aboutToReuse: ${this.data.message}`); 1323 } 1324 1325 build() { 1326 Row() { 1327 Text(this.data.message).fontSize(50).fontColor(Color.Gray) 1328 Text('Type B') 1329 } 1330 } 1331} 1332``` 1333**迁移Repeat** 1334 1335Repeat本身具备模板渲染能力,开发者可以通过[templateId](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#templateid)方法为不同的数据项选择不同的模板,再通过[template](../../reference/apis-arkui/arkui-ts/ts-rendering-control-repeat.md#template)方法为不同的模板配置不同的组件生成函数。同时,开发者仍然可以选择自己实现逻辑判断,为不同的数据项分配不同的模板。 1336 1337需要注意的是,如果开发者选择自己实现模板渲染,则需要关闭Repeat自身的复用功能。否则,Repeat在复用子组件时无法选择正确的模板,会导致渲染异常。 1338 1339**示例8 - 迁移方案1:使用Repeat自身的模板渲染能力** 1340 1341```ts 1342class StringData { 1343 message: string; 1344 type: number; 1345 1346 constructor(message: string, type: number) { 1347 this.message = message; 1348 this.type = type; 1349 } 1350 1351 getType(): number { 1352 if (this.type >= 1) { 1353 return 1; 1354 } else { 1355 return 0; 1356 } 1357 } 1358} 1359 1360@Entry 1361@ComponentV2 1362struct MyComponent { 1363 data: StringData[] = []; 1364 1365 aboutToAppear() { 1366 for (let i = 0; i <= 200; i++) { 1367 this.data.push(new StringData(`Hello${i}`, i % 2)); 1368 } 1369 } 1370 1371 build() { 1372 List({ space: 3 }) { 1373 Repeat(this.data) 1374 .each((repeatItem) => { 1375 ListItem() { 1376 Text('Default item') 1377 } 1378 }) 1379 .template('A', (repeatItem) => { // 模板A 1380 ListItem() { 1381 Row() { 1382 Text(repeatItem.item.message).fontSize(50) 1383 Button('Type A') 1384 } 1385 } 1386 }) 1387 .template('B', (repeatItem) => { // 模板B 1388 ListItem() { 1389 Row() { 1390 Text(repeatItem.item.message).fontSize(50).fontColor(Color.Gray) 1391 Text('Type B') 1392 } 1393 } 1394 }) 1395 .templateId((item: StringData) => { // 为不同的数据项选择不同的模板 1396 if (item.getType() == 0) { 1397 return 'A'; 1398 } else { 1399 return 'B'; 1400 } 1401 }) 1402 .key((item: StringData, index: number) => index.toString()) 1403 .virtualScroll() 1404 }.cachedCount(5) 1405 } 1406} 1407``` 1408 1409**示例8 - 迁移方案2:由开发者实现模板渲染能力** 1410 1411```ts 1412class StringData { 1413 message: string; 1414 type: number; 1415 1416 constructor(message: string, type: number) { 1417 this.message = message; 1418 this.type = type; 1419 } 1420 1421 getType(): number { 1422 if (this.type >= 1) { 1423 return 1; 1424 } else { 1425 return 0; 1426 } 1427 } 1428} 1429 1430@Entry 1431@ComponentV2 1432struct MyComponent { 1433 data: StringData[] = []; 1434 1435 aboutToAppear() { 1436 for (let i = 0; i <= 200; i++) { 1437 this.data.push(new StringData(`Hello${i}`, i % 2)); 1438 } 1439 } 1440 1441 build() { 1442 List({ space: 3 }) { 1443 Repeat(this.data) 1444 .each((repeatItem) => { 1445 ListItem() { 1446 // 开发者自己实现逻辑判断,为不同的数据项选择不同的渲染模板 1447 if (repeatItem.item.getType() == 0) { 1448 ChildComponentA({ data: repeatItem.item }) // 模板A 1449 .onAppear(() => { 1450 console.info(`type A onAppear: ${repeatItem.item.message}`); 1451 }) 1452 } else { 1453 ChildComponentB({ data: repeatItem.item }) // 模板B 1454 .onAppear(() => { 1455 console.info(`type B onAppear: ${repeatItem.item.message}`); 1456 }) 1457 } 1458 } 1459 }) 1460 .key((item: StringData, index: number) => index.toString()) 1461 .virtualScroll({ reusable: false }) // 关闭Repeat自身的复用功能(API 19),避免渲染异常 1462 }.cachedCount(5) 1463 } 1464} 1465 1466// 使用@ReusableV2实现组件复用(API 18) 1467@ReusableV2 1468@ComponentV2 1469struct ChildComponentA { 1470 @Param data: StringData = new StringData('', 0); 1471 1472 aboutToAppear(): void { 1473 console.info(`type A aboutToAppear: ${this.data.message}`); 1474 } 1475 1476 aboutToRecycle(): void { 1477 console.info(`type A aboutToRecycle: ${this.data.message}`); 1478 } 1479 1480 aboutToReuse(): void { 1481 console.info(`type A aboutToReuse: ${this.data.message}`); 1482 } 1483 1484 build() { 1485 Row() { 1486 Text(this.data.message).fontSize(50) 1487 Button('Type A') 1488 } 1489 } 1490} 1491 1492@ReusableV2 1493@ComponentV2 1494struct ChildComponentB { 1495 @Param data: StringData = new StringData('', 0); 1496 1497 aboutToAppear(): void { 1498 console.info(`type B aboutToAppear: ${this.data.message}`); 1499 } 1500 1501 aboutToRecycle(): void { 1502 console.info(`type B aboutToRecycle: ${this.data.message}`); 1503 } 1504 1505 aboutToReuse(): void { 1506 console.info(`type B aboutToReuse: ${this.data.message}`); 1507 } 1508 1509 build() { 1510 Row() { 1511 Text(this.data.message).fontSize(50).fontColor(Color.Gray) 1512 Text('Type B') 1513 } 1514 } 1515} 1516``` 1517 1518运行后界面如下图所示。 1519 1520 1521 1522## BasicDataSource示例代码 1523 1524### string类型数组的BasicDataSource代码 1525 1526```ts 1527// BasicDataSource实现了IDataSource接口,用于管理listener监听,以及通知LazyForEach数据更新 1528class BasicDataSource implements IDataSource { 1529 private listeners: DataChangeListener[] = []; 1530 private originDataArray: string[] = []; 1531 1532 public totalCount(): number { 1533 return this.originDataArray.length; 1534 } 1535 1536 public getData(index: number): string { 1537 return this.originDataArray[index]; 1538 } 1539 1540 // 该方法为框架侧调用,为LazyForEach组件向其数据源处添加listener监听 1541 registerDataChangeListener(listener: DataChangeListener): void { 1542 if (this.listeners.indexOf(listener) < 0) { 1543 console.info('add listener'); 1544 this.listeners.push(listener); 1545 } 1546 } 1547 1548 // 该方法为框架侧调用,为对应的LazyForEach组件在数据源处去除listener监听 1549 unregisterDataChangeListener(listener: DataChangeListener): void { 1550 const pos = this.listeners.indexOf(listener); 1551 if (pos >= 0) { 1552 console.info('remove listener'); 1553 this.listeners.splice(pos, 1); 1554 } 1555 } 1556 1557 // 通知LazyForEach组件需要重载所有子组件 1558 notifyDataReload(): void { 1559 this.listeners.forEach(listener => { 1560 listener.onDataReloaded(); 1561 }); 1562 } 1563 1564 // 通知LazyForEach组件需要在index对应索引处添加子组件 1565 notifyDataAdd(index: number): void { 1566 this.listeners.forEach(listener => { 1567 listener.onDataAdd(index); 1568 // 写法2:listener.onDatasetChange([{type: DataOperationType.ADD, index: index}]); 1569 }); 1570 } 1571 1572 // 通知LazyForEach组件在index对应索引处数据有变化,需要重建该子组件 1573 notifyDataChange(index: number): void { 1574 this.listeners.forEach(listener => { 1575 listener.onDataChange(index); 1576 // 写法2:listener.onDatasetChange([{type: DataOperationType.CHANGE, index: index}]); 1577 }); 1578 } 1579 1580 // 通知LazyForEach组件需要在index对应索引处删除该子组件 1581 notifyDataDelete(index: number): void { 1582 this.listeners.forEach(listener => { 1583 listener.onDataDelete(index); 1584 // 写法2:listener.onDatasetChange([{type: DataOperationType.DELETE, index: index}]); 1585 }); 1586 } 1587 1588 // 通知LazyForEach组件将from索引和to索引处的子组件进行交换 1589 notifyDataMove(from: number, to: number): void { 1590 this.listeners.forEach(listener => { 1591 listener.onDataMove(from, to); 1592 // 写法2:listener.onDatasetChange( 1593 // [{type: DataOperationType.EXCHANGE, index: {start: from, end: to}}]); 1594 }); 1595 } 1596 1597 notifyDatasetChange(operations: DataOperation[]): void { 1598 this.listeners.forEach(listener => { 1599 listener.onDatasetChange(operations); 1600 }); 1601 } 1602} 1603``` 1604 1605### StringData类型数组的BasicDataSource代码 1606 1607```ts 1608class BasicDataSource implements IDataSource { 1609 private listeners: DataChangeListener[] = []; 1610 private originDataArray: StringData[] = []; 1611 1612 public totalCount(): number { 1613 return this.originDataArray.length; 1614 } 1615 1616 public getData(index: number): StringData { 1617 return this.originDataArray[index]; 1618 } 1619 1620 registerDataChangeListener(listener: DataChangeListener): void { 1621 if (this.listeners.indexOf(listener) < 0) { 1622 console.info('add listener'); 1623 this.listeners.push(listener); 1624 } 1625 } 1626 1627 unregisterDataChangeListener(listener: DataChangeListener): void { 1628 const pos = this.listeners.indexOf(listener); 1629 if (pos >= 0) { 1630 console.info('remove listener'); 1631 this.listeners.splice(pos, 1); 1632 } 1633 } 1634 1635 notifyDataReload(): void { 1636 this.listeners.forEach(listener => { 1637 listener.onDataReloaded(); 1638 }); 1639 } 1640 1641 notifyDataAdd(index: number): void { 1642 this.listeners.forEach(listener => { 1643 listener.onDataAdd(index); 1644 }); 1645 } 1646 1647 notifyDataChange(index: number): void { 1648 this.listeners.forEach(listener => { 1649 listener.onDataChange(index); 1650 }); 1651 } 1652 1653 notifyDataDelete(index: number): void { 1654 this.listeners.forEach(listener => { 1655 listener.onDataDelete(index); 1656 }); 1657 } 1658 1659 notifyDataMove(from: number, to: number): void { 1660 this.listeners.forEach(listener => { 1661 listener.onDataMove(from, to); 1662 }); 1663 } 1664 1665 notifyDatasetChange(operations: DataOperation[]): void { 1666 this.listeners.forEach(listener => { 1667 listener.onDatasetChange(operations); 1668 }); 1669 } 1670} 1671```