• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Reusable Decorator: Reusing Components
2
3
4When the \@Reusable decorator decorates any custom component, the custom component is reusable.
5
6> **NOTE**
7>
8> The \@Reusable decorator is supported since API version 10.
9
10## Overview
11
12- \@Reusable applies to custom components and is used together with \@Component. When a custom component marked with \@Reusable is detached from the component tree, the component and its corresponding **JSView** object are stored in the cache pool. When a custom component node is created later, nodes in the cache pool are reused, saving the time for re-creating components.
13
14## Constraints
15
16- The \@Reusable decorator is used only for custom components.
17
18```ts
19import { ComponentContent } from "@kit.ArkUI";
20
21// An error is reported when @Builder is used together with @Reusable.
22// @Reusable
23@Builder
24function buildCreativeLoadingDialog(closedClick: () => void) {
25  Crash()
26}
27
28@Component
29export struct Crash {
30  build() {
31    Column() {
32      Text("Crash")
33        .fontSize(12)
34        .lineHeight(18)
35        .fontColor(Color.Blue)
36        .margin({
37          left: 6
38        })
39    }.width('100%')
40    .height('100%')
41    .justifyContent(FlexAlign.Center)
42  }
43}
44
45@Entry
46@Component
47struct Index {
48  @State message: string = 'Hello World';
49  private uicontext = this.getUIContext();
50
51  build() {
52    RelativeContainer() {
53      Text(this.message)
54        .id('Index')
55        .fontSize(50)
56        .fontWeight(FontWeight.Bold)
57        .alignRules({
58          center: { anchor: '__container__', align: VerticalAlign.Center },
59          middle: { anchor: '__container__', align: HorizontalAlign.Center }
60        })
61        .onClick(() => {
62          let contentNode = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog), () => {
63          });
64          this.uicontext.getPromptAction().openCustomDialog(contentNode);
65        })
66    }
67    .height('100%')
68    .width('100%')
69  }
70}
71```
72
73- **ComponentContent** does not support @Reusable decorated custom components.
74
75```ts
76import { ComponentContent } from "@kit.ArkUI";
77
78@Builder
79function buildCreativeLoadingDialog(closedClick: () => void) {
80  Crash()
81}
82
83// If @Reusable is commented out, the dialog box displays properly; if @Reusable is added, the project crashes.
84@Reusable
85@Component
86export struct Crash {
87  build() {
88    Column() {
89      Text("Crash")
90        .fontSize(12)
91        .lineHeight(18)
92        .fontColor(Color.Blue)
93        .margin({
94          left: 6
95        })
96    }.width('100%')
97    .height('100%')
98    .justifyContent(FlexAlign.Center)
99  }
100}
101
102@Entry
103@Component
104struct Index {
105  @State message: string = 'Hello World';
106  private uicontext = this.getUIContext();
107
108  build() {
109    RelativeContainer() {
110      Text(this.message)
111        .id('Index')
112        .fontSize(50)
113        .fontWeight(FontWeight.Bold)
114        .alignRules({
115          center: { anchor: '__container__', align: VerticalAlign.Center },
116          middle: { anchor: '__container__', align: HorizontalAlign.Center }
117        })
118        .onClick(() => {
119          // buildNode, the bottom layer of ComponentContent, does not support the @Reusable decorated custom component.
120          let contentNode = new ComponentContent(this.uicontext, wrapBuilder(buildCreativeLoadingDialog), () => {
121          });
122          this.uicontext.getPromptAction().openCustomDialog(contentNode);
123        })
124    }
125    .height('100%')
126    .width('100%')
127  }
128}
129```
130
131- \@Reusable decorators do not support nested use, which increases the memory and is inconvenient for maintenance.
132
133
134> **NOTE**
135>
136> Nested use is not supported. One mark will add a cache pool and each of the cache pool has the same tree structure, leading to low reuse efficiency and increased reused memory.
137>
138> After the nested use forms independent reuse cache pools, the lifecycle transfer is abnormal. Resources and variables cannot be shared, which is inconvenient for maintenance and may cause problems.
139>
140> In the following example, the reuse cache pool formed by **PlayButton** cannot be used in the reuse cache pool of **PlayButton02**, but the reuse cache pools formed by **PlayButton02** can be used by each other.
141> The lifecycle method reused by the component cannot be called in pairs. When **PlayButton** is hidden, **aboutToRecycle** of **PlayButton02** is triggered. However, when **PlayButton02** is displayed independently, **aboutToReuse** cannot be executed.
142>
143> In conclusion, nested use is not recommended.
144
145
146```ts
147@Entry
148@Component
149struct Index {
150  @State isPlaying: boolean = false;
151  @State isPlaying02: boolean = true;
152  @State isPlaying01: boolean = false;
153
154  build() {
155    Column() {
156      if (this.isPlaying02) {
157
158        // Initial state of the button: shown
159        Text("Default shown childbutton")
160          .fontSize(14)
161        PlayButton02({ isPlaying02: $isPlaying02 })
162      }
163      Text(`------------------------`)
164
165      // Initial state of the button: hidden
166      if (this.isPlaying01) {
167        Text("Default hidden childbutton")
168          .fontSize(14)
169        PlayButton02({ isPlaying02: $isPlaying01 })
170      }
171      Text(`------------------------`)
172
173      // Parent-child nesting
174      if (this.isPlaying) {
175        Text("Parent-child nesting")
176          .fontSize(14)
177        PlayButton({ buttonPlaying: $isPlaying })
178      }
179      Text(`------------------------`)
180
181      // Parent-child nesting control
182      Text(`Parent=child==is ${this.isPlaying ? '' : 'not'} playing`).fontSize(14)
183      Button('Parent=child===controll=' + this.isPlaying)
184        .margin(14)
185        .onClick(() => {
186          this.isPlaying = !this.isPlaying;
187        })
188
189      Text(`------------------------`)
190
191      // Hide the button control by default.
192      Text(`Hiddenchild==is ${this.isPlaying01 ? '' : 'not'} playing`).fontSize(14)
193      Button('Button===hiddenchild==control==' + this.isPlaying01)
194        .margin(14)
195        .onClick(() => {
196          this.isPlaying01 = !this.isPlaying01;
197        })
198      Text(`------------------------`)
199
200      // Display the button control by default.
201      Text(`shownchid==is ${this.isPlaying02 ? '' : 'not'} playing`).fontSize(14)
202      Button('Button===shownchid==control==:' + this.isPlaying02)
203        .margin(15)
204        .onClick(() => {
205          this.isPlaying02 = !this.isPlaying02;
206        })
207    }
208  }
209}
210
211// Reuse 1
212@Reusable
213@Component
214struct PlayButton {
215  @Link buttonPlaying: boolean;
216
217  build() {
218    Column() {
219
220      // Reuse
221      PlayButton02({ isPlaying02: $buttonPlaying })
222      Button(this.buttonPlaying ? 'parent_pause' : 'parent_play')
223        .margin(12)
224        .onClick(() => {
225          this.buttonPlaying = !this.buttonPlaying;
226        })
227    }
228  }
229}
230
231// Reuse 2: Nested use is not recommended.
232@Reusable
233@Component
234struct PlayButton02 {
235  @Link isPlaying02: boolean;
236
237  aboutToRecycle(): void {
238    console.log("=====aboutToRecycle====PlayButton02====");
239  }
240
241  aboutToReuse(params: ESObject): void {
242    console.log("=====aboutToReuse====PlayButton02====");
243  }
244
245  build() {
246    Column() {
247      Button('===commonbutton=====')
248        .margin(12)
249    }
250  }
251}
252```
253
254## Use Scenario
255
256- List scrolling: When a user scrolls a list containing a large amount of data, frequently creating and destroying list item views may cause stuttering and performance problems. In this case, the reuse mechanism of the **List** component can reuse the created list view to improve the scrolling smoothness.
257
258- Dynamic layout update: If the application UI requires frequent layout updates, for example, the view structure and style are dynamically changed based on user operations or data changes, frequent creation and destruction of views may cause frequent layout calculation, affecting the frame rate. In this case, component reuse can avoid unnecessary view creation and layout calculation, improving performance.
259
260- In the scenario where data items are frequently created and destroyed, the component reuse mechanism can be applied to reuse created views and update only their data content, reducing view creation and destruction.
261
262
263## Usage Case
264
265### Dynamic Layout Update
266
267- In the sample code, the child custom component is marked as a reusable component. You can update **Child** by clicking the button to trigger **Child** reuse.
268- \@Reusable: The custom component to reuse is decorated by @Reusable.
269- **aboutToReuse**: Invoked when a reusable custom component is re-added to the node tree from the reuse cache to receive construction parameters of the component.
270
271```ts
272// xxx.ets
273export class Message {
274  value: string | undefined;
275
276  constructor(value: string) {
277    this.value = value;
278  }
279}
280
281@Entry
282@Component
283struct Index {
284  @State switch: boolean = true;
285
286  build() {
287    Column() {
288      Button('Hello')
289        .fontSize(30)
290        .fontWeight(FontWeight.Bold)
291        .onClick(() => {
292          this.switch = !this.switch;
293        })
294      if (this.switch) {
295        // If only one component to be reused, you do not need to set reuseId.
296        Child({ message: new Message('Child') })
297          .reuseId('Child')
298      }
299    }
300    .height("100%")
301    .width('100%')
302  }
303}
304
305@Reusable
306@Component
307struct Child {
308  @State message: Message = new Message('AboutToReuse');
309
310  aboutToReuse(params: Record<string, ESObject>) {
311    console.info("Recycle ====Child==");
312    this.message = params.message as Message;
313  }
314
315  build() {
316    Column() {
317      Text(this.message.value)
318        .fontSize(30)
319    }
320    .borderWidth(1)
321    .height(100)
322  }
323}
324```
325
326### Using List Scrolling with LazyForEach
327
328- In the sample code, the **CardView** custom component is marked as a reusable component, and the list is scrolled up and down to trigger **CardView** reuse.
329- Only the \@State decorated variable **item** can be updated.
330
331```ts
332class MyDataSource implements IDataSource {
333  private dataArray: string[] = [];
334  private listener: DataChangeListener | undefined;
335
336  public totalCount(): number {
337    return this.dataArray.length;
338  }
339
340  public getData(index: number): string {
341    return this.dataArray[index];
342  }
343
344  public pushData(data: string): void {
345    this.dataArray.push(data);
346  }
347
348  public reloadListener(): void {
349    this.listener?.onDataReloaded();
350  }
351
352  public registerDataChangeListener(listener: DataChangeListener): void {
353    this.listener = listener;
354  }
355
356  public unregisterDataChangeListener(listener: DataChangeListener): void {
357    this.listener = undefined;
358  }
359}
360
361@Entry
362@Component
363struct ReuseDemo {
364  private data: MyDataSource = new MyDataSource();
365
366  aboutToAppear() {
367    for (let i = 1; i < 1000; i++) {
368      this.data.pushData(i + "");
369    }
370  }
371
372  // ...
373  build() {
374    Column() {
375      List() {
376        LazyForEach(this.data, (item: string) => {
377          ListItem() {
378            CardView({ item: item })
379          }
380        }, (item: string) => item)
381      }
382    }
383  }
384}
385
386// Reusable component
387@Reusable
388@Component
389export struct CardView {
390  @State item: string = '';
391
392  aboutToReuse(params: Record<string, Object>): void {
393    this.item = params.item as string;
394  }
395
396  build() {
397    Column() {
398      Text(this.item)
399        .fontSize(30)
400    }
401    .borderWidth(1)
402    .height(100)
403  }
404}
405```
406
407### if Statement
408
409- In the sample code, the **OneMoment** custom component is marked as a reusable component, and the list is scrolled up and down to trigger **OneMoment** reuse.
410- You can use **reuseId** to assign reuse groups to reusable components. Components with the same **reuseId** will be reused in the same reuse group. If there is only one reusable component, you do not need to set **reuseId**.
411- The **reuseId** is used to identify the component to be reused and omit the deletion and re-creation logic executed by **if**, improving the efficiency and performance of component reuse.
412
413```ts
414@Entry
415@Component
416struct Index {
417  private dataSource = new MyDataSource<FriendMoment>();
418
419  aboutToAppear(): void {
420    for (let i = 0; i < 20; i++) {
421      let title = i + 1 + "test_if";
422      this.dataSource.pushData(new FriendMoment(i.toString(), title, 'app.media.app_icon'));
423    }
424
425    for (let i = 0; i < 50; i++) {
426      let title = i + 1 + "test_if";
427      this.dataSource.pushData(new FriendMoment(i.toString(), title, ''));
428    }
429  }
430
431  build() {
432    Column() {
433      // TopBar()
434      List({ space: 3 }) {
435        LazyForEach(this.dataSource, (moment: FriendMoment) => {
436          ListItem() {
437            // Use reuseId to control component reuse.
438            OneMoment({ moment: moment })
439              .reuseId((moment.image !== '') ? 'withImage' : 'noImage')
440          }
441        }, (moment: FriendMoment) => moment.id)
442      }
443      .cachedCount(0)
444    }
445  }
446}
447
448class FriendMoment {
449  id: string = '';
450  text: string = '';
451  title: string = '';
452  image: string = '';
453  answers: Array<ResourceStr> = [];
454
455  constructor(id: string, title: string, image: string) {
456    this.text = id;
457    this.title = title;
458    this.image = image;
459  }
460}
461
462@Reusable
463@Component
464export struct OneMoment {
465  @Prop moment: FriendMoment;
466
467  // The reuse can be triggered only when the reuse ID is the same.
468  aboutToReuse(params: ESObject): void {
469    console.log("=====aboutToReuse====OneMoment==reused==" + this.moment.text);
470  }
471
472  build() {
473    Column() {
474      Text(this.moment.text)
475      // if branch judgment
476      if (this.moment.image !== '') {
477        Flex({ wrap: FlexWrap.Wrap }) {
478          Image($r(this.moment.image)).height(50).width(50)
479          Image($r(this.moment.image)).height(50).width(50)
480          Image($r(this.moment.image)).height(50).width(50)
481          Image($r(this.moment.image)).height(50).width(50)
482        }
483      }
484    }
485  }
486}
487
488class BasicDataSource<T> implements IDataSource {
489  private listeners: DataChangeListener[] = [];
490  private originDataArray: T[] = [];
491
492  public totalCount(): number {
493    return 0;
494  }
495
496  public getData(index: number): T {
497    return this.originDataArray[index];
498  }
499
500  registerDataChangeListener(listener: DataChangeListener): void {
501    if (this.listeners.indexOf(listener) < 0) {
502      this.listeners.push(listener);
503    }
504  }
505
506  unregisterDataChangeListener(listener: DataChangeListener): void {
507    const pos = this.listeners.indexOf(listener);
508    if (pos >= 0) {
509      this.listeners.splice(pos, 1);
510    }
511  }
512
513  notifyDataAdd(index: number): void {
514    this.listeners.forEach(listener => {
515      listener.onDataAdd(index);
516    });
517  }
518}
519
520export class MyDataSource<T> extends BasicDataSource<T> {
521  private dataArray: T[] = [];
522
523  public totalCount(): number {
524    return this.dataArray.length;
525  }
526
527  public getData(index: number): T {
528    return this.dataArray[index];
529  }
530
531  public pushData(data: T): void {
532    this.dataArray.push(data);
533    this.notifyDataAdd(this.dataArray.length - 1);
534  }
535}
536```
537
538### Foreach
539
540- When **Foreach** is used to create a reusable custom component, component reuse cannot be triggered due to the full expansion attribute of **Foreach**. In the following example, after **update** is clicked, the data is refreshed successfully, but **ListItemView** cannot be reused.
541- Click **clear** and then click **update** again. **ListItemView** is successfully reused because multiple destroyed custom components are repeatedly created in a frame.
542
543```ts
544// xxx.ets
545class MyDataSource implements IDataSource {
546  private dataArray: string[] = [];
547
548  public totalCount(): number {
549    return this.dataArray.length;
550  }
551
552  public getData(index: number): string {
553    return this.dataArray[index];
554  }
555
556  public pushData(data: string): void {
557    this.dataArray.push(data);
558  }
559
560  public registerDataChangeListener(listener: DataChangeListener): void {
561  }
562
563  public unregisterDataChangeListener(listener: DataChangeListener): void {
564  }
565}
566
567@Entry
568@Component
569struct Index {
570  private data: MyDataSource = new MyDataSource();
571  private data02: MyDataSource = new MyDataSource();
572  @State isShow: boolean = true;
573  @State dataSource: ListItemObject[] = [];
574
575  aboutToAppear() {
576    for (let i = 0; i < 100; i++) {
577      this.data.pushData(i.toString());
578    }
579
580    for (let i = 30; i < 80; i++) {
581      this.data02.pushData(i.toString());
582    }
583  }
584
585  build() {
586    Column() {
587      Row() {
588        Button('clear').onClick(() => {
589          for (let i = 1; i < 50; i++) {
590            let obj = new ListItemObject();
591            obj.id = i;
592            obj.uuid = Math.random().toString();
593            obj.isExpand = false;
594            this.dataSource.pop();
595          }
596        }).height(40)
597
598        Button('update').onClick(() => {
599          for (let i = 1; i < 50; i++) {
600            let obj = new ListItemObject();
601            obj.id = i;
602            obj.uuid = Math.random().toString();
603            obj.isExpand = false;
604            this.dataSource.push(obj);
605          }
606        }).height(40)
607      }
608
609      List({ space: 10 }) {
610        ForEach(this.dataSource, (item: ListItemObject) => {
611          ListItem() {
612            ListItemView({
613              obj: item
614            })
615          }
616        }, (item: ListItemObject) => {
617          return item.uuid.toString();
618        })
619
620      }.cachedCount(0)
621      .width('100%')
622      .height('100%')
623    }
624  }
625}
626
627@Reusable
628@Component
629struct ListItemView {
630  @ObjectLink obj: ListItemObject;
631  @State item: string = '';
632
633  aboutToAppear(): void {
634    // Click update and scroll the list. The components cannot be reused because of the full expansion attribute of Foreach.
635    console.log("=====aboutToAppear=====ListItemView==created==" + this.item);
636  }
637
638  aboutToReuse(params: ESObject) {
639    this.item = params.item;
640    // Click clear and update and the reuse is successful,
641    // because multiple destroyed custom components are repeatedly created in a frame.
642    console.log("=====aboutToReuse====ListItemView==reused==" + this.item);
643  }
644
645  build() {
646    Column({ space: 10 }) {
647      Text('${this.obj.id}.Title')
648        .fontSize(16)
649        .fontColor('#000000')
650        .padding({
651          top: 20,
652          bottom: 20,
653        })
654
655      if (this.obj.isExpand) {
656        Text('')
657          .fontSize(14)
658          .fontColor('#999999')
659      }
660    }
661    .width('100%')
662    .borderRadius(10)
663    .backgroundColor(Color.White)
664    .padding(15)
665    .onClick(() => {
666      this.obj.isExpand = !this.obj.isExpand;
667    })
668  }
669}
670
671@Observed
672class ListItemObject {
673  uuid: string = "";
674  id: number = 0;
675  isExpand: boolean = false;
676}
677```
678
679### Grid
680
681- In the following example, the @Reusable decorator is used to decorate the custom component **ReusableChildComponent** in **GridItem**, indicating that the component can be reused.
682- **aboutToReuse** is used to trigger **Grid** before it is added from the reuse cache to the component tree during scrolling and update the component state variable to display the correct content.
683- Note that you do not need to update the state variables decorated by \@Link, \@StorageLink, \@ObjectLink, and \@Consume in **aboutToReuse**. These state variables are automatically updated, and manual update may trigger unnecessary component re-renders.
684
685```ts
686// Class MyDataSource implements the IDataSource API.
687class MyDataSource implements IDataSource {
688  private dataArray: number[] = [];
689
690  public pushData(data: number): void {
691    this.dataArray.push(data);
692  }
693
694  // Total data amount of the data source
695  public totalCount(): number {
696    return this.dataArray.length;
697  }
698
699  // Return the data with the specified index.
700  public getData(index: number): number {
701    return this.dataArray[index];
702  }
703
704  registerDataChangeListener(listener: DataChangeListener): void {
705  }
706
707  unregisterDataChangeListener(listener: DataChangeListener): void {
708  }
709}
710
711@Entry
712@Component
713struct MyComponent {
714  // Data source
715  private data: MyDataSource = new MyDataSource();
716
717  aboutToAppear() {
718    for (let i = 1; i < 1000; i++) {
719      this.data.pushData(i);
720    }
721  }
722
723  build() {
724    Column({ space: 5 }) {
725      Grid() {
726        LazyForEach(this.data, (item: number) => {
727          GridItem() {
728            // Use reusable custom components.
729            ReusableChildComponent({ item: item })
730          }
731        }, (item: string) => item)
732      }
733      .cachedCount(2) // Set the number of cached GridItems.
734      .columnsTemplate('1fr 1fr 1fr')
735      .columnsGap(10)
736      .rowsGap(10)
737      .margin(10)
738      .height(500)
739      .backgroundColor(0xFAEEE0)
740    }
741  }
742}
743
744@Reusable
745@Component
746struct ReusableChildComponent {
747  @State item: number = 0;
748
749  // aboutToReuse is called when a reusable custom component is added to the component tree from the reuse cache. The component's state variables can be updated here to display the correct content.
750  // The aboutToReuse parameter does not support any and Record is used to specify a data type. Record is used to create an object type, of which the attribute key is Keys and the attribute value is Type.
751  aboutToReuse(params: Record<string, number>) {
752    this.item = params.item;
753  }
754
755  build() {
756    Column() {
757      // Add the app.media.app_icon image to the src/main/resources/base/media directory. Otherwise, an error will be reported due to missing resources.
758      Image($r('app.media.app_icon'))
759        .objectFit(ImageFit.Fill)
760        .layoutWeight(1)
761      Text(`Image${this.item}`)
762        .fontSize(16)
763        .textAlign(TextAlign.Center)
764    }
765    .width('100%')
766    .height(120)
767    .backgroundColor(0xF9CF93)
768  }
769}
770```
771
772### WaterFlow
773
774- In the **WaterFlow** scrolling scenario, **FlowItem** and its child components are frequently created and destroyed. You can encapsulate the components in **FlowItem** into custom components and decorate them using \@Reusable so that these components can be reused.
775
776```ts
777class WaterFlowDataSource implements IDataSource {
778  private dataArray: number[] = [];
779  private listeners: DataChangeListener[] = [];
780
781  constructor() {
782    for (let i = 0; i <= 60; i++) {
783      this.dataArray.push(i);
784    }
785  }
786
787  // Obtain the data corresponding to the specified index.
788  public getData(index: number): number {
789    return this.dataArray[index];
790  }
791
792  // Notify the controller to add data.
793  notifyDataAdd(index: number): void {
794    this.listeners.forEach(listener => {
795      listener.onDataAdd(index);
796    });
797  }
798
799  // Obtain the total number of data records.
800  public totalCount(): number {
801    return this.dataArray.length;
802  }
803
804  // Register the data change listener.
805  registerDataChangeListener(listener: DataChangeListener): void {
806    if (this.listeners.indexOf(listener) < 0) {
807      this.listeners.push(listener);
808    }
809  }
810
811  // Unregister the data change listener.
812  unregisterDataChangeListener(listener: DataChangeListener): void {
813    const pos = this.listeners.indexOf(listener);
814    if (pos >= 0) {
815      this.listeners.splice(pos, 1);
816    }
817  }
818
819  // Add an item to the end of the data.
820  public addLastItem(): void {
821    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length);
822    this.notifyDataAdd(this.dataArray.length - 1);
823  }
824}
825
826@Reusable
827@Component
828struct ReusableFlowItem {
829  @State item: number = 0;
830
831  // Invoked when a reusable custom component is added to the component tree from the reuse cache. The component's state variable can be updated here to display the correct content.
832  aboutToReuse(params: ESObject) {
833    this.item = params.item;
834    console.log("=====aboutToReuse====FlowItem==reused==" + this.item);
835  }
836
837  aboutToRecycle(): void {
838    console.log("=====aboutToRecycle====FlowItem==recycled==" + this.item);
839  }
840
841  build() {
842    // Add the app.media.app_icon image to the src/main/resources/base/media directory. Otherwise, an error will be reported due to missing resources.
843    Column() {
844      Text("N" + this.item).fontSize(24).height('26').margin(10)
845      Image($r('app.media.app_icon'))
846        .objectFit(ImageFit.Cover)
847        .width(50)
848        .height(50)
849    }
850  }
851}
852
853@Entry
854@Component
855struct Index {
856  @State minSize: number = 50;
857  @State maxSize: number = 80;
858  @State fontSize: number = 24;
859  @State colors: number[] = [0xFFC0CB, 0xDA70D6, 0x6B8E23, 0x6A5ACD, 0x00FFFF, 0x00FF7F];
860  scroller: Scroller = new Scroller();
861  dataSource: WaterFlowDataSource = new WaterFlowDataSource();
862  private itemWidthArray: number[] = [];
863  private itemHeightArray: number[] = [];
864
865  // Calculate the width and height of the flow item.
866  getSize() {
867    let ret = Math.floor(Math.random() * this.maxSize);
868    return (ret > this.minSize ? ret : this.minSize);
869  }
870
871  // Save the width and height of the flow item.
872  getItemSizeArray() {
873    for (let i = 0; i < 100; i++) {
874      this.itemWidthArray.push(this.getSize());
875      this.itemHeightArray.push(this.getSize());
876    }
877  }
878
879  aboutToAppear() {
880    this.getItemSizeArray();
881  }
882
883  build() {
884    Stack({ alignContent: Alignment.TopStart }) {
885      Column({ space: 2 }) {
886        Button('back top')
887          .height('5%')
888          .onClick(() => { // Back to the top once clicked.
889            this.scroller.scrollEdge(Edge.Top);
890          })
891        WaterFlow({ scroller: this.scroller }) {
892          LazyForEach(this.dataSource, (item: number) => {
893            FlowItem() {
894              ReusableFlowItem({ item: item })
895            }.onAppear(() => {
896              if (item + 20 == this.dataSource.totalCount()) {
897                for (let i = 0; i < 50; i++) {
898                  this.dataSource.addLastItem();
899                }
900              }
901            })
902
903          })
904        }
905      }
906    }
907  }
908
909  @Builder
910  itemFoot() {
911    Column() {
912      Text(`Footer`)
913        .fontSize(10)
914        .backgroundColor(Color.Red)
915        .width(50)
916        .height(50)
917        .align(Alignment.Center)
918        .margin({ top: 2 })
919    }
920  }
921}
922```
923
924### Swiper
925
926- In the **Swiper** scrolling scenario, child components are frequently created and destroyed in an item. You can encapsulate the child components in the item into custom components and use \@Reusable to decorate the custom components so that they can be reused.
927
928```ts
929@Entry
930@Component
931struct Index {
932  private dataSource = new MyDataSource<Question>();
933
934  aboutToAppear(): void {
935    for (let i = 0; i < 1000; i++) {
936      let title = i + 1 + "test_swiper";
937      let answers = ["test1", "test2", "test3",
938        "test4"];
939      // Add the app.media.app_icon image to the src/main/resources/base/media directory. Otherwise, an error will be reported due to missing resources.
940      this.dataSource.pushData(new Question(i.toString(), title, $r('app.media.app_icon'), answers));
941    }
942  }
943
944  build() {
945    Column({ space: 5 }) {
946      Swiper() {
947        LazyForEach(this.dataSource, (item: Question) => {
948          QuestionSwiperItem({ itemData: item })
949        }, (item: Question) => item.id)
950      }
951    }
952    .width('100%')
953    .margin({ top: 5 })
954  }
955}
956
957class Question {
958  id: string = '';
959  title: ResourceStr = '';
960  image: ResourceStr = '';
961  answers: Array<ResourceStr> = [];
962
963  constructor(id: string, title: ResourceStr, image: ResourceStr, answers: Array<ResourceStr>) {
964    this.id = id;
965    this.title = title;
966    this.image = image;
967    this.answers = answers;
968  }
969}
970
971@Reusable
972@Component
973struct QuestionSwiperItem {
974  @State itemData: Question | null = null;
975
976  aboutToReuse(params: Record<string, Object>): void {
977    this.itemData = params.itemData as Question;
978    console.info("===test===aboutToReuse====QuestionSwiperItem==");
979  }
980
981  build() {
982    Column() {
983      Text(this.itemData?.title)
984        .fontSize(18)
985        .fontColor($r('sys.color.ohos_id_color_primary'))
986        .alignSelf(ItemAlign.Start)
987        .margin({
988          top: 10,
989          bottom: 16
990        })
991      Image(this.itemData?.image)
992        .width('100%')
993        .borderRadius(12)
994        .objectFit(ImageFit.Contain)
995        .margin({
996          bottom: 16
997        })
998        .height(80)
999        .width(80)
1000
1001      Column({ space: 16 }) {
1002        ForEach(this.itemData?.answers, (item: Resource) => {
1003          Text(item)
1004            .fontSize(16)
1005            .fontColor($r('sys.color.ohos_id_color_primary'))
1006        }, (item: ResourceStr) => JSON.stringify(item))
1007      }
1008      .width('100%')
1009      .alignItems(HorizontalAlign.Start)
1010    }
1011    .width('100%')
1012    .padding({
1013      left: 16,
1014      right: 16
1015    })
1016  }
1017}
1018
1019class BasicDataSource<T> implements IDataSource {
1020  private listeners: DataChangeListener[] = [];
1021  private originDataArray: T[] = [];
1022
1023  public totalCount(): number {
1024    return 0;
1025  }
1026
1027  public getData(index: number): T {
1028    return this.originDataArray[index];
1029  }
1030
1031  registerDataChangeListener(listener: DataChangeListener): void {
1032    if (this.listeners.indexOf(listener) < 0) {
1033      this.listeners.push(listener);
1034    }
1035  }
1036
1037  unregisterDataChangeListener(listener: DataChangeListener): void {
1038    const pos = this.listeners.indexOf(listener);
1039    if (pos >= 0) {
1040      this.listeners.splice(pos, 1);
1041    }
1042  }
1043
1044  notifyDataAdd(index: number): void {
1045    this.listeners.forEach(listener => {
1046      listener.onDataAdd(index);
1047    });
1048  }
1049}
1050
1051export class MyDataSource<T> extends BasicDataSource<T> {
1052  private dataArray: T[] = [];
1053
1054  public totalCount(): number {
1055    return this.dataArray.length;
1056  }
1057
1058  public getData(index: number): T {
1059    return this.dataArray[index];
1060  }
1061
1062  public pushData(data: T): void {
1063    this.dataArray.push(data);
1064    this.notifyDataAdd(this.dataArray.length - 1);
1065  }
1066}
1067```
1068
1069### ListItemGroup
1070
1071- This case can be regarded as a special **List** scrolling scenario. Encapsulate the child component of **ListItem** that needs to be destroyed and re-created into a custom component and use \@Reusable to decorate the custom component so that the custom component can be reused.
1072
1073```ts
1074@Entry
1075@Component
1076struct ListItemGroupAndReusable {
1077  data: DataSrc2 = new DataSrc2();
1078
1079  @Builder
1080  itemHead(text: string) {
1081    Text(text)
1082      .fontSize(20)
1083      .backgroundColor(0xAABBCC)
1084      .width('100%')
1085      .padding(10)
1086  }
1087
1088  aboutToAppear() {
1089    for (let i = 0; i < 10000; i++) {
1090      let data_1 = new DataSrc1();
1091      for (let j = 0; j < 12; j++) {
1092        data_1.Data.push('Test item data: ${i} - ${j}');
1093      }
1094      this.data.Data.push(data_1);
1095    }
1096  }
1097
1098  build() {
1099    Stack() {
1100      List() {
1101        LazyForEach(this.data, (item: DataSrc1, index: number) => {
1102          ListItemGroup({ header: this.itemHead(index.toString()) }) {
1103            LazyForEach(item, (ii: string, index: number) => {
1104              ListItem() {
1105                Inner({ str: ii })
1106              }
1107            })
1108          }
1109          .width('100%')
1110          .height('60vp')
1111        })
1112      }
1113    }
1114    .width('100%')
1115    .height('100%')
1116  }
1117}
1118
1119@Reusable
1120@Component
1121struct Inner {
1122  @State str: string = '';
1123
1124  aboutToReuse(param: ESObject) {
1125    this.str = param.str;
1126  }
1127
1128  build() {
1129    Text(this.str)
1130  }
1131}
1132
1133class DataSrc1 implements IDataSource {
1134  listeners: DataChangeListener[] = [];
1135  Data: string[] = [];
1136
1137  public totalCount(): number {
1138    return this.Data.length;
1139  }
1140
1141  public getData(index: number): string {
1142    return this.Data[index];
1143  }
1144
1145  // This method is called by the framework to register a listener to the LazyForEach data source.
1146  registerDataChangeListener(listener: DataChangeListener): void {
1147    if (this.listeners.indexOf(listener) < 0) {
1148      this.listeners.push(listener);
1149    }
1150  }
1151
1152  // This method is called by the framework to unregister the listener from the LazyForEach data source.
1153  unregisterDataChangeListener(listener: DataChangeListener): void {
1154    const pos = this.listeners.indexOf(listener);
1155    if (pos >= 0) {
1156      this.listeners.splice(pos, 1);
1157    }
1158  }
1159
1160  // Notify LazyForEach that all child components need to be reloaded.
1161  notifyDataReload(): void {
1162    this.listeners.forEach(listener => {
1163      listener.onDataReloaded();
1164    });
1165  }
1166
1167  // Notify LazyForEach that a child component needs to be added for the data item with the specified index.
1168  notifyDataAdd(index: number): void {
1169    this.listeners.forEach(listener => {
1170      listener.onDataAdd(index);
1171    });
1172  }
1173
1174  // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt.
1175  notifyDataChange(index: number): void {
1176    this.listeners.forEach(listener => {
1177      listener.onDataChange(index);
1178    });
1179  }
1180
1181  // Notify LazyForEach that the child component needs to be deleted from the data item with the specified index.
1182  notifyDataDelete(index: number): void {
1183    this.listeners.forEach(listener => {
1184      listener.onDataDelete(index);
1185    });
1186  }
1187
1188  // Notify LazyForEach that data needs to be swapped between the from and to positions.
1189  notifyDataMove(from: number, to: number): void {
1190    this.listeners.forEach(listener => {
1191      listener.onDataMove(from, to);
1192    });
1193  }
1194}
1195
1196class DataSrc2 implements IDataSource {
1197  listeners: DataChangeListener[] = [];
1198  Data: DataSrc1[] = [];
1199
1200  public totalCount(): number {
1201    return this.Data.length;
1202  }
1203
1204  public getData(index: number): DataSrc1 {
1205    return this.Data[index];
1206  }
1207
1208  // This method is called by the framework to register a listener to the LazyForEach data source.
1209  registerDataChangeListener(listener: DataChangeListener): void {
1210    if (this.listeners.indexOf(listener) < 0) {
1211      this.listeners.push(listener);
1212    }
1213  }
1214
1215  // This method is called by the framework to unregister the listener from the LazyForEach data source.
1216  unregisterDataChangeListener(listener: DataChangeListener): void {
1217    const pos = this.listeners.indexOf(listener);
1218    if (pos >= 0) {
1219      this.listeners.splice(pos, 1);
1220    }
1221  }
1222
1223  // Notify LazyForEach that all child components need to be reloaded.
1224  notifyDataReload(): void {
1225    this.listeners.forEach(listener => {
1226      listener.onDataReloaded();
1227    });
1228  }
1229
1230  // Notify LazyForEach that a child component needs to be added for the data item with the specified index.
1231  notifyDataAdd(index: number): void {
1232    this.listeners.forEach(listener => {
1233      listener.onDataAdd(index);
1234    });
1235  }
1236
1237  // Notify LazyForEach that the data item with the specified index has changed and the child component needs to be rebuilt.
1238  notifyDataChange(index: number): void {
1239    this.listeners.forEach(listener => {
1240      listener.onDataChange(index);
1241    });
1242  }
1243
1244  // Notify LazyForEach that the child component needs to be deleted from the data item with the specified index.
1245  notifyDataDelete(index: number): void {
1246    this.listeners.forEach(listener => {
1247      listener.onDataDelete(index);
1248    });
1249  }
1250
1251  // Notify LazyForEach that data needs to be swapped between the from and to positions.
1252  notifyDataMove(from: number, to: number): void {
1253    this.listeners.forEach(listener => {
1254      listener.onDataMove(from, to);
1255    });
1256  }
1257}
1258```
1259
1260
1261### Multiple Item Types
1262
1263#### Standard
1264
1265- Reusable components have the same layouts.
1266- For the sample code of this type, see section "List Scrolling Used with LazyForEach".
1267
1268#### Limited
1269
1270- Types of different reusable components are limited.
1271- In the following example, two reuse IDs are explicitly set for reusing two custom components.
1272
1273```ts
1274class MyDataSource implements IDataSource {
1275  private dataArray: string[] = [];
1276  private listener: DataChangeListener | undefined;
1277
1278  public totalCount(): number {
1279    return this.dataArray.length;
1280  }
1281
1282  public getData(index: number): string {
1283    return this.dataArray[index];
1284  }
1285
1286  public pushData(data: string): void {
1287    this.dataArray.push(data);
1288  }
1289
1290  public reloadListener(): void {
1291    this.listener?.onDataReloaded();
1292  }
1293
1294  public registerDataChangeListener(listener: DataChangeListener): void {
1295    this.listener = listener;
1296  }
1297
1298  public unregisterDataChangeListener(listener: DataChangeListener): void {
1299    this.listener = undefined;
1300  }
1301}
1302
1303@Entry
1304@Component
1305struct Index {
1306  private data: MyDataSource = new MyDataSource();
1307
1308  aboutToAppear() {
1309    for (let i = 0; i < 1000; i++) {
1310      this.data.pushData(i + "");
1311    }
1312  }
1313
1314  build() {
1315    Column() {
1316      List({ space: 10 }) {
1317        LazyForEach(this.data, (item: number) => {
1318          ListItem() {
1319            ReusableComponent({ item: item })
1320              .reuseId(item % 2 === 0 ? 'ReusableComponentOne' : 'ReusableComponentTwo')
1321          }
1322          .backgroundColor(Color.Orange)
1323          .width('100%')
1324        }, (item: number) => item.toString())
1325      }
1326      .cachedCount(2)
1327    }
1328  }
1329}
1330
1331@Reusable
1332@Component
1333struct ReusableComponent {
1334  @State item: number = 0;
1335
1336  aboutToReuse(params: ESObject) {
1337    this.item = params.item;
1338  }
1339
1340  build() {
1341    Column() {
1342      if (this.item % 2 === 0) {
1343        Text(`Item ${this.item} ReusableComponentOne`)
1344          .fontSize(20)
1345          .margin({ left: 10 })
1346      } else {
1347        Text(`Item ${this.item} ReusableComponentTwo`)
1348          .fontSize(20)
1349          .margin({ left: 10 })
1350      }
1351    }.margin({ left: 10, right: 10 })
1352  }
1353}
1354```
1355
1356#### Composite
1357
1358- Different reusable components have common child components.
1359- Based on the composite component reuse, after the three reusable components are converted into the **Builder** function, the common child components are under the same parent component **MyComponent**.
1360- When you reuse these child components, their cache pools are also shared in the parent component, reducing the consumption during component creation.
1361
1362```ts
1363class MyDataSource implements IDataSource {
1364  private dataArray: string[] = [];
1365  private listener: DataChangeListener | undefined;
1366
1367  public totalCount(): number {
1368    return this.dataArray.length;
1369  }
1370
1371  public getData(index: number): string {
1372    return this.dataArray[index];
1373  }
1374
1375  public pushData(data: string): void {
1376    this.dataArray.push(data);
1377  }
1378
1379  public reloadListener(): void {
1380    this.listener?.onDataReloaded();
1381  }
1382
1383  public registerDataChangeListener(listener: DataChangeListener): void {
1384    this.listener = listener;
1385  }
1386
1387  public unregisterDataChangeListener(listener: DataChangeListener): void {
1388    this.listener = undefined;
1389  }
1390}
1391
1392@Entry
1393@Component
1394struct MyComponent {
1395  private data: MyDataSource = new MyDataSource();
1396
1397  aboutToAppear() {
1398    for (let i = 0; i < 1000; i++) {
1399      this.data.pushData(i.toString());
1400    }
1401  }
1402
1403  // Convert itemBuilderOne to Builder.
1404  @Builder
1405  itemBuilderOne(item: string) {
1406    Column() {
1407      ChildComponentA({ item: item })
1408      ChildComponentB({ item: item })
1409      ChildComponentC({ item: item })
1410    }
1411  }
1412
1413  // Convert itemBuilderTwo to Builder.
1414  @Builder
1415  itemBuilderTwo(item: string) {
1416    Column() {
1417      ChildComponentA({ item: item })
1418      ChildComponentC({ item: item })
1419      ChildComponentD({ item: item })
1420    }
1421  }
1422
1423  // Convert itemBuilderThree to Builder.
1424  @Builder
1425  itemBuilderThree(item: string) {
1426    Column() {
1427      ChildComponentA({ item: item })
1428      ChildComponentB({ item: item })
1429      ChildComponentD({ item: item })
1430    }
1431  }
1432
1433  build() {
1434    List({ space: 40 }) {
1435      LazyForEach(this.data, (item: string, index: number) => {
1436        ListItem() {
1437          if (index % 3 === 0) {
1438            this.itemBuilderOne(item)
1439          } else if (index % 5 === 0) {
1440            this.itemBuilderTwo(item)
1441          } else {
1442            this.itemBuilderThree(item)
1443          }
1444        }
1445        .backgroundColor('#cccccc')
1446        .width('100%')
1447        .onAppear(() => {
1448          console.log(`ListItem ${index} onAppear`);
1449        })
1450      }, (item: number) => item.toString())
1451    }
1452    .width('100%')
1453    .height('100%')
1454    .cachedCount(0)
1455  }
1456}
1457
1458@Reusable
1459@Component
1460struct ChildComponentA {
1461  @State item: string = '';
1462
1463  aboutToReuse(params: ESObject) {
1464    console.log(`ChildComponentA ${params.item} Reuse ${this.item}`);
1465    this.item = params.item;
1466  }
1467
1468  aboutToRecycle(): void {
1469    console.log(`ChildComponentA ${this.item} Recycle`);
1470  }
1471
1472  build() {
1473    Column() {
1474      Text(`Item ${this.item} Child Component A`)
1475        .fontSize(20)
1476        .margin({ left: 10 })
1477        .fontColor(Color.Blue)
1478      Grid() {
1479        ForEach((new Array(20)).fill(''), (item: string, index: number) => {
1480          GridItem() {
1481            // Add the app.media.startIcon image to the src/main/resources/base/media directory. Otherwise, an error will be reported due to missing resources.
1482            Image($r('app.media.startIcon'))
1483              .height(20)
1484          }
1485        })
1486      }
1487      .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
1488      .rowsTemplate('1fr 1fr 1fr 1fr')
1489      .columnsGap(10)
1490      .width('90%')
1491      .height(160)
1492    }
1493    .margin({ left: 10, right: 10 })
1494    .backgroundColor(0xFAEEE0)
1495  }
1496}
1497
1498@Reusable
1499@Component
1500struct ChildComponentB {
1501  @State item: string = '';
1502
1503  aboutToReuse(params: ESObject) {
1504    this.item = params.item;
1505  }
1506
1507  build() {
1508    Row() {
1509      Text(`Item ${this.item} Child Component B`)
1510        .fontSize(20)
1511        .margin({ left: 10 })
1512        .fontColor(Color.Red)
1513    }.margin({ left: 10, right: 10 })
1514  }
1515}
1516
1517@Reusable
1518@Component
1519struct ChildComponentC {
1520  @State item: string = '';
1521
1522  aboutToReuse(params: ESObject) {
1523    this.item = params.item;
1524  }
1525
1526  build() {
1527    Row() {
1528      Text(`Item ${this.item} Child Component C`)
1529        .fontSize(20)
1530        .margin({ left: 10 })
1531        .fontColor(Color.Green)
1532    }.margin({ left: 10, right: 10 })
1533  }
1534}
1535
1536@Reusable
1537@Component
1538struct ChildComponentD {
1539  @State item: string = '';
1540
1541  aboutToReuse(params: ESObject) {
1542    this.item = params.item;
1543  }
1544
1545  build() {
1546    Row() {
1547      Text(`Item ${this.item} Child Component D`)
1548        .fontSize(20)
1549        .margin({ left: 10 })
1550        .fontColor(Color.Orange)
1551    }.margin({ left: 10, right: 10 })
1552  }
1553}
1554```
1555