鸿蒙中级课程笔记2—状态管理V2—@ReusableV2装饰器:组件复用

为了降低反复创建销毁自定义组件带来的性能开销,开发者可以使用@ReusableV2装饰@ComponentV2装饰的自定义组件,达成组件复用的效果。

在阅读本文前,建议提前阅读:@Reusable装饰器:V1组件复用

说明

从API version 18开始,可以使用@ReusableV2装饰@ComponentV2装饰的自定义组件。

从API version 18开始,该装饰器支持在元服务中使用。

概述

@ReusableV2用于装饰V2的自定义组件,表明该自定义组件具有被复用的能力:

  • @ReusableV2仅能装饰V2的自定义组件,即@ComponentV2装饰的自定义组件。并且仅能将@ReusableV2装饰的自定义组件作为V2自定义组件的子组件使用。
  • @ReusableV2同样提供了aboutToRecycleaboutToReuse的生命周期,在组件被回收时调用aboutToRecycle,在组件被复用时调用aboutToReuse,但与@Reusable不同的是,aboutToReuse没有入参。
  • 在回收阶段,会递归地调用所有子组件的aboutToRecycle回调(即使子组件未被标记可复用);在复用阶段,会递归地调用所有子组件的aboutToReuse回调(即使子组件未被标记可复用)。
  • @ReusableV2装饰的自定义组件会在被回收期间保持冻结状态,即无法触发UI刷新、无法触发@Monitor回调,与freezeWhenInactive标记位不同的是,在解除冻结状态后,不会触发延后的刷新。
  • @ReusableV2装饰的自定义组件会在复用时自动重置组件内状态变量的值、重新计算组件内@Computed以及与之相关的@Monitor。不建议开发者在aboutToRecycle中更改组件内状态变量,详见复用前的组件内状态变量重置
  • V1和V2的复用组件可在一定规则下混用,详见使用限制第二点。
  • 不建议开发者嵌套滥用@ReusableV2装饰器,这可能会导致复用效率降低以及内存开销变大。

装饰器说明

@ReusableV2装饰器 说明
装饰器参数
可装饰的组件 @ComponentV2装饰的自定义组件
装饰器作用 表明该组件可被复用

接口说明

reuse、ReuseOptions、ReuseIdCallback的接口说明参考API文档:复用选项

TypeScript 复制代码
@Entry
@ComponentV2
struct Index {
  build() {
    Column() {
      ReusableV2Component()
        .reuse({ reuseId: () => 'reuseComponent' }) // 使用'reuseComponent'作为reuseId
      ReusableV2Component()
        .reuse({ reuseId: () => '' }) // 使用空字符串将默认使用组件名'ReusableV2Component'作为reuseId
      ReusableV2Component() // 未指定reuseId将默认使用组件名'ReusableV2Component'作为reuseId
    }
  }
}
@ReusableV2
@ComponentV2
struct ReusableV2Component {
  build() {
  }
}

使用限制

代码参考@ReusableV2装饰器:组件复用

  • 仅能将@ReusableV2装饰的自定义组件作为V2自定义组件的子组件使用。如果在V1的自定义组件中使用V2的复用组件将导致编译期报错,编译期无法校验到的复杂场景下将会有运行时报错。
  • V1和V2支持部分混用场景。

    下文提到的描述对应关系如下表:

    描述 对应组件类型
    V1普通组件 @Component装饰的struct。
    V2普通组件 @ComponentV2装饰的struct。
    V1复用组件 @Reusable@Component装饰的struct。
    V2复用组件 @ReusableV2@ComponentV2装饰的struct。

    下面的表展示了V1和V2的混用支持关系,每行的含义为第一列作为父组件,能否将后面列的组件作为子组件。

    以第一行V1普通组件为例,可以将V1普通组件、V2普通组件以及V1复用组件作为子组件,但无法将V2复用组件作为子组件。

    展开

    混用支持关系 V1普通组件 V2普通组件 V1复用组件 V2复用组件
    V1普通组件 支持 支持 支持 不支持,编译报错
    V2普通组件 支持 支持 不支持,编译告警,实际使用子组件不创建 支持
    V1复用组件 支持 支持 支持 不支持,编译报错
    V2复用组件 支持 支持 不支持,编译报错 支持

    根据上表,仅支持12种可能的父子关系,不推荐开发者高度嵌套可复用组件,这会造成复用效率降低。

  • V2的复用组件当前不支持直接用于Repeat的template中,但是可以用在template中的V2自定义组件中。

TypeScript 复制代码
@Entry
@ComponentV2
struct Index {
  @Local arr: number[] = [1, 2, 3, 4, 5];
  build() {
    Column() {
      List() {
        Repeat(this.arr)
          .each(() => {})
          .virtualScroll()
          .templateId(() => 'a')
          .template('a', (ri) => {
            ListItem() {
              Column() {
                ReusableV2Component({ val: ri.item}) // 暂不支持,编译期报错
                ReusableV2Builder(ri.item) // 暂不支持,运行时报错
                NormalV2Component({ val: ri.item}) // 支持普通V2自定义组件下面包含V2复用组件
              }
            }
          })
      }
    }
  }
}
@ComponentV2
struct NormalV2Component {
  @Require @Param val: number;
  build() {
    ReusableV2Component({ val: this.val })
  }
}
@Builder
function ReusableV2Builder(param: number) {
  ReusableV2Component({ val: param })
}
@ReusableV2
@ComponentV2
struct ReusableV2Component {
  @Require @Param val: number;
  build() {
    Column() {
      Text(`val: ${this.val}`)
    }
  }
}

回收与复用的生命周期

@ReusableV2提供了aboutToRecycle以及aboutToReuse的生命周期,当组件被回收时触发aboutToRecycle,当组件被复用时触发aboutToReuse。

TypeScript 复制代码
import { hilog } from '@kit.PerformanceAnalysisKit';

const TAG = '[Sample_Reusablev2]';
const DOMAIN = 0xF811;

@Entry
@ComponentV2
struct Index {
  @Local condition1: boolean = false;
  @Local condition2: boolean = true;
  build() {
    Column(){
      Button('step1. appear')
        .onClick(() => {
          this.condition1 = true;
        })
      Button('step2. recycle')
        .onClick(() => {
          this.condition2 = false;
        })
      Button('step3. reuse')
        .onClick(() => {
          this.condition2 = true;
        })
      Button('step4. disappear')
        .onClick(() => {
          this.condition1 = false;
        })
      if (this.condition1) {
        NormalV2Component({ condition: this.condition2 })
      }
    }
  }
}
@ComponentV2
struct NormalV2Component {
  @Require @Param condition: boolean;
  build() {
    if (this.condition) {
      ReusableV2Component()
    }
  }
}
@ReusableV2
@ComponentV2
struct ReusableV2Component {
  aboutToAppear() {
    hilog.info(DOMAIN, TAG, 'ReusableV2Component aboutToAppear called'); // 组件创建时调用
  }
  aboutToDisappear() {
    hilog.info(DOMAIN, TAG, 'ReusableV2Component aboutToDisappear called'); // 组件销毁时调用
  }
  aboutToRecycle() {
    hilog.info(DOMAIN, TAG, 'ReusableV2Component aboutToRecycle called'); // 组件回收时调用
  }
  aboutToReuse() {
    hilog.info(DOMAIN, TAG, 'ReusableV2Component aboutToReuse called'); // 组件复用时调用
  }
  build() {
    Column() {
      Text('ReusableV2Component')
    }
  }
}

建议按下面顺序进行操作:

  1. 点击step1. appear,此时condition1变为true,Index中的if组件切换分支,创建出NormalV2Component,由于condition2初始值为true,所以NormalV2Component中的if条件满足,尝试创建ReusableV2Component。此时复用池中无元素,因此会创建ReusableV2Component,并回调aboutToAppear的方法,输出ReusableV2Component aboutToAppear called的日志。
  2. 点击step2. recycle,此时condition2变为false,通过@Param同步给NormalV2Component,if条件切换,由于ReusableV2Component使用了@ReusableV2,因此会将该组件回收至复用池而不是销毁,回调aboutToRecycle的方法并输出ReusableV2Component aboutToRecycle called的日志。
  3. 点击step3. reuse,此时condition2变为true,通过@Param传递给NormalV2Component,if条件切换,由于ReusableV2Component使用了@ReusableV2,因此在创建该组件时尝试去复用池中寻找。此时复用池中有第二步放入的组件实例,因此从复用池中取出复用,回调aboutToReuse方法并输出ReusableV2Component aboutToReuse called的日志。
  4. 点击step4. disappear,此时condition1变为false,Index组件中的if组件切换分支,销毁NormalV2Component,此时ReusableV2Component因为父组件销毁,所以会被一起销毁,回调aboutToDisappear的方法并输出ReusableV2Component aboutToDisappear called的日志。

倘若该复用组件下有子组件时,会在回收和复用时递归调用子组件的aboutToRecycle和aboutToReuse(与子组件是否被标记复用无关),直到遍历完所有的孩子组件。

复用阶段的冻结

在之前的复用中,V1组件在复用池中仍能响应更新,这会对性能带来一定的负面影响,需要开发者使用组件冻结能力,才能够使V1组件在复用池中时不响应更新。针对这一点,V2组件在复用时将会被自动冻结,不会响应在回收期间发生的变化。这一个期间包括aboutToRecycle,即aboutToRecycle中的修改不会刷新到UI上,也不会触发@Computed以及@Monitor。冻结状态将持续到aboutToReuse前,即aboutToReuse及之后的变量更改,才会正常触发UI刷新、@Computed重新计算以及@Monitor的调用。

在复杂的混用场景中,是否冻结的规则可以总结为以下两点:

  1. V1的组件根据是否开启组件冻结freezeWhenInactive决定。
  2. V2的组件自动被冻结。

复用前的组件内状态变量重置

与@Reusable不同的是,@ReusableV2在复用前会重置组件中的状态变量以及相关的@Computed、@Monitor的内容。在复用的过程当中,所有的V2自定义组件,无论是否被标记了@ReusableV2,都会经历这一个重置过程。

重置会按照变量在组件中定义的顺序按照下面的规则依次进行:

展开

装饰器 重置方法
@Local 直接使用定义时的初始值重新赋值。
@Param 如果有外部传入则使用外部传入值重新赋值,否则用本地初始值重新赋值。注意:@Once装饰的变量同样会被重置初始化一次。
@Event 如果有外部传入则使用外部传入值重新赋值,否则用本地初始值重新赋值。如果本地没有初始值,则生成默认的空实现。
@Provider 直接使用定义时的初始值重新赋值。
@Consumer 如果有对应的@Provider则直接使用@Provider对应的值,否则使用本地初始值重新赋值。
@Computed 使用当前最新的值重新计算一次,如果使用到的变量还未被重置,将会使用重置前的值,因此推荐开发者将@Computed定义在所使用的变量之后。
@Monitor 在上述所有变量重置完成之后触发。重置过程中产生的变量变化不会触发@Monitor回调,仅更新IMonitorValue中的before值。重置过程中不产生变化的赋值不会触发@Monitor的重置。
常量 包括readonly的常量,不重置。

使用场景

这一章没看到实际使用价值,之后再整理使用场景及后续部分。

相关推荐
张小勇2 小时前
uniappx开发鸿蒙app调试证书和正式证书极速配置
华为·harmonyos
智者知已应修善业2 小时前
【输入字符串不用数组回车检测转换连续数字为整数】2024-10-26
c语言·c++·经验分享·笔记·算法
JavaLearnerZGQ2 小时前
ElasticSearch 笔记1
大数据·笔记·elasticsearch
时光慢煮2 小时前
Flutter × OpenHarmony 跨端开发:实现排序与创建选项功能
flutter·华为·开源·openharmony
智者知已应修善业2 小时前
【整数各位和循环求在0-9范围】2024-10-27
c语言·c++·经验分享·笔记·算法
Miguo94well2 小时前
Flutter框架跨平台鸿蒙开发——养生APP的开发流程
flutter·华为·harmonyos·鸿蒙
代码游侠2 小时前
学习笔记——I2C(Inter-Intergrated Circuit)总线详解
arm开发·笔记·嵌入式硬件·学习·架构
后来后来啊2 小时前
20261.23 &1.24学习笔记
笔记·学习·算法
紫雾凌寒3 小时前
【 HarmonyOS 高频题】2026 最新 ArkUI 开发与组件面试题
ui·华为·面试·程序员·职场发展·harmonyos·ark-ui