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