1# High-Performance WaterFlow Development
2
3## Background
4
5The waterfall layout is a popular layout for presenting images and frequently seen in shopping and information applications. It is implemented using the [WaterFlow](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md) component in ArkUI. This document discusses how to improve the **WaterFlow** performance, with practical examples.
6
7## Using Lazy Loading
8
9Below shows the basic usage of the **WaterFlow** component.
10
11```ts
12  build() {
13    Column({ space: 2 }) {
14      WaterFlow() {
15        LazyForEach(this.datasource, (item: number) => {
16          FlowItem() {
17            Column() {
18              Text("N" + item).fontSize(12).height('16')
19              Image('res/waterFlowTest (' + item % 5 + ').jpg')
20                .objectFit(ImageFit.Fill)
21                .width('100%')
22                .layoutWeight(1)
23            }
24          }
25          .width('100%')
26          // Set the <FlowItem> height to avoid the need to adapt to image heights.
27          .height(this.itemHeightArray[item])
28          .backgroundColor(this.colors[item % 5])
29        }, (item: string) => item)
30      }
31      .columnsTemplate("1fr 1fr")
32      .columnsGap(10)
33      .rowsGap(5)
34      .backgroundColor(0xFAEEE0)
35      .width('100%')
36      .height('80%')
37    }
38  }
39```
40
41In the sample code, [LazyForEach](../quick-start/arkts-rendering-control-lazyforeach.md) is used for lazy loading. During the layout of the **WaterFlow** component, the **FlowItem** components are created as needed based on the visible area; those that extend beyond the visible area are destroyed to reduce memory usage.
42
43Considering that **Image** components are loaded asynchronously by default, you are advised to set the height for **FlowItem** components based on the image size, to avoid layout re-render caused by **FlowItem** components' changing heights to accommodate images.
44
45## Implementing Infinite Scrolling
46
47In the example, the fixed number of **FlowItem** components results in failure to achieve infinite scrolling.
48
49To implement infinite scrolling with the capabilities provided by the **WaterFlow** component, you can new data to the **LazyForEach** data source during **onReachEnd**, and set the footer to the loading-new-data style (by using the [\<LoadingProgress>](../reference/apis-arkui/arkui-ts/ts-basic-components-loadingprogress.md) component).
50
51```ts
52  build() {
53    Column({ space: 2 }) {
54      WaterFlow({ footer: this.itemFoot.bind(this) }) {
55        LazyForEach(this.datasource, (item: number) => {
56          FlowItem() {
57            Column() {
58              Text("N" + item).fontSize(12).height('16')
59              Image('res/waterFlowTest (' + item % 5 + ').jpg')
60                .objectFit(ImageFit.Fill)
61                .width('100%')
62                .layoutWeight(1)
63            }
64          }
65          .width('100%')
66          .height(this.itemHeightArray[item % 100])
67          .backgroundColor(this.colors[item % 5])
68        }, (item: string) => item)
69      }
70      // Load data once the scrolling reaches the end of the page.
71      .onReachEnd(() => {
72        console.info("onReachEnd")
73        setTimeout(() => {
74          for (let i = 0; i < 100; i++) {
75            this.datasource.AddLastItem()
76          }
77        }, 1000)
78      })
79      .columnsTemplate("1fr 1fr")
80      .columnsGap(10)
81      .rowsGap(5)
82      .backgroundColor(0xFAEEE0)
83      .width('100%')
84      .height('80%')
85    }
86  }
87
88  // Add an item to the end of the data.
89  public AddLastItem(): void {
90    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
91    this.notifyDataAdd(this.dataArray.length - 1)
92  }
93```
94
95To add new data, you must add an item to the end of the data. Do not directly modify the data array and use **onDataReloaded()** of **LazyForEach** to instruct the **WaterFlow** component to reload data.
96
97Because the heights of the child components in **WaterFlow** vary, and the position of a lower child component depends on its upper one, reloading all data in **WaterFlow** will trigger waterfall layout recalculation, causing frame freezing. In comparison, if you add new data by adding an item to the end of the data and then call **notifyDataAdd(this.dataArray.length - 1)**, the **WaterFlow** component loads new data, without processing existing data repeatedly.
98
99![](figures/waterflow-perf-demo1.gif)
100
101## Adding Data in Advance
102
103Although infinite scrolling can be achieved through triggering of **onReachEnd()** upon new data, there may be an obvious pause in the process of loading new data when the user scrolls to the bottom.
104
105To create a smooth scrolling experience, you need to adjust the time for adding new data. For example, you can add some new data in advance when the **LazyForEach** data source still has several pieces of data left before iteration ends.
106
107```ts
108  build() {
109    Column({ space: 2 }) {
110      WaterFlow() {
111        LazyForEach(this.datasource, (item: number) => {
112          FlowItem() {
113            Column() {
114              Text("N" + item).fontSize(12).height('16')
115              Image('res/waterFlowTest (' + item % 5 + ').jpg')
116                .objectFit(ImageFit.Fill)
117                .width('100%')
118                .layoutWeight(1)
119            }
120          }
121          .onAppear(() => {
122            // Add data in advance when scrolling is about to end.
123            if (item + 20 == this.datasource.totalCount()) {
124              for (let i = 0; i < 100; i++) {
125                this.datasource.AddLastItem()
126              }
127            }
128          })
129          .width('100%')
130          .height(this.itemHeightArray[item % 100])
131          .backgroundColor(this.colors[item % 5])
132        }, (item: string) => item)
133      }
134      .columnsTemplate("1fr 1fr")
135      .columnsGap(10)
136      .rowsGap(5)
137      .backgroundColor(0xFAEEE0)
138      .width('100%')
139      .height('80%')
140    }
141  }
142```
143
144In this example, the quantity of data items left till the end is determined in **onAppear** of **FlowItem**, and new data is added in advance to implement stutter-free infinite scrolling.
145
146![](figures/waterflow-perf-demo2.gif)
147
148## Reusing Components
149
150Now that we have a waterfall that scrolls infinitely without explicitly waiting for more data, we can further optimize its performance by making the components reusable.
151
152During scrolling, **FlowItem** and its child component are frequently created and destroyed. By encapsulating the component in **FlowItem** into a custom component and decorating it with the **@Reusable** decorator, you make the component reusable, reducing the overhead of repeatedly creating and destroying nodes in the ArkUI framework. For details about component reuse, see [Best Practices for Component Reuse](component-recycle.md).
153
154```ts
155  build() {
156    Column({ space: 2 }) {
157      WaterFlow() {
158        LazyForEach(this.datasource, (item: number) => {
159          FlowItem() {
160            // Use reusable custom components.
161            ResuableFlowItem({ item: item })
162          }
163          .onAppear(() => {
164            // Add data in advance when scrolling is about to end.
165            if (item + 20 == this.datasource.totalCount()) {
166              for (let i = 0; i < 100; i++) {
167                this.datasource.AddLastItem()
168              }
169            }
170          })
171          .width('100%')
172          .height(this.itemHeightArray[item % 100])
173          .backgroundColor(this.colors[item % 5])
174        }, (item: string) => item)
175      }
176      .columnsTemplate("1fr 1fr")
177      .columnsGap(10)
178      .rowsGap(5)
179      .backgroundColor(0xFAEEE0)
180      .width('100%')
181      .height('80%')
182    }
183  }
184@Reusable
185@Component
186struct ResuableFlowItem {
187  @State item: number = 0
188
189  // Invoked when a reusable custom component is re-added to the component tree from the reuse cache. The component state variable can be updated here to display the correct content.
190  aboutToReuse(params) {
191    this.item = params.item;
192  }
193
194  build() {
195    Column() {
196      Text("N" + this.item).fontSize(12).height('16')
197      Image('res/waterFlowTest (' + this.item % 5 + ').jpg')
198        .objectFit(ImageFit.Fill)
199        .width('100%')
200        .layoutWeight(1)
201    }
202  }
203}
204
205```
206
207## Takeaway
208
209To achieve the optimal performance in infinite scrolling, you can use **WaterFlow** with the **LazyForEach** rendering control syntax, ahead-of-time data addition, and component reuse.
210