• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Creating a Custom Component
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @jiyujia926; @huyisuo-->
5<!--Designer: @zhangboren-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9In ArkUI, components refer to the elements displayed on the UI. They fall into two categories: built-in components (provided by the ArkUI framework out of the box) and custom components (defined by developers). While it is technically possible to build an entire UI using only built-in components, this approach often results in a monolithic structure, leading to low code maintainability and suboptimal performance. A well-designed UI requires careful planning, balancing factors such as code reusability, the separation of service logic from UI layers, and adaptability to version evolution. Creating custom components, which encapsulate UI elements and service logic, serves as a critical step in achieving this goal.
10
11Custom components offer the following features:
12
13- Combinability: You can combine built-in components and other components, as well as their attributes and methods.
14
15- Reusability: Custom components can be reused across different components, serving as distinct instances in various parent components or containers.
16
17- Data-driven update: Custom components can hold internal state variables. When these state variables change, UI re-rendering is triggered.
18
19## Basic Usage of Custom Components
20
21The following example shows the basic usage of a custom component.
22
23```ts
24@Component
25struct HelloComponent {
26  @State message: string = 'Hello, World!';
27
28  build() {
29    // The HelloComponent custom component combines the Row and Text built-in components.
30    Row() {
31      Text(this.message)
32        .onClick(() => {
33          // The change of the state variable message drives the UI to be re-rendered. As a result, the text changes from "Hello, World!" to "Hello, ArkUI!".
34          this.message = 'Hello, ArkUI!';
35        })
36    }
37  }
38}
39```
40> **NOTE**
41>
42> To reference a custom component in another file, use the keyword **export** to export the component and then use **import** to import it to the target file.
43
44Multiple **HelloComponent** instances can be created in **build()** of other custom components. In this way, **HelloComponent** is reused across those components.
45
46```ts
47@Entry
48@Component
49struct ParentComponent {
50  build() {
51    Column() {
52      Text('ArkUI message')
53      HelloComponent({ message: 'Hello World!' });
54      Divider()
55      HelloComponent({message: 'Hello, World!'});
56    }
57  }
58}
59```
60
61To fully understand the preceding example, a knowledge of the following concepts is essential:
62
63- [Basic Structure of a Custom Component](#basic-structure-of-a-custom-component)
64
65- [Member Functions/Variables](#member-functionsvariables)
66
67- [Rules for Custom Component Parameters](#rules-for-custom-component-parameters)
68
69- [build()](#build)
70
71- [Universal Style of a Custom Component](#universal-style-of-a-custom-component)
72
73
74## Basic Structure of a Custom Component
75
76### struct
77
78The definition of a custom component must start with the \@Component struct followed by the component name, and then component body enclosed by curly brackets. No inheritance is allowed. You can omit the **new** operator when instantiating a struct.
79
80  > **NOTE**
81  >
82  > The name assigned to a class, function, or custom component must be different from the name of any built-in component.
83
84### \@Component
85
86The \@Component decorator can decorate only the structs declared by the **struct** keyword. When being decorated by \@Component, a struct has the componentization capability. You must implement the **build** function for it to describe the UI. Each struct can be decorated by only one \@Component. \@Component can accept an optional parameter of the Boolean type.
87
88  > **NOTE**
89  >
90  > This decorator can be used in ArkTS widgets since API version 9.
91  >
92  > For the \@Component decorator, an optional parameter of the Boolean type is supported since API version 11.
93  >
94  > This decorator can be used in atomic services since API version 11.
95
96  ```ts
97  @Component
98  struct MyComponent {
99  }
100  ```
101
102 #### freezeWhenInactive<sup>11+</sup>
103  Describes the [custom component freezing](arkts-custom-components-freeze.md) option.
104
105  | Name  | Type  | Mandatory| Description                                                        |
106  | ------ | ------ | ---- | ------------------------------------------------------------ |
107  | freezeWhenInactive | boolean | No| Whether to enable component freezing. The default value is **false**. **true** means to enable component freezing, and **false** means the opposite.|
108
109  ```ts
110  @Component({ freezeWhenInactive: true })
111  struct MyComponent {
112  }
113  ```
114
115### build()
116
117The **build()** function is used to define the declarative UI description of a custom component. Every custom component must define a **build()** function.
118
119  ```ts
120  @Component
121  struct MyComponent {
122    build() {
123    }
124  }
125  ```
126
127### \@Entry
128
129The @Entry decorator marks a custom component as the entry point of a page. A single page can only have one @Entry decorated custom component serving as its entry. The \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md).
130
131  > **NOTE**
132  >
133  > This decorator can be used in ArkTS widgets since API version 9.
134  >
135  > Since API version 10, the \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md) or type **EntryOptions**<sup>10+</sup>.
136  >
137  > This decorator can be used in atomic services since API version 11.
138
139  ```ts
140  @Entry
141  @Component
142  struct MyComponent {
143  }
144  ```
145
146#### EntryOptions<sup>10+</sup>
147
148  Describes the named route options.
149
150  | Name  | Type  | Mandatory| Description                                                        |
151  | ------ | ------ | ---- | ------------------------------------------------------------ |
152  | routeName | string | No| Name of the target named route.|
153  | storage | [LocalStorage](arkts-localstorage.md) | No| Storage of the page-level UI state. If no value is passed, the framework creates a new LocalStorage instance as the default value.|
154  | useSharedStorage<sup>12+</sup> | boolean | No| Whether to use the LocalStorage instance passed by [LocalContent](../../reference/apis-arkui/arkts-apis-window-WindowStage.md#loadcontent9). The default value is **false**. **true**: Use the shared [LocalStorage](arkts-localstorage.md) instance. **false**: Do not use the shared [LocalStorage](arkts-localstorage.md) instance.|
155
156  > **NOTE**
157  >
158  > When **useSharedStorage** is set to **true** and **storage** is assigned a value, the value of **useSharedStorage** has a higher priority.
159
160  ```ts
161  @Entry({ routeName : 'myPage' })
162  @Component
163  struct MyComponent {
164  }
165  ```
166
167### \@Reusable
168
169The \@Reusable decorator enables a custom component to be reusable. For details, see [\@Reusable Decorator: Reusing Components](./arkts-reusable.md#use-scenarios).
170
171  > **NOTE**
172  >
173  > This decorator can be used in ArkTS widgets since API version 10.
174
175  ```ts
176  @Reusable
177  @Component
178  struct MyComponent {
179  }
180  ```
181
182## Member Functions/Variables
183
184In addition to the mandatory **build()**, a custom component may implement other member functions with the following restrictions:
185
186- Member functions of a custom component can only be accessed from within the component. Avoid declaring them as static functions.
187
188A custom component can also implement member variables with the following restrictions:
189
190- Member variables of a custom component can only be accessed from within the component. Avoid declaring them as static variables.
191
192- Local initialization is optional for some member variables and mandatory for others. For details about whether local initialization or initialization from the parent component is required, see [State Management](arkts-state-management-overview.md).
193
194
195## Rules for Custom Component Parameters
196
197As can be seen from preceding examples, a custom component can be created using a **build** method. During the creation process, the custom component's parameters are initialized based on the decorator rules.
198
199```ts
200@Component
201struct MyComponent {
202  private countDownFrom: number = 0;
203  private color: Color = Color.Blue;
204
205  build() {
206  }
207}
208
209@Entry
210@Component
211struct ParentComponent {
212  private someColor: Color = Color.Pink;
213
214  build() {
215    Column() {
216      // Create an instance of MyComponent and initialize its countDownFrom variable with the value 10 and its color variable with the value this.someColor.
217      MyComponent({ countDownFrom: 10, color: this.someColor })
218    }
219  }
220}
221```
222
223In the following example, a function in the parent component is passed to the child component and called therein.
224
225```ts
226@Entry
227@Component
228struct Parent {
229  @State cnt: number = 0
230  submit: () => void = () => {
231    this.cnt++;
232  }
233
234  build() {
235    Column() {
236      Text(`${this.cnt}`)
237      Son({ submitArrow: this.submit })
238    }
239  }
240}
241
242@Component
243struct Son {
244  submitArrow?: () => void
245
246  build() {
247    Row() {
248      Button('add')
249        .width(80)
250        .onClick(() => {
251          if (this.submitArrow) {
252            this.submitArrow()
253          }
254        })
255    }
256    .height(56)
257  }
258}
259```
260
261## build()
262
263Whatever declared in **build()** are called UI descriptions. UI descriptions must comply with the following rules:
264
265- For an \@Entry decorated custom component, exactly one root component is required under **build()**. This root component must be a container component. **ForEach** is not allowed at the top level.
266  For an \@Component decorated custom component, exactly one root component is required under **build()**. This root component is not necessarily a container component. **ForEach** is not allowed at the top level.
267
268  ```ts
269  @Entry
270  @Component
271  struct MyComponent {
272    build() {
273      // Exactly one root component is required, and it must be a container component.
274      Row() {
275        ChildComponent()
276      }
277    }
278  }
279
280  @Component
281  struct ChildComponent {
282    build() {
283      // Exactly one root component is required, and it is not necessarily a container component.
284      Image('test.jpg')
285    }
286  }
287  ```
288
289- Local variable declaration is not allowed. The following example should be avoided:
290
291  ```ts
292  build() {
293    // Avoid: declaring a local variable.
294    let num: number = 1;
295  }
296  ```
297
298- **console.info** can be used in the UI description only when it is in a method or function. The following example should be avoided:
299
300  ```ts
301  build() {
302    // Avoid: using console.info directly in UI description.
303    console.info('print debug log');
304  }
305  ```
306
307- Creation of a local scope is not allowed. The following example should be avoided:
308
309  ```ts
310  build() {
311    // Avoid: creating a local scope.
312    {
313      // ...
314    }
315  }
316  ```
317
318- Only methods decorated by \@Builder can be called. The parameters of built-in components can be the return values of TS methods.
319
320  ```ts
321  @Component
322  struct ParentComponent {
323    doSomeCalculations() {
324    }
325
326    calcTextValue(): string {
327      return 'Hello World';
328    }
329
330    @Builder doSomeRender() {
331      Text(`Hello World`)
332    }
333
334    build() {
335      Column() {
336        // Avoid: calling a method not decorated by @Builder.
337        this.doSomeCalculations();
338        // Prefer: Call an @Builder decorated method.
339        this.doSomeRender();
340        // Prefer: Pass the return value of a TS method as the parameter.
341        Text(this.calcTextValue())
342      }
343    }
344  }
345  ```
346
347- The **switch** syntax is not allowed. If conditional judgment is required, use the [if](./arkts-rendering-control-ifelse.md) statement. Refer to the code snippet below.
348
349  ```ts
350  build() {
351    Column() {
352      // Avoid: using the switch syntax.
353      switch (expression) {
354        case 1:
355          Text('...')
356          break;
357        case 2:
358          Image('...')
359          break;
360        default:
361          Text('...')
362          break;
363      }
364      // Correct usage: Use if.
365      if(expression == 1) {
366        Text('...')
367      } else if(expression == 2) {
368        Image('...')
369      } else {
370        Text('...')
371      }
372    }
373  }
374  ```
375
376- Expressions are not allowed except for the **if** component. Refer to the code snippet below.
377
378  ```ts
379  build() {
380    Column() {
381      // Avoid: expressions.
382      (this.aVar > 10) ? Text('...') : Image('...')
383
384      // Positive example: Use if for judgment.
385      if(this.aVar > 10) {
386        Text('...')
387      } else {
388        Image('...')
389      }
390    }
391  }
392  ```
393
394- Directly changing a state variable is not allowed. The following example should be avoided. For details, see [Changing State Variables in build() Is Forbidden](./arkts-state.md#changing-state-variables-in-build-is-forbidden).
395
396  ```ts
397  @Component
398  struct MyComponent {
399    @State textColor: Color = Color.Yellow;
400    @State columnColor: Color = Color.Green;
401    @State count: number = 1;
402    build() {
403      Column() {
404        // Avoid directly changing the value of count in the Text component.
405        Text(`${this.count++}`)
406          .width(50)
407          .height(50)
408          .fontColor(this.textColor)
409          .onClick(() => {
410            this.columnColor = Color.Red;
411          })
412        Button("change textColor").onClick(() =>{
413          this.textColor = Color.Pink;
414        })
415      }
416      .backgroundColor(this.columnColor)
417    }
418  }
419  ```
420
421  In ArkUI state management, UI re-render is driven by state.
422
423  ![en-us_image_0000001651365257](figures/en-us_image_0000001651365257.png)
424
425  Therefore, do not change any state variable in the **build()** or \@Builder decorated method of a custom component. Otherwise, loop rendering may result. Depending on the update mode (full update or minimum update), **Text('${this.count++}')** imposes different effects:
426
427  - Full update (API version 8 or before): ArkUI may fall into an infinite re-rendering loop because each rendering of the **Text** component changes the application state and causes a new round of re-renders. When **this.columnColor** is changed, the entire **build** function is executed. As a result, the text bound to **Text(${this.count++})** is also changed. Each time **Text(${this.count++})** is re-rendered, the **this.count** state variable is updated, and a new round of **build** execution follows, resulting in an infinite loop.
428  - Minimized update (API version 9 or later): Changing **this.columnColor** updates the **Column** component, but not the **Text** component. The entire **Text** component is updated only when **this.textColor** changes. During the update, all of the component's attribute functions are executed. As a result, the value of **Text(${this.count++})** is incremented. Currently, the UI is updated by component. If an attribute of a component changes, the entire component is updated. Therefore, the overall update link is as follows: **this.textColor** = **Color.Pink** -> **Text** re-render -> **this.count++** -> **Text** re-render. It should be noted that this way of writing causes the **Text** component to be rendered twice during the initial render, which affects the performance.
429
430  The behavior of changing the application state in the **build** function may be more covert than that in the preceding example. The following are some examples:
431
432  - Changing the state variable within the \@Builder, \@Extend, or \@Styles decorated method
433
434  - Changing the application state variable in the function called during parameter calculation, for example, **Text('${this.calcLabel()}')**
435
436  - Modifying the current array: In the following code snippet, **sort()** changes the array **this.arr**, and the subsequent **filter** method returns a new array.
437
438    ```ts
439    // Incorrect usage:
440    @State arr : Array<...> = [ ... ];
441    ForEach(this.arr.sort().filter(...),
442      item => {
443      // ...
444    })
445    // Prefer: Call filter before sort() to return a new array. In this way, sort() does not change this.arr.
446    ForEach(this.arr.filter(...).sort(),
447      item => {
448      // ...
449    })
450    ```
451
452## Universal Style of a Custom Component
453
454The universal style of a custom component is configured by the chain call.
455
456```ts
457@Component
458struct ChildComponent {
459  build() {
460    Button(`Hello World`)
461  }
462}
463
464@Entry
465@Component
466struct MyComponent {
467  build() {
468    Row() {
469      ChildComponent()
470        .width(200)
471        .height(300)
472        .backgroundColor(Color.Red)
473    }
474  }
475}
476```
477
478> **NOTE**
479>
480> When applying styles to a custom component (**ChildComponent** in this example), the ArkUI framework implicitly wraps **ChildComponent** with an invisible container component. These styles are actually applied to this container component instead of the **Button** component inside **ChildComponent**. This behavior can be observed in rendering results: The red background color is not applied directly to the **Button** component; instead, it is rendered on the invisible container component that wraps the **Button** component.
481<!--no_check-->
482