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