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