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