1e41f4b71Sopenharmony_ci
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ci# High-Performance WaterFlow Development
4e41f4b71Sopenharmony_ci
5e41f4b71Sopenharmony_ci## Background
6e41f4b71Sopenharmony_ci
7e41f4b71Sopenharmony_ciThe 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.
8e41f4b71Sopenharmony_ci
9e41f4b71Sopenharmony_ci## Using Lazy Loading
10e41f4b71Sopenharmony_ci
11e41f4b71Sopenharmony_ciBelow shows the basic usage of the **\<WaterFlow>** component.
12e41f4b71Sopenharmony_ci
13e41f4b71Sopenharmony_ci```ts
14e41f4b71Sopenharmony_ci  build() {
15e41f4b71Sopenharmony_ci    Column({ space: 2 }) {
16e41f4b71Sopenharmony_ci      WaterFlow() {
17e41f4b71Sopenharmony_ci        LazyForEach(this.datasource, (item: number) => {
18e41f4b71Sopenharmony_ci          FlowItem() {
19e41f4b71Sopenharmony_ci            Column() {
20e41f4b71Sopenharmony_ci              Text("N" + item).fontSize(12).height('16')
21e41f4b71Sopenharmony_ci              Image('res/waterFlowTest (' + item % 5 + ').jpg')
22e41f4b71Sopenharmony_ci                .objectFit(ImageFit.Fill)
23e41f4b71Sopenharmony_ci                .width('100%')
24e41f4b71Sopenharmony_ci                .layoutWeight(1)
25e41f4b71Sopenharmony_ci            }
26e41f4b71Sopenharmony_ci          }
27e41f4b71Sopenharmony_ci          .width('100%')
28e41f4b71Sopenharmony_ci          // Set the <FlowItem> height to avoid the need to adapt to image heights. 
29e41f4b71Sopenharmony_ci          .height(this.itemHeightArray[item])
30e41f4b71Sopenharmony_ci          .backgroundColor(this.colors[item % 5])
31e41f4b71Sopenharmony_ci        }, (item: string) => item)
32e41f4b71Sopenharmony_ci      }
33e41f4b71Sopenharmony_ci      .columnsTemplate("1fr 1fr")
34e41f4b71Sopenharmony_ci      .columnsGap(10)
35e41f4b71Sopenharmony_ci      .rowsGap(5)
36e41f4b71Sopenharmony_ci      .backgroundColor(0xFAEEE0)
37e41f4b71Sopenharmony_ci      .width('100%')
38e41f4b71Sopenharmony_ci      .height('80%')
39e41f4b71Sopenharmony_ci    }
40e41f4b71Sopenharmony_ci  }
41e41f4b71Sopenharmony_ci```
42e41f4b71Sopenharmony_ci
43e41f4b71Sopenharmony_ciIn 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.
44e41f4b71Sopenharmony_ci
45e41f4b71Sopenharmony_ciConsidering 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.
46e41f4b71Sopenharmony_ci
47e41f4b71Sopenharmony_ci## Implementing Infinite Scrolling
48e41f4b71Sopenharmony_ci
49e41f4b71Sopenharmony_ciIn the example, the fixed number of **\<FlowItem>** components results in failure to achieve infinite scrolling.
50e41f4b71Sopenharmony_ci
51e41f4b71Sopenharmony_ciTo 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).
52e41f4b71Sopenharmony_ci
53e41f4b71Sopenharmony_ci```ts
54e41f4b71Sopenharmony_ci  build() {
55e41f4b71Sopenharmony_ci    Column({ space: 2 }) {
56e41f4b71Sopenharmony_ci      WaterFlow({ footer: this.itemFoot.bind(this) }) {
57e41f4b71Sopenharmony_ci        LazyForEach(this.datasource, (item: number) => {
58e41f4b71Sopenharmony_ci          FlowItem() {
59e41f4b71Sopenharmony_ci            Column() {
60e41f4b71Sopenharmony_ci              Text("N" + item).fontSize(12).height('16')
61e41f4b71Sopenharmony_ci              Image('res/waterFlowTest (' + item % 5 + ').jpg')
62e41f4b71Sopenharmony_ci                .objectFit(ImageFit.Fill)
63e41f4b71Sopenharmony_ci                .width('100%')
64e41f4b71Sopenharmony_ci                .layoutWeight(1)
65e41f4b71Sopenharmony_ci            }
66e41f4b71Sopenharmony_ci          }
67e41f4b71Sopenharmony_ci          .width('100%')
68e41f4b71Sopenharmony_ci          .height(this.itemHeightArray[item % 100])
69e41f4b71Sopenharmony_ci          .backgroundColor(this.colors[item % 5])
70e41f4b71Sopenharmony_ci        }, (item: string) => item)
71e41f4b71Sopenharmony_ci      }
72e41f4b71Sopenharmony_ci      // Load data once the scrolling reaches the end of the page. 
73e41f4b71Sopenharmony_ci      .onReachEnd(() => {
74e41f4b71Sopenharmony_ci        console.info("onReachEnd")
75e41f4b71Sopenharmony_ci        setTimeout(() => {
76e41f4b71Sopenharmony_ci          for (let i = 0; i < 100; i++) {
77e41f4b71Sopenharmony_ci            this.datasource.AddLastItem()
78e41f4b71Sopenharmony_ci          }
79e41f4b71Sopenharmony_ci        }, 1000)
80e41f4b71Sopenharmony_ci      })
81e41f4b71Sopenharmony_ci      .columnsTemplate("1fr 1fr")
82e41f4b71Sopenharmony_ci      .columnsGap(10)
83e41f4b71Sopenharmony_ci      .rowsGap(5)
84e41f4b71Sopenharmony_ci      .backgroundColor(0xFAEEE0)
85e41f4b71Sopenharmony_ci      .width('100%')
86e41f4b71Sopenharmony_ci      .height('80%')
87e41f4b71Sopenharmony_ci    }
88e41f4b71Sopenharmony_ci  }
89e41f4b71Sopenharmony_ci
90e41f4b71Sopenharmony_ci  // Add an item to the end of the data. 
91e41f4b71Sopenharmony_ci  public AddLastItem(): void {
92e41f4b71Sopenharmony_ci    this.dataArray.splice(this.dataArray.length, 0, this.dataArray.length)
93e41f4b71Sopenharmony_ci    this.notifyDataAdd(this.dataArray.length - 1)
94e41f4b71Sopenharmony_ci  }
95e41f4b71Sopenharmony_ci```
96e41f4b71Sopenharmony_ci
97e41f4b71Sopenharmony_ciTo 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.
98e41f4b71Sopenharmony_ci
99e41f4b71Sopenharmony_ciBecause 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.
100e41f4b71Sopenharmony_ci
101e41f4b71Sopenharmony_ci![](figures/waterflow-perf-demo1.gif)
102e41f4b71Sopenharmony_ci
103e41f4b71Sopenharmony_ci## Adding Data in Advance
104e41f4b71Sopenharmony_ci
105e41f4b71Sopenharmony_ciAlthough 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.
106e41f4b71Sopenharmony_ci
107e41f4b71Sopenharmony_ciTo 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.
108e41f4b71Sopenharmony_ci
109e41f4b71Sopenharmony_ci```ts
110e41f4b71Sopenharmony_ci  build() {
111e41f4b71Sopenharmony_ci    Column({ space: 2 }) {
112e41f4b71Sopenharmony_ci      WaterFlow() {
113e41f4b71Sopenharmony_ci        LazyForEach(this.datasource, (item: number) => {
114e41f4b71Sopenharmony_ci          FlowItem() {
115e41f4b71Sopenharmony_ci            Column() {
116e41f4b71Sopenharmony_ci              Text("N" + item).fontSize(12).height('16')
117e41f4b71Sopenharmony_ci              Image('res/waterFlowTest (' + item % 5 + ').jpg')
118e41f4b71Sopenharmony_ci                .objectFit(ImageFit.Fill)
119e41f4b71Sopenharmony_ci                .width('100%')
120e41f4b71Sopenharmony_ci                .layoutWeight(1)
121e41f4b71Sopenharmony_ci            }
122e41f4b71Sopenharmony_ci          }
123e41f4b71Sopenharmony_ci          .onAppear(() => {
124e41f4b71Sopenharmony_ci            // Add data in advance when scrolling is about to end. 
125e41f4b71Sopenharmony_ci            if (item + 20 == this.datasource.totalCount()) {
126e41f4b71Sopenharmony_ci              for (let i = 0; i < 100; i++) {
127e41f4b71Sopenharmony_ci                this.datasource.AddLastItem()
128e41f4b71Sopenharmony_ci              }
129e41f4b71Sopenharmony_ci            }
130e41f4b71Sopenharmony_ci          })
131e41f4b71Sopenharmony_ci          .width('100%')
132e41f4b71Sopenharmony_ci          .height(this.itemHeightArray[item % 100])
133e41f4b71Sopenharmony_ci          .backgroundColor(this.colors[item % 5])
134e41f4b71Sopenharmony_ci        }, (item: string) => item)
135e41f4b71Sopenharmony_ci      }
136e41f4b71Sopenharmony_ci      .columnsTemplate("1fr 1fr")
137e41f4b71Sopenharmony_ci      .columnsGap(10)
138e41f4b71Sopenharmony_ci      .rowsGap(5)
139e41f4b71Sopenharmony_ci      .backgroundColor(0xFAEEE0)
140e41f4b71Sopenharmony_ci      .width('100%')
141e41f4b71Sopenharmony_ci      .height('80%')
142e41f4b71Sopenharmony_ci    }
143e41f4b71Sopenharmony_ci  }
144e41f4b71Sopenharmony_ci```
145e41f4b71Sopenharmony_ci
146e41f4b71Sopenharmony_ciIn 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.
147e41f4b71Sopenharmony_ci
148e41f4b71Sopenharmony_ci![](figures/waterflow-perf-demo2.gif)
149e41f4b71Sopenharmony_ci
150e41f4b71Sopenharmony_ci## Reusing Components
151e41f4b71Sopenharmony_ci
152e41f4b71Sopenharmony_ciNow 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.
153e41f4b71Sopenharmony_ci
154e41f4b71Sopenharmony_ciDuring 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).
155e41f4b71Sopenharmony_ci
156e41f4b71Sopenharmony_ci```ts
157e41f4b71Sopenharmony_ci  build() {
158e41f4b71Sopenharmony_ci    Column({ space: 2 }) {
159e41f4b71Sopenharmony_ci      WaterFlow() {
160e41f4b71Sopenharmony_ci        LazyForEach(this.datasource, (item: number) => {
161e41f4b71Sopenharmony_ci          FlowItem() {
162e41f4b71Sopenharmony_ci            // Use reusable custom components. 
163e41f4b71Sopenharmony_ci            ResuableFlowItem({ item: item })
164e41f4b71Sopenharmony_ci          }
165e41f4b71Sopenharmony_ci          .onAppear(() => {
166e41f4b71Sopenharmony_ci            // Add data in advance when scrolling is about to end. 
167e41f4b71Sopenharmony_ci            if (item + 20 == this.datasource.totalCount()) {
168e41f4b71Sopenharmony_ci              for (let i = 0; i < 100; i++) {
169e41f4b71Sopenharmony_ci                this.datasource.AddLastItem()
170e41f4b71Sopenharmony_ci              }
171e41f4b71Sopenharmony_ci            }
172e41f4b71Sopenharmony_ci          })
173e41f4b71Sopenharmony_ci          .width('100%')
174e41f4b71Sopenharmony_ci          .height(this.itemHeightArray[item % 100])
175e41f4b71Sopenharmony_ci          .backgroundColor(this.colors[item % 5])
176e41f4b71Sopenharmony_ci        }, (item: string) => item)
177e41f4b71Sopenharmony_ci      }
178e41f4b71Sopenharmony_ci      .columnsTemplate("1fr 1fr")
179e41f4b71Sopenharmony_ci      .columnsGap(10)
180e41f4b71Sopenharmony_ci      .rowsGap(5)
181e41f4b71Sopenharmony_ci      .backgroundColor(0xFAEEE0)
182e41f4b71Sopenharmony_ci      .width('100%')
183e41f4b71Sopenharmony_ci      .height('80%')
184e41f4b71Sopenharmony_ci    }
185e41f4b71Sopenharmony_ci  }
186e41f4b71Sopenharmony_ci@Reusable
187e41f4b71Sopenharmony_ci@Component
188e41f4b71Sopenharmony_cistruct ResuableFlowItem {
189e41f4b71Sopenharmony_ci  @State item: number = 0
190e41f4b71Sopenharmony_ci
191e41f4b71Sopenharmony_ci  // 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.
192e41f4b71Sopenharmony_ci  aboutToReuse(params) {
193e41f4b71Sopenharmony_ci    this.item = params.item;
194e41f4b71Sopenharmony_ci  }
195e41f4b71Sopenharmony_ci
196e41f4b71Sopenharmony_ci  build() {
197e41f4b71Sopenharmony_ci    Column() {
198e41f4b71Sopenharmony_ci      Text("N" + this.item).fontSize(12).height('16')
199e41f4b71Sopenharmony_ci      Image('res/waterFlowTest (' + this.item % 5 + ').jpg')
200e41f4b71Sopenharmony_ci        .objectFit(ImageFit.Fill)
201e41f4b71Sopenharmony_ci        .width('100%')
202e41f4b71Sopenharmony_ci        .layoutWeight(1)
203e41f4b71Sopenharmony_ci    }
204e41f4b71Sopenharmony_ci  }
205e41f4b71Sopenharmony_ci}
206e41f4b71Sopenharmony_ci
207e41f4b71Sopenharmony_ci```
208e41f4b71Sopenharmony_ci
209e41f4b71Sopenharmony_ci## Takeaway
210e41f4b71Sopenharmony_ci
211e41f4b71Sopenharmony_ciTo 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.
212