• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@ReusableV2装饰器:组件复用
2
3为了降低反复创建销毁自定义组件带来的性能开销,开发者可以使用\@ReusableV2装饰\@ComponentV2装饰的自定义组件,达成组件复用的效果。
4
5在阅读本文前,建议提前阅读:[\@Reusable装饰器:组件复用](./arkts-reusable.md)。
6
7>**说明:**
8>
9>从API version 18开始,可以使用\@ReusableV2装饰\@ComponentV2装饰的自定义组件。
10>
11
12## 概述
13
14\@ReusableV2用于装饰V2的自定义组件,表明该自定义组件具有被复用的能力:
15
16- \@ReusableV2仅能装饰V2的自定义组件,即\@ComponentV2装饰的自定义组件。并且仅能将\@ReusableV2装饰的自定义组件作为V2自定义组件的子组件使用。
17- \@ReusableV2同样提供了aboutToRecycle和aboutToReuse的生命周期,在组件被回收时调用aboutToRecycle,在组件被复用时调用aboutToReuse,但与\@Reusable不同的是,aboutToReuse没有入参。
18- 在回收阶段,会递归地调用所有子组件的aboutToRecycle回调(即使子组件未被标记可复用);在复用阶段,会递归地调用所有子组件的aboutToReuse回调(即使子组件未被标记可复用)。
19- \@ReusableV2装饰的自定义组件会在被回收期间保持冻结状态,即无法触发UI刷新、无法触发\@Monitor回调,与freezeWhenInactive标记位不同的是,在解除冻结状态后,不会触发延后的刷新。
20- \@ReusableV2装饰的自定义组件会在复用时自动重置组件内状态变量的值、重新计算组件内\@Computed以及与之相关的\@Monitor。不建议开发者在aboutToRecycle中更改组件内状态变量,详见[复用前的组件内状态变量重置](#复用前的组件内状态变量重置)。
21- V1和V2的复用组件可在一定规则下混用,详见[使用限制](#使用限制)第二点。
22- 不建议开发者嵌套滥用\@ReusableV2装饰器,这可能会导致复用效率降低以及内存开销变大。
23
24## 装饰器说明
25
26| \@ReusableV2装饰器 | 说明                          |
27| ------------------ | ----------------------------- |
28| 装饰器参数         | 无                            |
29| 可装饰的组件       | \@ComponentV2装饰的自定义组件 |
30| 装饰器作用         | 表明该组件可被复用            |
31
32```ts
33@ReusableV2 // 装饰ComponentV2的自定义组件
34@ComponentV2
35struct ReusableV2Component {
36  @Local message: string = 'Hello World';
37  build () {
38  	Column() {
39  	  Text(this.message)
40  	}
41  }
42}
43```
44
45## 接口说明
46
47### ReuseIdCallback
48
49ReuseIdCallback用于计算reuseId,类型为() => string。
50
51### ReuseOptions
52
53ReuseOptions保存了reuseId信息,用于.reuse属性中。
54
55| 属性    | 类型            | 说明                  |
56| ------- | --------------- | --------------------- |
57| reuseId | ReuseIdCallback | 获取reuseId的回调函数 |
58
59### reuse属性
60
61reuse属性用于\@ReusableV2装饰的自定义组件,给组件指定reuseId。相同reuseId的自定义组件会被互相复用。如果未指定reuseId,则使用自定义组件名作为reuseId。
62
63| 参数    | 类型         | 说明                                                   |
64| ------- | ------------ | ------------------------------------------------------ |
65| options | ReuseOptions | reuse的配置选项,例如{reuseId: () => 'reuseComponent'} |
66
67```ts
68@Entry
69@ComponentV2
70struct Index {
71  build() {
72    Column() {
73      ReusableV2Component()
74        .reuse({reuseId: () => 'reuseComponent'}) // 使用'reuseComponent'作为reuseId
75      ReusableV2Component()
76        .reuse({reuseId: () => ''}) // 使用空字符串将默认使用组件名'ReusableV2Component'作为reuseId
77      ReusableV2Component() // 未指定reuseId将默认使用组件名'ReusableV2Component'作为reuseId
78    }
79  }
80}
81@ReusableV2
82@ComponentV2
83struct ReusableV2Component {
84  build() {
85  }
86}
87```
88
89## 使用限制
90
91- 仅能将\@ReusableV2装饰的自定义组件作为V2自定义组件的子组件使用。如果在V1的自定义组件中使用V2的复用组件将导致编译期报错,编译期无法校验到的复杂场景下将会有运行时报错。
92
93  ```ts
94  @Entry
95  @ComponentV2
96  struct Index {
97    build() {
98      Column() {
99        ReusableV2Component() // 正确用法
100        V1Component()
101      }
102    }
103  }
104  @ReusableV2
105  @ComponentV2
106  struct ReusableV2Component {
107    build() {
108    }
109  }
110  @Builder
111  function V2ReusableBuilder() {
112    ReusableV2Component()
113  }
114  @Component
115  struct V1Component {
116    build() {
117      Column() {
118        ReusableV2Component() // 错误用法,编译报错
119        V2ReusableBuilder() // 错误用法,较复杂场景,运行时报错
120      }
121    }
122  }
123  ```
124
125- V1和V2支持部分混用场景。
126
127  下文提到的描述对应关系如下表:
128
129  | 描述       | 对应组件类型                         |
130  | ---------- | ------------------------------------ |
131  | V1普通组件 | @Component装饰的struct               |
132  | V2普通组件 | @ComponentV2装饰的struct             |
133  | V1复用组件 | @Reusable @Component装饰的struct     |
134  | V2复用组件 | @ReusableV2 @ComponentV2装饰的struct |
135
136  下面的表展示了V1和V2的混用支持关系,每行的含义为第一列作为父组件,能否将后面列的组件作为子组件。
137
138  以第一行V1普通组件为例,可以将V1普通组件、V2普通组件以及V1复用组件作为子组件,但无法将V2复用组件作为子组件。
139
140  |            | V1普通组件 | V2普通组件 |               V1复用组件               |    V2复用组件    |
141  | ---------- | :--------: | :--------: | :------------------------------------: | :--------------: |
142  | V1普通组件 |    支持    |    支持    |                  支持                  | 不支持,编译报错 |
143  | V2普通组件 |    支持    |    支持    | 不支持,编译告警,实际使用子组件不创建 |       支持       |
144  | V1复用组件 |    支持    |    支持    |                  支持                  | 不支持,编译报错 |
145  | V2复用组件 |    支持    |    支持    |            不支持,编译报错            |       支持       |
146
147  根据上表,仅支持12种可能的父子关系,不推荐开发者高度嵌套可复用组件,这会造成复用效率降低。
148
149- V2的复用组件当前不支持直接用于Repeat的template中,但是可以用在template中的V2自定义组件中。
150
151  ```ts
152  @Entry
153  @ComponentV2
154  struct Index {
155    @Local arr: number[] = [1, 2, 3, 4, 5];
156    build() {
157      Column() {
158        List() {
159          Repeat(this.arr)
160            .each(() => {})
161            .virtualScroll()
162            .templateId(() => 'a')
163            .template('a', (ri) => {
164              ListItem() {
165                Column() {
166                  ReusableV2Component({ val: ri.item}) // 暂不支持,编译期报错
167                  ReusableV2Builder(ri.item) // 暂不支持,运行时报错
168                  NormalV2Component({ val: ri.item}) // 支持普通V2自定义组件下面包含V2复用组件
169                }
170              }
171            })
172        }
173      }
174    }
175  }
176  @ComponentV2
177  struct NormalV2Component {
178    @Require @Param val: number;
179    build() {
180      ReusableV2Component({ val: this.val })
181    }
182  }
183  @Builder
184  function ReusableV2Builder(param: number) {
185    ReusableV2Component({ val: param })
186  }
187  @ReusableV2
188  @ComponentV2
189  struct ReusableV2Component {
190    @Require @Param val: number;
191    build() {
192      Column() {
193        Text(`val: ${this.val}`)
194      }
195    }
196  }
197  ```
198
199## 回收与复用的生命周期
200
201\@ReusableV2提供了aboutToRecycle以及aboutToReuse的生命周期,当组件被回收时触发aboutToRecycle,当组件被复用时触发aboutToReuse。
202
203以if的使用场景为例:
204
205```ts
206@Entry
207@ComponentV2
208struct Index {
209  @Local condition1: boolean = false;
210  @Local condition2: boolean = true;
211  build() {
212    Column() {
213      Button('step1. appear')
214        .onClick(() => {
215          this.condition1 = true;
216        })
217      Button('step2. recycle')
218        .onClick(() => {
219          this.condition2 = false;
220        })
221      Button('step3. reuse')
222        .onClick(() => {
223          this.condition2 = true;
224        })
225      Button('step4. disappear')
226        .onClick(() => {
227          this.condition1 = false;
228        })
229      if (this.condition1) {
230        NormalV2Component({ condition: this.condition2 })
231      }
232    }
233  }
234}
235@ComponentV2
236struct NormalV2Component {
237  @Require @Param condition: boolean;
238  build() {
239    if (this.condition) {
240      ReusableV2Component()
241    }
242  }
243}
244@ReusableV2
245@ComponentV2
246struct ReusableV2Component {
247  aboutToAppear() {
248    console.log('ReusableV2Component aboutToAppear called'); // 组件创建时调用
249  }
250  aboutToDisappear() {
251    console.log('ReusableV2Component aboutToDisappear called'); // 组件销毁时调用
252  }
253  aboutToRecycle() {
254    console.log('ReusableV2Component aboutToRecycle called'); // 组件回收时调用
255  }
256  aboutToReuse() {
257    console.log('ReusableV2Component aboutToReuse called'); // 组件复用时调用
258  }
259  build() {
260    Column() {
261      Text('ReusableV2Component')
262    }
263  }
264}
265```
266
267建议按下面顺序进行操作:
268
2691. 点击`step1. appear`,此时`condition1`变为true,`Index`中的if组件切换分支,创建出`NormalV2Component`,由于`condition2`初始值为true,所以`NormalV2Component`中的if条件满足,尝试创建`ReusableV2Component`。此时复用池中无元素,因此会创建`ReusableV2Component`,并回调`aboutToAppear`的方法,输出`ReusableV2Component aboutToAppear called`的日志。
2702. 点击`step2. recycle`,此时`condition2`变为false,通过\@Param同步给`NormalV2Component`,if条件切换,由于`ReusableV2Component`使用了\@ReusableV2,因此会将该组件回收至复用池而不是销毁,回调`aboutToRecycle`的方法并输出`ReusableV2Component aboutToRecycle called`的日志。
2713. 点击`step3. reuse`,此时`condition2`变为true,通过\@Param传递给`NormalV2Component`,if条件切换,由于`ReusableV2Component`使用了\@ReusableV2,因此在创建该组件时尝试去复用池中寻找。此时复用池中有第二步放入的组件实例,因此从复用池中取出复用,回调`aboutToReuse`方法并输出`ReusableV2Component aboutToReuse called`的日志。
2724. 点击`step4. disappear`,此时`condition1`变为false,`Index`组件中的if组件切换分支,销毁`NormalV2Component`,此时`ReusableV2Component`因为父组件销毁,所以会被一起销毁,回调`aboutToDisappear`的方法并输出`ReusableV2Component aboutToDisappear called`的日志。
273
274倘若该复用组件下有子组件时,会在回收和复用时递归调用子组件的aboutToRecycle和aboutToReuse(与子组件是否被标记复用无关),直到遍历完所有的孩子组件。
275
276## 复用阶段的冻结
277
278在之前的复用中,V1组件在复用池中仍能响应更新,这会对性能带来一定的负面影响,需要开发者使用组件冻结能力,才能够使V1组件在复用池中时不响应更新。针对这一点,V2组件在复用时将会被自动冻结,不会响应在回收期间发生的变化。这一个期间包括`aboutToRecycle`,即`aboutToRecycle`中的修改不会刷新到UI上,也不会触发\@Computed以及\@Monitor。冻结状态将持续到aboutToReuse前,即`aboutToReuse`及之后的变量更改,才会正常触发UI刷新、\@Computed重新计算以及\@Monitor的调用。
279
280以if的使用场景为例:
281
282```ts
283@ObservedV2
284class Info {
285  @Trace age: number = 25;
286}
287const info: Info = new Info();
288@Entry
289@ComponentV2
290struct Index {
291  @Local condition: boolean = true;
292  build() {
293    Column() {
294      Button('复用/回收').onClick(()=>{this.condition=!this.condition;})
295      Button('改值').onClick(()=>{info.age++;})
296      if (this.condition) {
297        ReusableV2Component()
298      }
299    }
300  }
301}
302@ReusableV2
303@ComponentV2
304struct ReusableV2Component {
305  @Local info: Info = info; // 仅做演示使用,并不建议@Local赋值全局变量
306  @Monitor('info.age')
307  onValChange() {
308    console.log('info.age change');
309  }
310  aboutToRecycle() {
311    console.log('aboutToRecycle');
312    this.info.age++;
313  }
314  aboutToReuse() {
315    console.log('aboutToReuse');
316    this.info.age++;
317  }
318  onRender(): string {
319    console.log('info.age onRender')
320    return this.info.age.toString();
321  }
322  build() {
323    Column() {
324      Text(this.onRender())
325    }
326  }
327}
328```
329
330建议按如下步骤进行操作:
331
3321. 点击`改值`按钮,可以观察到UI变化,\@Monitor触发并输出日志`info.age change`以及`info.age onRender`,说明此时能够正常监听到变化以及触发UI刷新。
3332. 点击`复用/回收`按钮,此时调用`aboutToRecycle`回调并输出`aboutToRecycle`的日志,但\@Monitor不被触发,且`onRender`方法不被回调。
3343. 点击`改值`按钮,UI无变化,\@Monitor不触发且`onRender`方法不被回调。
3354. 点击`复用/回收`按钮,此时UI变化,\@Monitor触发并输出日志`info.age change`且`onRender`方法回调输出`info.age onRender`。
336
337如果去掉`aboutToReuse`方法中的自增操作,则上述第四步不会触发\@Monitor回调。
338
339在复杂的混用场景中,是否冻结的规则可以总结为以下两点:
340
3411. V1的组件根据是否开启组件冻结freezeWhenInactive决定。
3422. V2的组件自动被冻结。
343
344## 复用前的组件内状态变量重置
345
346与\@Reusable不同的是,\@ReusableV2在复用前会重置组件中的状态变量以及相关的\@Computed、\@Monitor的内容。在复用的过程当中,所有的V2自定义组件,无论是否被标记了\@ReusableV2,都会经历这一个重置过程。
347
348重置会按照变量在组件中定义的顺序按照下面的规则依次进行:
349
350| 装饰器     | 重置方法                                                     |
351| ---------- | ------------------------------------------------------------ |
352| \@Local    | 直接使用定义时的初始值重新赋值。                             |
353| \@Param    | 如果有外部传入则使用外部传入值重新赋值,否则用本地初始值重新赋值。注意:\@Once装饰的变量同样会被重置初始化一次。 |
354| \@Event    | 如果有外部传入则使用外部传入值重新赋值,否则用本地初始值重新赋值。如果本地没有初始值,则生成默认的空实现。 |
355| \@Provider | 直接使用定义时的初始值重新赋值。                             |
356| \@Consumer | 如果有对应的\@Provider则直接使用\@Provider对应的值,否则使用本地初始值重新赋值。 |
357| \@Computed | 使用当前最新的值重新计算一次,如果使用到的变量还未被重置,将会使用重置前的值,因此推荐开发者将\@Computed定义在所使用的变量之后。 |
358| \@Monitor  | 在上述所有变量重置完成之后触发。重置过程中产生的变量变化不会触发\@Monitor回调,仅更新IMonitorValue中的before值。重置过程中不产生变化的赋值不会触发\@Monitor的重置。 |
359| 常量       | 包括readonly的常量,不重置。                                 |
360
361下面的例子展示了重置的一些效果:
362
363```ts
364@ObservedV2
365class Info {
366  @Trace age: number;
367  constructor(age: number) {
368    this.age = age;
369  }
370}
371@Entry
372@ComponentV2
373struct Index {
374  @Local local: number = 0;
375  @Provider('inherit') inheritProvider: number = 100;
376  @Local condition: boolean = true;
377  build() {
378    Column() {
379      Button('回收/复用').onClick(()=>{this.condition=!this.condition;})
380      Column() {
381        Text('父组件变量')
382        Text(`local: ${this.local}`).onClick(()=>{this.local++;})
383        Text(`inheritProvider: ${this.inheritProvider}`).onClick(()=>{this.inheritProvider++;})
384      }.borderWidth(2)
385      if (this.condition) {
386        ReusableV2Component({
387          paramOut: this.local,
388          paramOnce: this.local,
389          changeParam: () => {
390            this.local++;
391          }
392        })
393      }
394    }
395  }
396}
397@ReusableV2
398@ComponentV2
399struct ReusableV2Component {
400  @Local val: number = 0;
401  @Local info: Info = new Info(25);
402  @Param paramLocal: number = 1;
403  @Require @Param paramOut: number;
404  @Require @Param @Once paramOnce: number;
405  @Event changeParam: () => void;
406  @Provider('selfProvider') selfProvider: number = 0;
407  @Consumer('inherit') inheritConsumer: number = 0;
408  @Consumer('selfConsumer') selfConsumer: number = 0;
409  noDecoVariable: number = 0; // 未加装饰器,被视作常量
410  noDecoInfo: Info = new Info(30); // 未加装饰器,被视作常量
411  readonly readOnlyVariable: number = 0; // readonly常量
412  @Computed
413  get plusParam() {
414    return this.paramLocal + this.paramOut + this.paramOnce;
415  }
416  @Monitor('val')
417  onValChange(monitor: IMonitor) {
418    console.log(`val change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
419  }
420  @Monitor('plusParam')
421  onPlusParamChange(monitor: IMonitor) {
422    console.log(`plusParam change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
423  }
424  build() {
425    Column() {
426	  Column() {
427        Text('重置为本地初始值的变量')
428        Text(`val: ${this.val}`).onClick(()=>{this.val++;})
429        Text(`info.age: ${this.info.age}`).onClick(()=>{this.info.age++;})
430        Text(`paramLocal: ${this.paramLocal}`).onClick(()=>{/* 无外部传入的Local无法本地修改 */})
431        Text(`selfProvider: ${this.selfProvider}`).onClick(()=>{this.selfProvider++;})
432        Text(`selfConsumer: ${this.selfConsumer}`).onClick(()=>{this.selfConsumer++;})
433      }.borderWidth(2)
434      Column() {
435        Text('重置为外部传入的变量')
436        Text(`paramOut: ${this.paramOut}`).onClick(()=>{this.changeParam();})
437        Text(`paramOnce: ${this.paramOnce}`).onClick(()=>{this.paramOnce++;})
438      }.borderWidth(2)
439      Column() {
440        Text('根据父组件情况决定')
441        Text(`inheritConsumer: ${this.inheritConsumer}`).onClick(()=>{this.inheritConsumer++;})
442        Text(`plusParam: ${this.plusParam}`)
443      }.borderWidth(2)
444      Column() {
445        Text('不被重置')
446        Text(`noDecoVariable: ${this.noDecoVariable}`)
447        Text(`noDecoInfo.age: ${this.noDecoInfo.age}`).onClick(()=>{this.noDecoInfo.age++;}) // 能够触发刷新但是复用时不会被重置
448        Text(`readOnlyVariable: ${this.readOnlyVariable}`)
449      }.borderWidth(2)
450    }
451  }
452}
453```
454
455开发者可以尝试点击各个变量,并点击`回收/复用`按钮查看复用后的重置情况。
456
457需要注意的是,上面的例子中`noDecoInfo`未被重置,如果存在监听`noDecoInfo.age`的\@Monitor,因为noDecoInfo本身未产生变化,所以该\@Monitor也不会被重置,因此在后续第一次更改`noDecoInfo.age`时,`IMonitorValue`的`before`值将不会被重置,仍是复用前的值。
458
459将上面的例子简化可得下面的例子:
460
461```ts
462@ObservedV2
463class Info {
464  @Trace age: number;
465  constructor(age: number) {
466    this.age = age;
467  }
468}
469@Entry
470@ComponentV2
471struct Index {
472  @Local condition: boolean = true;
473  build() {
474    Column() {
475      Button('回收/复用').onClick(()=>{this.condition=!this.condition;})
476      if (this.condition) {
477        ReusableV2Component()
478      }
479    }
480  }
481}
482@ReusableV2
483@ComponentV2
484struct ReusableV2Component {
485  noDecoInfo: Info = new Info(30); // 未加装饰器,被视作常量
486  @Monitor('noDecoInfo.age')
487  onAgeChange(monitor: IMonitor) {
488    console.log(`age change from ${monitor.value()?.before} to ${monitor.value()?.now}`);
489  }
490  aboutToRecycle() {
491    this.noDecoInfo.age = 25;
492  }
493  aboutToReuse() {
494    this.noDecoInfo.age = 35;
495  }
496  build() {
497    Column() {
498	  Column() {
499        Text(`noDecoInfo.age: ${this.noDecoInfo.age}`)
500          .onClick(()=>{this.noDecoInfo.age++;}) // 能够触发刷新但是不会被重置
501      }
502    }
503  }
504}
505```
506
507建议按照下列步骤进行操作:
508
5091. 点击`noDecoInfo.age: 30`,UI刷新为`noDecoInfo.age: 31`,\@Monitor触发并输出日志`age change from 30 to 31`。
5102. 点击`回收/复用`两次,UI刷新为`noDecoInfo.age: 35`,\@Monitor触发并输出日志`age change from 31 to 35`。
5113. 点击`noDecoInfo.age: 35`,UI刷新为`noDecoInfo.age: 36`,\@Monitor触发并输出日志`age change from 35 to 36`。
512
513由于冻结机制的存在,在aboutToRecycle中赋值不会被\@Monitor观察到。而在经历完变量重置后,变量又会被赋予新的值,因此对于组件内状态变量来说,在aboutToRecycle中赋值不会有明显的效果;而常量(例如上面的`noDecoInfo`)由于冻结机制的存在,在aboutToRecycle中更改`age`也不会被观察到,并且因为不会被重置,所以相关的\@Monitor也不会被重置,即这里的`age`值本身未被重置,也就不会重置与之绑定的\@Monitor。最终表现出来的现象即:第二步回调的\@Monitor中,`monitor.value()?.before`得到的值为31,而非age的初始值30。
514
515针对这一现象,推荐开发者在复用的场景减少使用类似的常量对象包含\@Trace属性的写法,以确保复用场景的功能符合预期。
516
517## 使用场景
518
519### 在if组件中使用
520
521通过改变if组件的条件可以控制组件回收/复用。
522
523```ts
524@Entry
525@ComponentV2
526struct Index {
527  @Local condition: boolean = true;
528  build() {
529    Column() {
530      Button('回收/复用').onClick(()=>{this.condition=!this.condition;}) // 点击切换回收/复用状态
531      if (this.condition) {
532        ReusableV2Component()
533      }
534    }
535  }
536}
537@ReusableV2
538@ComponentV2
539struct ReusableV2Component {
540  @Local message: string = 'Hello World';
541  aboutToRecycle() {
542    console.log('ReusableV2Component aboutToRecycle'); // 回收时被调用
543  }
544  aboutToReuse() {
545    console.log('ReusableV2Component aboutToReuse'); // 复用时被调用
546  }
547  build() {
548    Column() {
549      Text(this.message)
550    }
551  }
552}
553```
554
555### 在Repeat组件non-virtualScroll场景的each属性中使用
556
557Repeat组件non-virtualScroll场景中,会在删除/创建子树时触发回收/复用。
558
559```ts
560@Entry
561@ComponentV2
562struct Index {
563  @Local simpleList: number[] = [1, 2, 3, 4, 5];
564  @Local condition: boolean = true;
565  build() {
566    Column() {
567      Button('删除/创建Repeat').onClick(()=>{this.condition=!this.condition;})
568      Button('增加元素').onClick(()=>{this.simpleList.push(this.simpleList.length+1);})
569      Button('删除元素').onClick(()=>{this.simpleList.pop();})
570      Button('更改元素').onClick(()=>{this.simpleList[0]++;})
571      if (this.condition) {
572        List({ space: 10 }) {
573          Repeat(this.simpleList)
574            .each((obj: RepeatItem<number>) => {
575              ListItem() {
576                Column() {
577                  ReusableV2Component({ num: obj.item })
578                }
579              }
580            })
581        }
582      }
583    }
584  }
585}
586@ReusableV2
587@ComponentV2
588struct ReusableV2Component {
589  @Require @Param num: number;
590  aboutToAppear() {
591    console.log('ReusableV2Component aboutToAppear');
592  }
593  aboutToRecycle() {
594    console.log('ReusableV2Component aboutToRecycle');
595  }
596  aboutToReuse() {
597    console.log('ReusableV2Component aboutToReuse');
598  }
599  build() {
600    Column() {
601      Text(`${this.num}`)
602    }
603  }
604}
605```
606
607### 在Repeat组件virtualScroll场景的each属性中使用
608
609Repeat组件virtualScroll场景中,将会优先使用Repeat组件的缓存池,正常滑动场景、更新场景不涉及组件的回收与复用。当Repeat的缓存池需要扩充时将会向自定义组件要求新的子组件,此时如果复用池中有可复用的节点,将会进行复用。
610
611下面的例子中,先点击`改变condition`会让3个节点进入复用池,而后向下滑动List组件时,可以观察到日志输出`ReusableV2Component aboutToReuse`,表明Repeat可以使用自定义组件的复用池填充自己的缓存池。
612
613```ts
614@Entry
615@ComponentV2
616struct Index {
617  @Local condition: boolean = true;
618  @Local simpleList: number[] = [];
619  aboutToAppear(): void {
620    for (let i = 0; i < 100; i++) {
621      this.simpleList.push(i)
622    }
623  }
624  build() {
625    Column() {
626      Button('改变condition').onClick(()=>{this.condition=!this.condition;})
627      if (this.condition) {
628        // 此处仅做演示使用,让复用池中填充3个组件
629        ReusableV2Component({ num: 0})
630        ReusableV2Component({ num: 0})
631        ReusableV2Component({ num: 0})
632      }
633      List({ space: 10 }) {
634        Repeat(this.simpleList)
635          .virtualScroll()
636          .each((obj: RepeatItem<number>) => {
637            ListItem() {
638              Column() {
639                ReusableV2Component({ num: obj.item })
640              }
641            }
642          })
643      }.height('50%')
644      .cachedCount(2)
645    }
646  }
647}
648@ReusableV2
649@ComponentV2
650struct ReusableV2Component {
651  @Require @Param num: number;
652  aboutToAppear() {
653    console.log('ReusableV2Component aboutToAppear');
654  }
655  aboutToRecycle() {
656    console.log('ReusableV2Component aboutToRecycle');
657  }
658  aboutToReuse() {
659    console.log('ReusableV2Component aboutToReuse');
660  }
661  build() {
662    Column() {
663      Text(`${this.num}`).fontSize(50)
664    }
665  }
666}
667```
668
669### 在ForEach组件中使用
670>**说明:**
671>
672>推荐开发者使用Repeat组件的non-virtualScroll场景代替ForEach组件
673
674下面的例子中使用了ForEach组件渲染了数个可复用组件,由于每次点击`点击修改`按钮时key值都会发生变化,因此从第二次点击开始都会触发回收与复用(由于ForEach先判断有无可复用节点时复用池仍未初始化,因此第一次点击会创建新的节点,而后初始化复用池同时回收节点)。
675
676```ts
677@Entry
678@ComponentV2
679struct Index {
680  @Local simpleList: number[] = [0, 1, 2, 3, 4, 5];
681  build() {
682    Column() {
683      ForEach(this.simpleList, (num: number, index) => {
684        Row() {
685          Button('点击修改').onClick(()=>{this.simpleList[index]++;})
686          ReusableV2Component({ num: num })
687        }
688      }) // 每次修改完key发生变化
689    }
690  }
691}
692@ReusableV2
693@ComponentV2
694struct ReusableV2Component {
695  @Require @Param num: number;
696  aboutToAppear() {
697    console.log('ReusableV2Component aboutToAppear', this.num); // 创建时触发
698  }
699  aboutToRecycle() {
700    console.log('ReusableV2Component aboutToRecycle', this.num); // 回收时触发
701  }
702  aboutToReuse() {
703    console.log('ReusableV2Component aboutToReuse', this.num); // 复用时触发
704  }
705  build() {
706    Column() {
707      Text(`child: ${this.num}`)
708    }
709  }
710}
711```
712
713
714### 在LazyForEach组件中使用
715>**说明:**
716>
717>推荐开发者使用Repeat组件的virtualScroll场景代替LazyForEach组件
718
719下面的例子中使用了LazyForEach渲染了数个可复用组件,在滑动时可以先观察到组件创建,直到预加载节点全部创建完成之后,再滑动则触发复用和回收。
720
721```ts
722class BasicDataSource implements IDataSource {
723  private listeners: DataChangeListener[] = [];
724  private originDataArray: StringData[] = [];
725
726  public totalCount(): number {
727    return 0;
728  }
729
730  public getData(index: number): StringData {
731    return this.originDataArray[index];
732  }
733
734  registerDataChangeListener(listener: DataChangeListener): void {
735    if (this.listeners.indexOf(listener) < 0) {
736      console.info('add listener');
737      this.listeners.push(listener);
738    }
739  }
740
741  unregisterDataChangeListener(listener: DataChangeListener): void {
742    const pos = this.listeners.indexOf(listener);
743    if (pos >= 0) {
744      console.info('remove listener');
745      this.listeners.splice(pos, 1);
746    }
747  }
748
749  notifyDataReload(): void {
750    this.listeners.forEach(listener => {
751      listener.onDataReloaded();
752    })
753  }
754
755  notifyDataAdd(index: number): void {
756    this.listeners.forEach(listener => {
757      listener.onDataAdd(index);
758    })
759  }
760
761  notifyDataChange(index: number): void {
762    this.listeners.forEach(listener => {
763      listener.onDataChange(index);
764    })
765  }
766
767  notifyDataDelete(index: number): void {
768    this.listeners.forEach(listener => {
769      listener.onDataDelete(index);
770    })
771  }
772
773  notifyDataMove(from: number, to: number): void {
774    this.listeners.forEach(listener => {
775      listener.onDataMove(from, to);
776    })
777  }
778
779  notifyDatasetChange(operations: DataOperation[]): void {
780    this.listeners.forEach(listener => {
781      listener.onDatasetChange(operations);
782    })
783  }
784}
785
786class MyDataSource extends BasicDataSource {
787  private dataArray: StringData[] = [];
788
789  public totalCount(): number {
790    return this.dataArray.length;
791  }
792
793  public getData(index: number): StringData {
794    return this.dataArray[index];
795  }
796
797  public addData(index: number, data: StringData): void {
798    this.dataArray.splice(index, 0, data);
799    this.notifyDataAdd(index);
800  }
801
802  public pushData(data: StringData): void {
803    this.dataArray.push(data);
804    this.notifyDataAdd(this.dataArray.length - 1);
805  }
806}
807
808@ObservedV2
809class StringData {
810  @Trace message: string;
811  constructor(message: string) {
812    this.message = message;
813  }
814}
815
816@Entry
817@ComponentV2
818struct Index {
819  data: MyDataSource = new MyDataSource(); // 数据源
820
821  aboutToAppear() {
822    for (let i = 0; i <= 200; i++) {
823      this.data.pushData(new StringData('Hello' + i));
824    }
825  }
826  build() {
827    List({ space: 3 }) {
828      LazyForEach(this.data, (item: StringData, index: number) => {
829        ListItem() {
830          Column() {
831            Text(item.message)
832            ChildComponent({ data: item.message })
833              .onClick(() => {
834                item.message += '!'; // message为@Trace装饰的变量,可观察变化
835              })
836          }
837        }
838      })
839    }.cachedCount(5)
840  }
841}
842
843@ReusableV2
844@ComponentV2
845struct ChildComponent {
846  @Param @Require data: string;
847  aboutToAppear(): void {
848    console.log('ChildComponent aboutToAppear', this.data)
849  }
850  aboutToDisappear(): void {
851    console.log('ChildComponent aboutToDisappear', this.data)
852  }
853  aboutToReuse(): void {
854    console.log('ChildComponent aboutToReuse', this.data) // 复用时触发
855  }
856  aboutToRecycle(): void {
857    console.log('ChildComponent aboutToRecycle', this.data) // 回收时触发
858  }
859  build() {
860    Row() {
861      Text(this.data).fontSize(50)
862    }
863  }
864}
865```
866
867