HarmonyOS development: wrapBuilder to encapsulate global @ Builder

AbnerMingAbnerMing
4 min read

Foreword

this article code case based on api13.

@ Builder decorator can extract the component code in the build function separately. Although the build function is simplified and the reuse between components is realized, the code is still in the entire UI view, as shown in the following case:

@Entry
@Component
struct Index {
  @Builder
  TextView(text: string) {
    Text(text)
  }

  build() {
    Column() {
      this.TextView("test data 1")
      this.TextView("test data 2")
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

it can only be said that in the same UI view, component reuse is realized and the code is simplified. However, what if there are many pages sharing a component? Fortunately, the @ Builder decorator supports global definitions, which have been outlined in previous articles, and can extract common components into a global file.

@Builder
export function TextView(text: string) {
  Text(text)
}

Call directly where needed.

import { TextView } from './Views'

@Entry
@Component
struct Index {

  build() {
    Column() {
     TextView("test data 1")
     TextView("test data 2")
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

Well, looking back, we found that the @ Builder decorator can be local or global, which has already met the demand. What do you need wrapBuilder? Don't worry, let's look at these scenes first:

1. There is a custom Dialog pop-up window that needs to receive the UI view transferred from the outside. How do I transfer the component?

2. There is an encapsulated Network Library, one of which is to request Loading. Since the Loading of each project is different, this Loading component view needs to be passed separately. How?

How can I pass externally defined UI components to where they are needed? These needed places are not modified by struct. Let's think about it. Can they be passed on directly?

Let's look at another case. Although we can define a global @ Builder decorator, when I combine it into an array, I cannot call it, as follows:

import { TextView } from './Views';


let builderArr: Function[] = [TextView]


@Entry
@Component
struct Index {

  build() {
    Column() {

      ForEach(builderArr, (item: Function) => {
        item()
      })

    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

direct error reporting:

'item()' does not comply with the UI component syntax. <ArkTSCheck>

in order to solve the above problems,Hongmeng introduces wrapBuilder as a global @ Builder encapsulation function and returns a WrappedBuilder object to implement global @ Builder assignments and passes can be made.

How to use

the wrapBuilder is a template function that returns a WrappedBuilder object.

declare function wrapBuilder< Args extends Object[]>(builder: (...args: Args) => void): WrappedBuilder;

Declaring Variables

let builderVar: WrappedBuilder<[string, number]> = wrapBuilder(MyBuilder)

simple case:

import { TextView } from './Views'

@Entry
@Component
struct Index {
  textView: WrappedBuilder<[string]> = wrapBuilder(TextView)

  build() {
    Column() {
      this.textView.builder("test data")
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

of course, variables can also be declared directly where the component is defined.

@Builder
function TextView(test: string) {
  Text(test)
}

export let textView: WrappedBuilder<[string]> = wrapBuilder(TextView)

Call:

import { textView } from './Views'

@Entry
@Component
struct Index {
  build() {
    Column() {
      textView.builder("test data")
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

transfer Data

in fact, in the above case, it is already a case of passing parameters, following the principle of passing whatever type is received.

For example, it receives a string type.

let textView: WrappedBuilder<[string]> = wrapBuilder(TextView)

For example, receive two parameters, one is string and the other is number.

@Builder
function TextView(test: string,num:number) {
  Text(test)
}

export let textView: WrappedBuilder<[string,number]> = wrapBuilder(TextView)

If too many parameters are passed, they can be passed in the form of objects, arrays, collections, etc. Here is a reference type:

defining Objects

export class TestBean {
  testData?: string = "test data"
}

creating a Global Component

import { TestBean } from "./TestBean"

@Builder
function TextView(test: TestBean) {
  Text(test.testData)
}

export let textView: WrappedBuilder<[TestBean]> = wrapBuilder(TextView)

component call

import { TestBean } from './TestBean';
import { textView } from './Views'

@Entry
@Component
struct Index {
  @State test: TestBean = new TestBean();

  build() {
    Column() {
      textView.builder({ testData: this.test.testData })
      Button("click").onClick(() => {
        this.test.testData = "change data"
      })
    }
    .height('100%')
    .width('100%')
    .justifyContent(FlexAlign.Center)
  }
}

first of all, the first point, in the same UI component, the same wrapBuilder can only be initialized once. The second point is that the builder attribute method of the WrappedBuilder object can only be used inside the struct.

How to receive a UI component from outside is very simple. You only need to receive a WrappedBuilder<[Object]>. The simple case is as follows:

export class ViewUtils {
  view?: WrappedBuilder<[Object]>

  setView(view: WrappedBuilder<[Object]>) {
    this.view = view
  }

  getView(): WrappedBuilder<[Object]> | undefined {
    return this.view
  }
}
0
Subscribe to my newsletter

Read articles from AbnerMing directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

AbnerMing
AbnerMing