In ArkUI, the content displayed in the UI consists of components, with those provided directly by the framework referred to as system components, and those defined by developers referred to as custom components. When developing a UI interface, it is usually not a simple matter of combining system components; rather, it is necessary to consider factors such as code reusability, separation of business logic and UI, and the evolution of subsequent versions. Therefore, encapsulating the UI and some business logic into custom components is an indispensable capability.
Custom Components#
Custom components have the following characteristics:
- Composable: Allows developers to combine system components and their properties and methods.
- Reusable: Custom components can be reused by other components and used as different instances in different parent components or containers.
- Data-driven UI updates: UI refresh is driven by changes in state variables.
Basic Usage of Custom Components#
@Component
struct HelloComponent {
@State message: string = 'Hello, World!';
build() {
// HelloComponent custom component combines system components Row and Text
Row() {
Text(this.message)
.onClick(() => {
// The change of the state variable message drives the UI refresh, the UI refreshes from 'Hello, World!' to 'Hello, ArkUI!'
this.message = 'Hello, ArkUI!';
})
}
}
}
::: warning Note
If this custom component is referenced in another file, it needs to be exported using the export keyword and imported on the page where it is used.
:::
HelloComponent can be created multiple times in the build() function of other custom components, achieving reuse of the custom component.
class HelloComponentParam {
message: string = ""
}
@Entry
@Component
struct ParentComponent {
param: HelloComponentParam = {
message: 'Hello, World!'
}
build() {
Column() {
Text('ArkUI message')
HelloComponent(this.param);
Divider()
HelloComponent(this.param);
}
}
}
Basic Structure of Custom Components#
- struct: Custom components are implemented based on struct, and the combination of struct + custom component name + {...} constitutes a custom component, with no inheritance relationship. The instantiation of struct can omit new.
::: tip Note
The custom component name, class name, and function name cannot be the same as the system component name.
:::
- @Component: The @Component decorator can only decorate data structures declared with the struct keyword. After being decorated with @Component, a struct has the ability to be componentized and needs to implement the build method to describe the UI; a struct can only be decorated by one @Component. @Component can accept an optional bool type parameter.
::: tip Note
Starting from API version 9, this decorator supports use in ArkTS cards.
Starting from API version 11, @Component can accept an optional bool type parameter.
:::
@Component
struct MyComponent {
}
freezeWhenInactive#
Component freeze option.
@Component({ freezeWhenInactive: true })
struct MyComponent {
}
build() Function#
The build() function is used to define the declarative UI description of the custom component, and the custom component must define the build() function.
@Component
struct MyComponent {
build() {
}
}
@Entry#
Custom components decorated with @Entry will serve as the entry point for the UI page. At most, one custom component can be decorated with @Entry in a single UI page. @Entry can accept an optional LocalStorage parameter.
::: tip Note
Starting from API version 9, this decorator supports use in ArkTS cards.
Starting from API version 10, @Entry can accept an optional LocalStorage parameter or an optional EntryOptions parameter.
Starting from API version 11, this decorator supports use in meta services.
:::
@Entry
@Component
struct MyComponent {
}```
EntryOptions10#
Named route jump options.
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
@Reusable: Custom components decorated with @Reusable have reusable capabilities.
Member Functions/Variables#
- In addition to needing to implement the build() function, custom components can also implement other member functions, with the following constraints:
- Member functions of custom components are private and should not be declared as static functions.
- Custom components can contain member variables, with the following constraints:
- Member variables of custom components are private and should not be declared as static variables.
- Local initialization of member variables in custom components is sometimes optional and sometimes required. For specifics on whether local initialization is needed or whether member variables of child components need to be initialized from parent components via parameters, please refer to state management.
Parameter Specifications for Custom Components#
From the examples above, we have learned that custom components can be created in the build method, and during the creation of custom components, parameters can be initialized according to the rules of the decorators.
@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue;
build() {
}
}
@Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink;
build() {
Column() {
// Create an instance of MyComponent, initializing the member variable countDownFrom to 10 and the member variable color to this.someColor
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
build() Function#
All statements declared in the build() function are collectively referred to as UI descriptions, which need to follow the following rules:
- For custom components decorated with @Entry, the root node under the build() function must be unique and necessary, and must be a container component, where ForEach is prohibited as the root node.
- For custom components decorated with @Component, the root node under the build() function must be unique and necessary, and can be a non-container component, where ForEach is prohibited as the root node.
@Entry
@Component
struct MyComponent {
build() {
// The root node must be unique and necessary, and must be a container component
Row() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
build() {
// The root node must be unique and necessary, and can be a non-container component
Image('test.jpg')
}
}
- Local variables are not allowed to be declared, as shown in the following example:
build() {
// Example: Local variables are not allowed to be declared
let a: number = 1;
}
- Direct use of console.info in UI descriptions is not allowed, but it is allowed in methods or functions, as shown in the following example.
build() {
// Example: console.info is not allowed
console.info('print debug log');
}
- Methods that are not decorated with @Builder cannot be called, but parameters of system components can be the return values of TS methods.
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// Example: Cannot call methods that are not decorated with @Builder
this.doSomeCalculations();
// Correct example: Can call
this.doSomeRender();
// Correct example: Parameters can be the return values of TS methods
Text(this.calcTextValue())
}
}
}
- The switch syntax is not allowed; if conditional judgment is needed, please use if. An example is as follows.
build() {
Column() {
// Example: switch syntax is not allowed
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
// Correct example: use if
if(expression == 1) {
Text('...')
} else if(expression == 2) {
Image('...')
} else {
Text('...')
}
}
}
- Direct changes to state variables are not allowed, as shown in the following example. For detailed analysis, see @State common issues: Direct changes to state variables in build are not allowed.
@Component
struct CompA {
@State col1: Color = Color.Yellow;
@State col2: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
// Should avoid directly changing the value of count within the Text component
Text(`${this.count++}`)
.width(50)
.height(50)
.fontColor(this.col1)
.onClick(() => {
this.col2 = Color.Red;
})
Button("change col1").onClick(() =>{
this.col1 = Color.Pink;
})
}
.backgroundColor(this.col2)
}
}
In ArkUI state management, state drives UI updates.
Therefore, direct changes to state variables in the build() or @Builder methods of custom components are not allowed, as this may cause the risk of infinite rendering. Text('${this.count++}') will have different effects during full updates or minimal updates:
-
Full updates (API 8 and earlier versions): ArkUI may fall into an infinite re-rendering loop because each rendering of the Text component changes the application state, triggering the next round of rendering. When this.col2 changes, the entire build function will be executed, thus the text bound to
Text(${this.count++})
will also change, causing the state variable this.count to update with each re-rendering ofText(${this.count++})
, leading to another round of build execution, resulting in an infinite loop. -
Minimal updates (API 9 to present): When this.col2 changes, only the Column component will update, and the Text component will not change. Only when this.col1 changes will the entire Text component be updated, executing all its property functions, so you will see
Text(${this.count++})
increment. Since the UI updates on a component basis, if a property of a component changes, the entire component will update. Therefore, the overall update chain is: this.col1 = Color.Pink -> Text component overall update -> this.count++ -> Text component overall update. It is worth noting that this writing style will cause the Text component to render twice during the initial rendering, thus affecting performance.
Changing application state in the build function can be more subtle than the examples above, such as:
- Changing state variables within @Builder, @Extend, or @Styles methods.
- Changing application state variables when calculating parameters, for example,
Text('${this.calcLabel()}')
. - Modifying the current array, where sort() changes the array this.arr, and subsequent filter methods will return a new array.
Common Styles for Custom Components#
Custom components set common styles through chained calls with “.”.
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
::: tip Note
When ArkUI sets styles for custom components, it is equivalent to wrapping MyComponent2 in an invisible container component, and these styles are applied to the container component rather than directly to the Button component of MyComponent2. From the rendering results, we can clearly see that the red background color does not directly take effect on the Button but takes effect on the developer-invisible container component where the Button is located.
:::
This article was updated by Mix Space to xLog. The original link is http://www.sroxck.top/posts/harmony/arkts-components