1e41f4b71Sopenharmony_ci# Best Practices for Component Reuse
2e41f4b71Sopenharmony_ci
3e41f4b71Sopenharmony_ciComponent reuse is a useful tool in the following scenarios for removing the frame rate bottleneck in the UI thread:
4e41f4b71Sopenharmony_ci
5e41f4b71Sopenharmony_ci1. Scrolling scenarios where instances of the same type of custom components are frequently created and destroyed
6e41f4b71Sopenharmony_ci2. Scenarios where branches of conditional rendering are switched repeatedly, and the component subtree structure in the branches is complex
7e41f4b71Sopenharmony_ci
8e41f4b71Sopenharmony_ciFor component reuse to take effect, the following conditions must be met:
9e41f4b71Sopenharmony_ci
10e41f4b71Sopenharmony_ci1. The custom component to reuse is decorated by the @Reusable decorator.
11e41f4b71Sopenharmony_ci2. The reusable custom component (child) created under a custom component (parent) is added to the reuse cache of the parent after being is removed from the component tree.
12e41f4b71Sopenharmony_ci3. In attempts to create a reusable child component under a custom component (parent), the child is quickly created by updating the reusable component instance of the corresponding type, if any, in the reuse cache of the parent.
13e41f4b71Sopenharmony_ci
14e41f4b71Sopenharmony_ci## Constraints
15e41f4b71Sopenharmony_ci
16e41f4b71Sopenharmony_ci1. @Reusable indicates that a custom component can be reused. While it can be used to decorate any custom component, take notes of the creation and update processes of the custom component to ensure that the component behaves correctly after being reused.
17e41f4b71Sopenharmony_ci2. The cache and reuse of reusable custom components can occur only under the same parent component. This means that instances of the same custom component cannot be reused under different parent components. For example, component A is a reusable component, which is also a child component of component B and in the reuse cache of component B. When component A is created in component C, component A cached in component B cannot be used in component C.
18e41f4b71Sopenharmony_ci3. To reuse a subtree, you only need to decorate its root node with @Reusable. For example, if custom component A has custom child component B, then to reuse the subtree of A and B, you only need to add the @Reusable decorator to A.
19e41f4b71Sopenharmony_ci4. When a custom component is nested within a reusable custom component, to update the content of the nested component, you must implement the **aboutToReuse** lifecycle callback in the component.
20e41f4b71Sopenharmony_ci5. The performance benefits of component reuse mainly result from its reducing the time for creating JS objects and reusing the component tree structure. As such, if the component tree structure of custom components is significantly changed due to conditional rendering before or after the reuse, you will not be able to reap the performance benefits of component reuse.
21e41f4b71Sopenharmony_ci6. Component reuse occurs only when a reusable component is removed from the component tree and then added to the component tree again. For example, if **ForEach** is used to create a reusable custom component, component reuse cannot be triggered due to the full expansion attribute of **ForEach**.
22e41f4b71Sopenharmony_ci7. Reusable components cannot be nested. That is, if a reusable component exists in the subtree of another reusable component, undefined results may occur.
23e41f4b71Sopenharmony_ci
24e41f4b71Sopenharmony_ci## Developer's Tips
25e41f4b71Sopenharmony_ci
26e41f4b71Sopenharmony_ci1. To maximize the component reuse performance, avoid any operations that may change the component tree structure or re-lay out the reusable components.
27e41f4b71Sopenharmony_ci2. For best possible performance, combine component reuse with the **LazyForEach** syntax in list scrolling scenarios.
28e41f4b71Sopenharmony_ci3. Pay attention to the behavior differences between the creation and update of custom components. Component reuse is, in effect, a special form of component update. The process and lifecycle callbacks used in component creation will not occur during component reuse, and the constructor parameters of the custom component are passed to it through the **aboutToReuse** lifecycle callback. In other words, the **aboutToAppear** lifecycle and initialization parameter input of the custom component will not occur during component reuse.
29e41f4b71Sopenharmony_ci4. Avoid time-consuming operations during the **aboutToReuse** lifecycle callback. The best practice is to, in **aboutToReuse**, only update the state variable values required for updating custom components.
30e41f4b71Sopenharmony_ci5. 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-rendering.
31e41f4b71Sopenharmony_ci
32e41f4b71Sopenharmony_ci## Lifecycle
33e41f4b71Sopenharmony_ci
34e41f4b71Sopenharmony_ciWhen a reusable component is removed from the component tree in C++ code, the **CustomNode** instance of the component on the native side of the ArkUI framework is mounted to the corresponding JSView. When reuse occurs, **CustomNode** is referenced by JSView and the **aboutToRecycle** callback on **ViewPU** is triggered. The **ViewPU** instance is referenced by **RecycleManager**.
35e41f4b71Sopenharmony_ci
36e41f4b71Sopenharmony_ciWhen the reusable component is re-added to the component tree from **RecycleManager**, the **aboutToReuse** callback on the frontend **ViewPU** instance is called.
37e41f4b71Sopenharmony_ci
38e41f4b71Sopenharmony_ci## Available APIs
39e41f4b71Sopenharmony_ci
40e41f4b71Sopenharmony_ciCalled when this reusable component is about to be added from the reuse cache to the component tree. The component's state variables can be updated in this callback. The argument type is the same as the constructor parameter type of the custom component.
41e41f4b71Sopenharmony_ci
42e41f4b71Sopenharmony_ci```ts
43e41f4b71Sopenharmony_ciaboutToReuse?(params: { [key: string]: unknown }): void;
44e41f4b71Sopenharmony_ci```
45e41f4b71Sopenharmony_ci
46e41f4b71Sopenharmony_ciCalled when this reusable component is about to be added from the component tree to the reuse cache.
47e41f4b71Sopenharmony_ci
48e41f4b71Sopenharmony_ci```ts
49e41f4b71Sopenharmony_ciaboutToRecycle?(): void;
50e41f4b71Sopenharmony_ci```
51e41f4b71Sopenharmony_ci
52e41f4b71Sopenharmony_ciAdds this reusable component to a reuse group. Components with the same **reuseId** value are reused in the same reuse group.
53e41f4b71Sopenharmony_ci
54e41f4b71Sopenharmony_ci```ts
55e41f4b71Sopenharmony_cireuseId(id: string);
56e41f4b71Sopenharmony_ci```
57e41f4b71Sopenharmony_ci
58e41f4b71Sopenharmony_ciDeclares that a component is reusable.
59e41f4b71Sopenharmony_ci
60e41f4b71Sopenharmony_ci```ts
61e41f4b71Sopenharmony_cideclare const Reusable: ClassDecorator;
62e41f4b71Sopenharmony_ci```
63e41f4b71Sopenharmony_ci
64e41f4b71Sopenharmony_ci**Example**
65e41f4b71Sopenharmony_ci
66e41f4b71Sopenharmony_ci```ts
67e41f4b71Sopenharmony_ciprivate dataArray: string[] = [];
68e41f4b71Sopenharmony_ci  private listener: DataChangeListener;
69e41f4b71Sopenharmony_ci
70e41f4b71Sopenharmony_ci  public totalCount(): number {
71e41f4b71Sopenharmony_ci    return this.dataArray.length;
72e41f4b71Sopenharmony_ci  }
73e41f4b71Sopenharmony_ci
74e41f4b71Sopenharmony_ci  public getData(index: number): any {
75e41f4b71Sopenharmony_ci    return this.dataArray[index];
76e41f4b71Sopenharmony_ci  }
77e41f4b71Sopenharmony_ci
78e41f4b71Sopenharmony_ci  public pushData(data: string): void {
79e41f4b71Sopenharmony_ci    this.dataArray.push(data);
80e41f4b71Sopenharmony_ci  }
81e41f4b71Sopenharmony_ci
82e41f4b71Sopenharmony_ci  public reloadListener(): void {
83e41f4b71Sopenharmony_ci    this.listener.onDataReloaded();
84e41f4b71Sopenharmony_ci  }
85e41f4b71Sopenharmony_ci
86e41f4b71Sopenharmony_ci  public registerDataChangeListener(listener: DataChangeListener): void {
87e41f4b71Sopenharmony_ci    this.listener = listener;
88e41f4b71Sopenharmony_ci  }
89e41f4b71Sopenharmony_ci
90e41f4b71Sopenharmony_ci  public unregisterDataChangeListener(listener: DataChangeListener): void {
91e41f4b71Sopenharmony_ci    this.listener = null;
92e41f4b71Sopenharmony_ci  }
93e41f4b71Sopenharmony_ci}
94e41f4b71Sopenharmony_ci
95e41f4b71Sopenharmony_ci@Entry
96e41f4b71Sopenharmony_ci@Component
97e41f4b71Sopenharmony_cistruct MyComponent {
98e41f4b71Sopenharmony_ci  private data: MyDataSource = new MyDataSource();
99e41f4b71Sopenharmony_ci
100e41f4b71Sopenharmony_ci  aboutToAppear() {
101e41f4b71Sopenharmony_ci    for (let i = 0; i < 1000; i++) {
102e41f4b71Sopenharmony_ci      this.data.pushData(i.toString())
103e41f4b71Sopenharmony_ci    }
104e41f4b71Sopenharmony_ci  }
105e41f4b71Sopenharmony_ci
106e41f4b71Sopenharmony_ci  build() {
107e41f4b71Sopenharmony_ci    List({ space: 3 }) {
108e41f4b71Sopenharmony_ci      LazyForEach(this.data, (item: string) => {
109e41f4b71Sopenharmony_ci        ListItem() {
110e41f4b71Sopenharmony_ci          ReusableChildComponent({ item: item })
111e41f4b71Sopenharmony_ci        }
112e41f4b71Sopenharmony_ci      }, item => item)
113e41f4b71Sopenharmony_ci    }
114e41f4b71Sopenharmony_ci    .width('100%')
115e41f4b71Sopenharmony_ci    .height('100%')
116e41f4b71Sopenharmony_ci  }
117e41f4b71Sopenharmony_ci}
118e41f4b71Sopenharmony_ci
119e41f4b71Sopenharmony_ci@Reusable
120e41f4b71Sopenharmony_ci@Component
121e41f4b71Sopenharmony_cistruct ReusableChildComponent {
122e41f4b71Sopenharmony_ci  @State item: string = ''
123e41f4b71Sopenharmony_ci
124e41f4b71Sopenharmony_ci  aboutToReuse(params) {
125e41f4b71Sopenharmony_ci    this.item = params.item;
126e41f4b71Sopenharmony_ci  }
127e41f4b71Sopenharmony_ci
128e41f4b71Sopenharmony_ci  build() {
129e41f4b71Sopenharmony_ci    Row() {
130e41f4b71Sopenharmony_ci      Text(this.item)
131e41f4b71Sopenharmony_ci        .fontSize(20)
132e41f4b71Sopenharmony_ci        .margin({ left: 10 })
133e41f4b71Sopenharmony_ci    }.margin({ left: 10, right: 10 })
134e41f4b71Sopenharmony_ci  }
135e41f4b71Sopenharmony_ci}
136e41f4b71Sopenharmony_ci```
137e41f4b71Sopenharmony_ci
138e41f4b71Sopenharmony_ci## Samples
139e41f4b71Sopenharmony_ci
140e41f4b71Sopenharmony_ciThe following sample code from a shopping application exemplifies code before and after component reuse and the benefits that can be reaped from component reuse.
141e41f4b71Sopenharmony_ci
142e41f4b71Sopenharmony_ci### Code Before and After Component Reuse
143e41f4b71Sopenharmony_ci
144e41f4b71Sopenharmony_ci**Before Component Reuse**
145e41f4b71Sopenharmony_ci
146e41f4b71Sopenharmony_ci```ts
147e41f4b71Sopenharmony_ciLazyForEach(this.GoodDataOne, (item, index) => {
148e41f4b71Sopenharmony_ci  GridItem() {
149e41f4b71Sopenharmony_ci    Column() {
150e41f4b71Sopenharmony_ci      Image(item.img)
151e41f4b71Sopenharmony_ci        .height(item.hei)
152e41f4b71Sopenharmony_ci        .width('100%')
153e41f4b71Sopenharmony_ci        .objectFit(ImageFit.Fill)
154e41f4b71Sopenharmony_ci
155e41f4b71Sopenharmony_ci      Text(item.introduce)
156e41f4b71Sopenharmony_ci        .fontSize(14)
157e41f4b71Sopenharmony_ci        .padding({ left: 5, right: 5 })
158e41f4b71Sopenharmony_ci        .margin({ top: 5 })
159e41f4b71Sopenharmony_ci      Row() {
160e41f4b71Sopenharmony_ci        Row() {
161e41f4b71Sopenharmony_ci          Text('¥')
162e41f4b71Sopenharmony_ci            .fontSize(10)
163e41f4b71Sopenharmony_ci            .fontColor(Color.Red)
164e41f4b71Sopenharmony_ci            .baselineOffset(-4)
165e41f4b71Sopenharmony_ci          Text(item.price)
166e41f4b71Sopenharmony_ci            .fontSize(16)
167e41f4b71Sopenharmony_ci            .fontColor(Color.Red)
168e41f4b71Sopenharmony_ci          Text(item.numb)
169e41f4b71Sopenharmony_ci            .fontSize(10)
170e41f4b71Sopenharmony_ci            .fontColor(Color.Gray)
171e41f4b71Sopenharmony_ci            .baselineOffset(-4)
172e41f4b71Sopenharmony_ci            .margin({ left: 5 })
173e41f4b71Sopenharmony_ci        }
174e41f4b71Sopenharmony_ci
175e41f4b71Sopenharmony_ci        Image($r('app.media.photo63'))
176e41f4b71Sopenharmony_ci          .width(20)
177e41f4b71Sopenharmony_ci          .height(10)
178e41f4b71Sopenharmony_ci          .margin({ bottom: -8 })
179e41f4b71Sopenharmony_ci      }
180e41f4b71Sopenharmony_ci      .width('100%')
181e41f4b71Sopenharmony_ci      .justifyContent(FlexAlign.SpaceBetween)
182e41f4b71Sopenharmony_ci      .padding({ left: 5, right: 5 })
183e41f4b71Sopenharmony_ci      .margin({ top: 15 })
184e41f4b71Sopenharmony_ci    }
185e41f4b71Sopenharmony_ci    .borderRadius(10)
186e41f4b71Sopenharmony_ci    .backgroundColor(Color.White)
187e41f4b71Sopenharmony_ci    .clip(true)
188e41f4b71Sopenharmony_ci    .width('100%')
189e41f4b71Sopenharmony_ci    .height(290)
190e41f4b71Sopenharmony_ci  }
191e41f4b71Sopenharmony_ci}, (item) => JSON.stringify(item))
192e41f4b71Sopenharmony_ci```
193e41f4b71Sopenharmony_ci
194e41f4b71Sopenharmony_ci**After Component Reuse**
195e41f4b71Sopenharmony_ci
196e41f4b71Sopenharmony_ciWhen the component is reused, the ArkUI framework passes to the **aboutToReuse** lifecycle callback the constructor parameters of the component. Assign values to the state variables to be updated in **aboutToReuse**, and the ArkUI framework will display the UI with the latest state variable values.
197e41f4b71Sopenharmony_ci
198e41f4b71Sopenharmony_ciIf the instances of a custom component vary greatly, use **reuseId** to assign these instances to different reuse groups to achieve the optimal effect.
199e41f4b71Sopenharmony_ci
200e41f4b71Sopenharmony_ciIf a custom component has a reference to a large object or other unnecessary resources, release the reference in the **aboutToRecycle** lifecycle callback to avoid memory leak.
201e41f4b71Sopenharmony_ci
202e41f4b71Sopenharmony_ci```ts
203e41f4b71Sopenharmony_ciLazyForEach(this.GoodDataOne, (item, index) => {
204e41f4b71Sopenharmony_ci  GridItem() {
205e41f4b71Sopenharmony_ci    GoodItems({
206e41f4b71Sopenharmony_ci      boo:item.data.boo,
207e41f4b71Sopenharmony_ci      img:item.data.img,
208e41f4b71Sopenharmony_ci      webimg:item.data.webimg,
209e41f4b71Sopenharmony_ci      hei:item.data.hei,
210e41f4b71Sopenharmony_ci      introduce:item.data.introduce,
211e41f4b71Sopenharmony_ci      price:item.data.price,
212e41f4b71Sopenharmony_ci      numb:item.data.numb,
213e41f4b71Sopenharmony_ci      index:index
214e41f4b71Sopenharmony_ci    })
215e41f4b71Sopenharmony_ci    .reuseId(this.CombineStr(item.type))
216e41f4b71Sopenharmony_ci  }
217e41f4b71Sopenharmony_ci}, (item) => JSON.stringify(item))
218e41f4b71Sopenharmony_ci
219e41f4b71Sopenharmony_ci
220e41f4b71Sopenharmony_ci@Reusable
221e41f4b71Sopenharmony_ci@Component
222e41f4b71Sopenharmony_cistruct GoodItems {
223e41f4b71Sopenharmony_ci  @State img: Resource = $r("app.media.photo61")
224e41f4b71Sopenharmony_ci  @State webimg?: string = ''
225e41f4b71Sopenharmony_ci  @State hei: number = 0
226e41f4b71Sopenharmony_ci  @State introduce: string = ''
227e41f4b71Sopenharmony_ci  @State price: string = ''
228e41f4b71Sopenharmony_ci  @State numb: string = ''
229e41f4b71Sopenharmony_ci  @LocalStorageLink('storageSimpleProp') simpleVarName: string = ''
230e41f4b71Sopenharmony_ci  boo: boolean = true
231e41f4b71Sopenharmony_ci  index: number = 0
232e41f4b71Sopenharmony_ci  controllerVideo: VideoController = new VideoController();
233e41f4b71Sopenharmony_ci
234e41f4b71Sopenharmony_ci  aboutToReuse(params)
235e41f4b71Sopenharmony_ci  {
236e41f4b71Sopenharmony_ci    this.webimg = params.webimg
237e41f4b71Sopenharmony_ci    this.img = params.img
238e41f4b71Sopenharmony_ci    this.hei = params.hei
239e41f4b71Sopenharmony_ci    this.introduce = params.introduce
240e41f4b71Sopenharmony_ci    this.price = params.price
241e41f4b71Sopenharmony_ci    this.numb = params.numb
242e41f4b71Sopenharmony_ci  }
243e41f4b71Sopenharmony_ci
244e41f4b71Sopenharmony_ci  build() {
245e41f4b71Sopenharmony_ci    // ...
246e41f4b71Sopenharmony_ci  }
247e41f4b71Sopenharmony_ci}
248e41f4b71Sopenharmony_ci```
249e41f4b71Sopenharmony_ci
250e41f4b71Sopenharmony_ci### Performance Benefit
251e41f4b71Sopenharmony_ci
252e41f4b71Sopenharmony_ciAnalysis results from the profiler tool in DevEco Studio show that, with component reuse, the average component creation time is reduced from 1800 μs to 570 μs.
253e41f4b71Sopenharmony_ci
254e41f4b71Sopenharmony_ci![before recycle](./figures/before-recycle.png)
255e41f4b71Sopenharmony_ci
256e41f4b71Sopenharmony_ci![using recycle](./figures/using-recycle.png)
257e41f4b71Sopenharmony_ci
258e41f4b71Sopenharmony_ci| Component Reuse | Component Creation Time|
259e41f4b71Sopenharmony_ci| -------------- | ------------ |
260e41f4b71Sopenharmony_ci| Component reuse disabled| 1813 μs      |
261e41f4b71Sopenharmony_ci| Component reuse enabled  | 570 μs       |
262