ArkTs学习之ArkTS装饰器@Builder装饰器,@LocalBuilder装饰器,@BuilderParam装饰器(二)

一、@Builder装饰器

@Builder是一种更轻量的 UI 元素复用机制,可以将重复使用的 UI 元素抽象成一个方法,并用@Builder修饰该方法,此方法就可以在 build() 里调用。

❗️注意:自定义组件的build()方法中,不允许调用没有被@Builder装饰的方法

1. 在组件内定义构建函数

构建函数是该组件的私有、特殊类型的成员函数,只能在组件内被访问。

构建函数中,this 指当前所属组件,组件的状态变量可以在构建函数内访问。

定义的语法

@Builder MyBuilderFunction({ ... })

使用方法

this.MyBuilderFunction({ ... })

示例

@Entry
@Component
struct FirstComponent {
  @Builder MyBuilderFunction() {
    Text("我是在@builder里的text")
      .fontSize(30)
  }

  build() {
    //Column 是一个容器组件
    Column() {
      //第一个行
      Row() {
        this.MyBuilderFunction() //复用@Builder装饰的 MyBuilderFunction
      }
      //第二行
      Row() {
        this.MyBuilderFunction(); //复用@Builder装饰的 MyBuilderFunction
      }
    }
  }
}

2. 全局定义构建函数

全局的自定义构建函数可以被整个应用获取,不允许使用this和bind方法。

如果不涉及组件状态变化,建议使用全局定义构建方法。

定义的语法:多了function关键字

@Builder function MyGlobalBuilderFunction({ ... })

使用方法

MyGlobalBuilderFunction()

示例

@Entry
@Component
struct FirstComponent {
  build() {
    //Column 是一个容器组件
    Column() {
      //第一个行
      Row() {
        MyGlobalBuilderFunction() //复用@Builder装饰的全局 MyGlobalBuilderFunction
      }
      //第二行
      Row() {
        MyGlobalBuilderFunction(); //复用@Builder装饰的全局 MyGlobalBuilderFunction
      }
    }
  }
}

// 定义全局构建函数,需要添加function关键字
@Builder function MyGlobalBuilderFunction() {
  Text("我是在@builder里的Text").fontSize(30)
}

3. 参数传递规则

自定义构建函数的参数传递有按值传递按引用传递两种,均需遵守以下规则:

  • 参数的类型必须与参数声明的类型一致,不允许undefinednull和返回undefinednull的表达式。
  • 在自定义构建函数内部,不允许改变参数值。如果需要改变参数值,且同步回调用点,建议使用@Link。
  • @Builder内UI语法遵循UI语法规则。

🌾 1、按引用传递参数

按引用传递参数时,传递的参数可为状态变量,且状态变量的改变会引起@Builder方法内的UI刷新。ArkUI提供$$作为按引用传递参数的范式。

@Builder function ABuilder($$: { paramA1: string }) {
  Row() {
    Text(`UseStateVarByReference: ${$$.paramA1} `)
  }
}

@Entry
@Component
struct Parent {
  @State label: string = 'Hello';
  
  build() {
    Column() {
      // 在Parent组件中调用ABuilder的时候,将this.label引用传递给ABuilder
      ABuilder({ paramA1: this.label })
      Button('Click me').onClick(() => {
        // 点击“Click me”后,UI从“Hello”刷新为“ArkUI”
        this.label = 'ArkUI';
      })
    }
  }
}

🌾 2、按值传递参数

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

@Builder function ABuilder(paramA1: string) {
  Row() {
    Text(`UseStateVarByValue: ${paramA1} `)
  }
}

@Entry
@Component
struct Parent {
  label: string = 'Hello';

  build() {
    Column() {
      ABuilder(this.label)
    }
  }
}

二、@LocalBuilder装饰器

当开发者使用@Builder做引用数据传递时,会考虑组件的父子关系,使用了bind(this)之后,组件的父子关系和状态管理的父子关系并不一致。

为了解决组件的父子关系和状态管理的父子关系保持一致的问题,引入@LocalBuilder装饰器。

@LocalBuilder拥有和局部@Builder相同的功能,且比局部@Builder能够更好的确定组件的父子关系和状态管理的父子关系。

1. 在组件内定义构建函数

🌾 1、定义自定义组件

// 自定义组件
@Component
export struct CommonView{
  @State msg:string = "子 Child"
  @Builder customBuilder() {};
  @BuilderParam customBuilderParam:()=>void=this.customBuilder
  build() {
    Column() {
      this.customBuilderParam()
    }
  }
}

🌾2、使用该自定义组件 

import { CommonView } from "../components/CommonView";
@Entity
@Component
export struct Main {
 /*成员变量*/
  @State msg:string = "父Parent"
   
  /*构建函数*/
  // 通过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向在Child的label,即“Child”。
  @Builder customerBuilder(){
    Text(this.msg)
  }
  // 过this.componentBuilder的形式传给子组件@BuilderParam customBuilderParam,this指向Parent的label,即“Parent”。
  @LocalBuilder customerBuilder(){
    Text(this.msg)
  }
  build() {
    Column() {
      Text('Main')
      CommonView({customBuilderParam:this.customerBuilder})
      Text('End')
    }.width('100%').justifyContent(FlexAlign.Center)
  }
}

使用@Builder装饰的效果图:

使用@LocalBuilder装饰的效果图:

 

2. @LocalBuilder和@Builder区别说明

🔈说明

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

3. 参数传递规则

  • 参数的类型必须与参数声明的类型一致,不允许undefined、null和返回undefined、null的表达式。

  • 在@LocalBuilder修饰的函数内部,不允许改变参数值。

  • @LocalBuilder内UI语法遵循UI语法规则

  • 只有传入一个参数,且参数需要直接传入对象字面量才会按引用传递该参数,其余传递方式均为按值传递。

🌾 1、按值传递参数

当传递的参数为状态变量时,状态变量的改变不会引起@LocalBuilder方法内的UI刷新。所以当使用状态变量的时候,推荐使用按引用传递。

如下 点击事件 修改自定义组件参数内容后,自定义组件并不会刷新UI

import { ViewEntity } from "../animation/ViewEntity";
import { CommonView } from "../components/CommonView";

@Entity
@Component
export struct Main {
  /*成员变量*/
  @State viewEntity:ViewEntity = new ViewEntity();
  /*构建函数*/
  @Builder customerBuilder(content:string){
    Text(content) // 显示值传递的内
  }
  build() {
    Column() {
      Text('Main')
      this.customerBuilder(this.viewEntity.content) // 使用自定义组件,传值字符串内容
      Text('End')
      Button('修改数值').onClick(()=>{
        this.viewEntity.content = "修改后的内容" 
      })
    }.width('100%').justifyContent(FlexAlign.Center)
  }
}

🌾 2、按引用传递参数

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

组件Parent内的@LocalBuilder方法内调用自定义组件,且按照引用传递参数将值传递到自定义组件,

当Parent组件内状态变量值发生变化时,@LocalBuilder方法内的自定义组件HelloComponent的message值也会发生变化。

import { ViewEntity } from "../animation/ViewEntity";
import { CommonView } from "../components/CommonView";

@Entity
@Component
export struct Main {
  /*成员变量*/
  @State viewEntity:ViewEntity = new ViewEntity();
  /*构建函数*/
  @Builder customerBuilder($$:ViewEntity){
    Text($$.content) // 显示值传递的内
  }
  build() {
    Column() {
      Text('Main')
      this.customerBuilder({content:this.viewEntity.content})
      Text('End')
      Button('修改数值').onClick(()=>{
        this.viewEntity.content = "修改后的内容"
      })
    }.width('100%').justifyContent(FlexAlign.Center)
  }
}

三、@BuilderParam装饰器

引用@Builder函数,@LocalBuilder函数

当开发者创建了自定义组件,并想对该组件添加特定功能时,例如在自定义组件中添加一个点击跳转操作。若直接在组件内嵌入事件方法,将会导致所有引入该自定义组件的地方均增加了该功能。为解决此问题,ArkUI引入了@BuilderParam装饰器,@BuilderParam用来装饰指向@Builder方法、@LocalBuilder方法的变量,开发者可在初始化自定义组件时对此属性进行赋值,为自定义组件增加特定的功能。该装饰器用于声明任意UI描述的一个元素,类似slot占位符。

@BuilderParam用来装饰指向@Builder方法的变量,可在初始化自定义组件时对此属性进行赋值。

@Entry
@Component
struct Parent {
  @Builder MyBuilderFunction() {
    Text("我是在@builder里的Text").fontSize(30)
  }

  build() {
    Column() {
      // 使用自定义组件Child,并用MyBuilderFunction初始化自组件的builder参数
      Child({ builder: this.MyBuilderFunction })
    }
  }
}

@Component
struct Child {
  // 定义一个指向被@Builder修饰的方法的成员变量
  // 并且要求这个被@Builder修饰的方法:没有入参,没有返回值
  @BuilderParam builder: () => void;

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

1. 初始化@BuilderParam装饰的方法

@BuildParam装饰的方法只能被自定义构建函数(@Builder装饰的方法)初始化。

使用所属自定义组件的自定义构建函数或者全局的自定义构建函数,在本地初始化@BuilderParam。

@Builder function GlobalBuilder() {}

@Component
struct Child {
  @Builder doNothtingBuilder() {}
  //在组件内定义构建函数
  @BuilderParam aBuilder0: () => void = this.doNothtingBuilder()
  //全局定义构建函数
  @BuilderParam aBuilder1: () => void = GlobalBuilder();

  build() {
    this.aBuilder0()
    this.aBuilder1()
  }
}

用父组件自定义构建函数初始化子组件@BuildParam装饰的方法。

@Component
struct Child {
  @BuilderParam aBuilder0: () => void;
  
  build() {
    Column() {
      this.aBuilder0()
    }
  }
}

@Entry
@Component
struct Parent {
  @Builder componentBuilder() {
    Text(`Parent builder `)
  }

  build() {
    Column() {
      Child({ aBuilder0: this.componentBuilder })
    }
  }
}

需注意this指向正确。

以下示例中,Parent组件在调用this.componentBuilder()时,this.label指向其所属组件,即“Parent”。@Builder componentBuilder()传给子组件@BuilderParam aBuilder0,在Child组件中调用

this.aBuilder0()时,this.label指向在Child的label,即“Child”。

开发者谨慎使用bind改变函数调用的上下文,可能会使this指向混乱。

@Component
struct Child {
  label: string = `Child`
  @BuilderParam aBuilder0: () => void;

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

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

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

  build() {
    Column() {
      this.componentBuilder()
      Child({ aBuilder0: this.componentBuilder })
    }
  }
}

2. 使用场景

🌾 1. 参数初始化组件

@BuilderParam装饰的方法可以是有参数和无参数的两种形式,需与指向的@Builder方法类型匹配。@BuilderParam装饰的方法类型需要和@Builder方法类型一致。

@Builder function GlobalBuilder1($$: { label: string }) {
  Text($$.label)
    .width(400)
    .height(50)
    .backgroundColor(Color.Blue)
}

@Component
struct Child {
  label: string = 'Child'
  // 无参数类,指向的componentBuilder也是无参数类型
  @BuilderParam aBuilder0: () => void;
  // 有参数类型,指向的GlobalBuilder1也是有参数类型的方法
  @BuilderParam aBuilder1: ($$: { label: string }) => void;

  build() {
    Column() {
      this.aBuilder0()
      this.aBuilder1({ label: 'global Builder label' })
    }
  }
}

@Entry
@Component
struct Parent {
  label: string = 'Parent'

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

  build() {
    Column() {
      this.componentBuilder()
      Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 })
    }
  }
}

🌾 2. 尾随闭包初始化组件

在自定义组件中使用@BuilderParam装饰的属性时也可通过尾随闭包进行初始化。在初始化自定义组件时,组件后紧跟一个大括号“{}”形成尾随闭包场景。

此场景下自定义组件内有且仅有一个使用@BuilderParam装饰的属性。

开发者可以将尾随闭包内的内容看做@Builder装饰的函数传给@BuilderParam。示例如下:

// xxx.ets
@Component
struct CustomContainer {
  @Prop header: string;
  @BuilderParam closer: () => void

  build() {
    Column() {
      Text(this.header)
        .fontSize(30)
      this.closer()
    }
  }
}

@Builder function specificParam(label1: string, label2: string) {
  Column() {
    Text(label1)
      .fontSize(30)
    Text(label2)
      .fontSize(30)
  }
}

@Entry
@Component
struct CustomContainerUser {
  @State text: string = 'header';

  build() {
    Column() {
      // 创建CustomContainer,在创建CustomContainer时,通过其后紧跟一个大括号“{}”形成尾随闭包
      // 作为传递给子组件CustomContainer @BuilderParam closer: () => void的参数
      CustomContainer({ header: this.text }) {
        Column() {
          specificParam('testA', 'testB')
        }.backgroundColor(Color.Yellow)
        .onClick(() => {
          this.text = 'changeHeader';
        })
      }
    }
  }
}

posted on 2024-04-01 21:22  梁飞宇  阅读(1609)  评论(0)    收藏  举报