HarmonyOS:@LocalBuilder装饰器: 维持组件父子关系

一、前言

当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入@LocalBuilder装饰器。@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。
在阅读本文档前,建议提前阅读:@Builder
说明

从API version 12开始支持。

二、装饰器使用说明

2.1 自定义组件内自定义构建函数

定义的语法:

复制代码
@LocalBuilder MyBuilderFunction() { ... }

使用方法

复制代码
this.MyBuilderFunction()
  • 允许在自定义组件内定义一个或多个@LocalBuilder方法,该方法被认为是该组件的私有、特殊类型的成员函数。
  • 自定义构建函数可以在所属组件的build方法和其他自定义构建函数中调用,但不允许在组件外调用。
  • 在自定义函数体中,this指代当前所属组件,组件的状态变量可以在自定义构建函数内访问。建议通过this访问自定义组件的状态变量而不是参数传递。

三、限制条件

  • @LocalBuilder只能在所属组件内声明,不允许全局声明。
  • @LocalBuilder不能被内置装饰器和自定义装饰器使用。
  • 自定义组件内的静态方法不能和@LocalBuilder一起使用。

四、@LocalBuilder和局部@Builder使用区别

@Builder方法引用传参时,为了改变this指向,使用bind(this)后,会导致组件的父子关系和状态管理的父子关系不一致,但

@LocalBuilder是否使用bind(this),都不会改变组件的父子关系。@LocalBuilder和@Builder区别说明。

五、参数传递规则

@LocalBuilder函数的参数传递有按值传递和按引用传递两种,均需遵守以下规则:

  • 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。
  • 在@LocalBuilder修饰的函数内部,不允许改变参数值。
  • @LocalBuilder内UI语法遵循UI语法规则。
  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。
5.1 按引用传递参数

按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@LocalBuilder方法内的UI刷新。

若子组件调用父组件的@LocalBuilder函数,传入的参数发生变化,不会引起@LocalBuilder方法内的UI刷新。
使用场景:

组件TestLocalBuilder内的@LocalBuilder方法在build函数内调用,按键值对写法进行传值,当点击Click me 时,@LocalBuilder内的Text文本内容会随着状态变量内容的改变而改变。

复制代码
class ReferenceType {
  paramString: string = '';
}

@Entry
@Component
struct TestLocalBuilder {
  @State variableValue: string = 'Hello World';

  @LocalBuilder
  citeLocalBuilder(params: ReferenceType) {
    Row() {
      Text(`UseStateVarByReference: ${params.paramString} `)
    }
  };

  build() {
    Column({ space: 10 }) {
      this.citeLocalBuilder({ paramString: this.variableValue });
      Button('Click me').onClick(() => {
        this.variableValue = 'Hi World';
      })
    }
    .padding(20)
  }
}

效果图


按引用传递参数时,如果在@LocalBuilder方法内调用自定义组件,ArkUI提供$$作为按引用传递参数的范式。

使用场景:

组件TestLocalBuilder1内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。

复制代码
class ReferenceType2 {
  paramString: string = '';
}

@Component
struct HelloComponent22 {
  @Prop message: string;

  build() {
    Row() {
      Text(`HelloComponent===${this.message}`);
    }
  }
}

@Entry
@Component
struct TestLocalBuilder2 {
  @State variableValue: string = 'Hello World';

  @LocalBuilder
  citeLocalBuilder($$: ReferenceType2) {
    Row() {
      Column() {
        Text(`citeLocalBuilder===${$$.paramString}`);
        HelloComponent22({ message: $$.paramString });
      }
    }
  }

  build() {
    Column({ space: 10 }) {
      this.citeLocalBuilder({ paramString: this.variableValue });
      Button('Click me').onClick(() => {
        this.variableValue = 'Hi World';
      })
    }
  }
}

效果图


子组件引用父组件的@LocalBuilder函数,传入的参数为状态变量,状态变量的改变不会引发@LocalBuilder方法内的UI刷新,原因是@Localbuilder装饰的函数绑定在父组件上,状态变量刷新机制是刷新本组件以及其子组件,对父组件无影响,故无法引发刷新。若使用@Builder修饰则可引发刷新,原因是@Builder改变了函数的this指向,此时函数被绑定到子组件上,故能引发UI刷新。

使用场景:

组件Child将@State修饰的label值按照函数传参方式传递到Parent的@Builder和@LocalBuilder函数内,在被@Builder修饰的函数内,this指向Child,参数变化能引发UI刷新,在被@LocalBuilder修饰的函数内,this指向Parent,参数变化不能引发UI刷新。

复制代码
class LayoutSize3 {
  size:number = 0;
}

@Entry
@Component
struct TestLocalBuilder3 {
  label:string = 'parent';
  @State layoutSize:LayoutSize3 = {size:0};

  @LocalBuilder
  // @Builder
  componentBuilder($$:LayoutSize3) {
    Text(`${'this :'+this.label}`);
    Text(`${'size :'+$$.size}`);
  }

  build() {
    Column() {
      Child33({contentBuilder: this.componentBuilder });
    }
  }
}

@Component
struct Child33 {
  label:string = 'child';
  @BuilderParam contentBuilder:((layoutSize: LayoutSize3) => void);
  @State layoutSize:LayoutSize3 = {size:0};

  build() {
    Column() {
      this.contentBuilder({size: this.layoutSize.size});
      Button("add child size").onClick(()=>{
        this.layoutSize.size += 1;
      })
    }
  }
}

效果图


使用@Builder参数变化可以引发UI刷新

六、按值传递参数

调用@LocalBuilder装饰的函数默认按值传递。当传递的参数为状态变量时,状态变量的改变不会引起@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递
使用场景:

组件Parent将@State修饰的label值按照函数传参方式传递到@LocalBuilder函数内,此时@LocalBuilder函数获取到的值为普通变量值,所以改变@State修饰的label值时,@LocalBuilder函数内的值不会发生改变。

复制代码
@Entry
@Component
struct TestLocalBuilder4 {
  @State label: string = 'Hello';

  @LocalBuilder
  citeLocalBuilder(paramA1: string) {
    Row() {
      Text(`UseStateVarByValue: ${paramA1} `)
    }
  }

  build() {
    Column({space: 10}) {
      this.citeLocalBuilder(this.label);
      Button("点击改变@State修饰的label值").onClick(()=>{
        this.label = "word";
        console.log(`点击 改变@State修饰的label值 ,  this.label = ${ this.label}`);
      })
    }
  }
}

效果图

七、@LocalBuilder和@Builder区别说明

函数componentBuilder被@Builder修饰时,显示效果是 "Child",函数componentBuilder被@LocalBuilder修饰时,显示效果是"Parent"。

说明:

@Builder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即"Child"。

@LocalBuilder componentBuilder()通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即"Parent"。

复制代码
@Component
struct Child5 {
  label: string = `Child5`;
  @BuilderParam customBuilderParam: () => void;

  build() {
    Column() {
      this.customBuilderParam()
    }
  }
}

@Entry
@Component
struct TestLocalBuilder5 {
  label: string = `Parent`;

  @Builder componentBuilder() {
    Text(`${this.label}`)
  }

  // @LocalBuilder componentBuilder() {
  //   Text(`${this.label}`)
  // }

  build() {
    Column() {
      Child5({ customBuilderParam: this.componentBuilder })
    }
  }
}

函数componentBuilder被@Builder修饰时,显示效果是 "Child"效果图


函数componentBuilder被@LocalBuilder修饰时,显示效果是"Parent"效果图

八、使用场景

@LocalBuilder在@ComponentV2修饰的自定义组件中使用

使用局部的@LocalBuilder在@ComponentV2修饰的自定义组件中调用,修改变量触发UI刷新。

复制代码
@ObservedV2
class Info6 {
  @Trace name: string = '';
  @Trace age: number = 0;
}

@ComponentV2
struct ChildPage6 {
  @Require @Param childInfo: Info6;
  build() {
    Column() {
      Text(`自定义组件 name :${this.childInfo.name}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Text(`自定义组件 age :${this.childInfo.age}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    }
  }
}

@Entry
@ComponentV2
struct TestLocalBuilder6 {
  info1: Info = { name: "Tom", age: 25 };
  @Local info2: Info = { name: "Tom", age: 25 };

  @LocalBuilder
  privateBuilder() {
    Column() {
      Text(`局部LocalBuilder@Builder name :${this.info1.name}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Text(`局部LocalBuilder@Builder age :${this.info1.age}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    }
  }

  @LocalBuilder
  privateBuilderSecond() {
    Column() {
      Text(`局部LocalBuilder@Builder name :${this.info2.name}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Text(`局部LocalBuilder@Builder age :${this.info2.age}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    }
  }
  build() {
    Column() {
      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      this.privateBuilder() // 调用局部@Builder
      Line()
        .width('100%')
        .height(10)
        .backgroundColor('#000000').margin(10)
      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text1
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      this.privateBuilderSecond() // 调用局部@Builder
      Line()
        .width('100%')
        .height(10)
        .backgroundColor('#000000').margin(10)
      Text(`info1: ${this.info1.name}  ${this.info1.age}`) // Text1
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      ChildPage6({ childInfo: this.info1}) // 调用自定义组件
      Line()
        .width('100%')
        .height(10)
        .backgroundColor('#000000').margin(10)
      Text(`info2: ${this.info2.name}  ${this.info2.age}`) // Text2
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      ChildPage6({ childInfo: this.info2}) // 调用自定义组件
      Line()
        .width('100%')
        .height(10)
        .backgroundColor('#000000').margin(10)
      Button("change info1&info2")
        .onClick(() => {
          this.info1 = { name: "Cat", age: 18} // Text1不会刷新,原因是没有装饰器修饰监听不到值的改变。
          this.info2 = { name: "Cat", age: 18} // Text2会刷新,原因是有装饰器修饰,可以监听到值的改变。
        })
    }
  }
}

效果图

相关推荐
爱笑的眼睛111 小时前
HarmonyOS后台代理提醒机制深度解析与实践
华为·harmonyos
穆雄雄4 小时前
sd 适配 OpenHarmony构建指南
鸿蒙
爱笑的眼睛118 小时前
ArkTS接口与泛型在HarmonyOS应用开发中的深度应用
华为·harmonyos
大雷神10 小时前
【鸿蒙星光分享】HarmonyOS 语音朗读功能同步教程
华为·harmonyos
不凡的凡10 小时前
flutter 管理工具fvm
flutter·harmonyos
柒儿吖11 小时前
Electron for HarmonyOS_PC Swifty 密码管理器适配开源鸿蒙PC开发实践
javascript·electron·harmonyos
一只栖枝11 小时前
HarmonyOS 开发高级认证是什么?含金量高吗?
华为·华为认证·harmonyos·鸿蒙·考证
柒儿吖12 小时前
Electron for 鸿蒙PC - 菜单栏完整开发指南:从原生菜单到自定义菜单的实现
javascript·electron·harmonyos
A懿轩A12 小时前
【2025最新】最新HarmonyOS 6 DevEco Studio下载安装 详细步骤(带图展示)
华为·harmonyos
大雷神12 小时前
HarmonyOS文字书写功能实现指南
华为·harmonyos