• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Creating a Custom Component
2
3
4In ArkUI, components are what's displayed on the UI. They can be classified as built-in components – those directly provided by the ArkUI framework, and custom components – those defined by developers. Defining the entire application UI with just built-in components would lead to a monolithic design, low code maintainability, and poor execution performance. A good UI is the result of a well-thought-out development process, with such factors as code reusability, separation of service logic from the UI, and version evolution carefully considered. Creating custom components that encapsulate the UI and some business logic is a critical step in this process.
5
6
7The custom component has the following features:
8
9
10- Combinable: allows you to combine built-in components and other components, as well as their attributes and methods.
11
12- Reusable: can be reused by other components and used as different instances in different parent components or containers.
13
14- Data-driven update: holds some state and triggers UI re-rendering with the change of state variables.
15
16## Basic Usage of Custom Components
17
18The following example shows the basic usage of a custom component.
19
20```ts
21@Component
22struct HelloComponent {
23  @State message: string = 'Hello, World!';
24
25  build() {
26    // The HelloComponent custom component combines the <Row> and <Text> built-in components.
27    Row() {
28      Text(this.message)
29        .onClick(() => {
30          // 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!".
31          this.message = 'Hello, ArkUI!';
32        })
33    }
34  }
35}
36```
37> **NOTE**
38>
39> To reference the custom component in another file, use the keyword **export** to export the component and then use **import** to import it to the target file.
40
41Multiple **HelloComponent** instances can be created in the **build()** function of other custom components. In this way, **HelloComponent** is reused by those custom components.
42
43```ts
44class HelloComponentParam {
45  message: string = ""
46}
47
48@Entry
49@Component
50struct ParentComponent {
51  param: HelloComponentParam = {
52    message: 'Hello, World!'
53  }
54
55  build() {
56    Column() {
57      Text('ArkUI message')
58      HelloComponent(this.param);
59      Divider()
60      HelloComponent(this.param);
61    }
62  }
63}
64```
65
66
67To fully understand the preceding example, a knowledge of the following concepts is essential:
68
69
70- [Basic Structure of a Custom Component](#basic-structure-of-a-custom-component)
71
72- [Member Functions/Variables](#member-functionsvariables)
73
74- [Rules for Custom Component Parameters](#rules-for-custom-component-parameters)
75
76- [build Function](#build-function)
77
78- [Universal Style of a Custom Component](#universal-style-of-a-custom-component)
79
80
81## Basic Structure of a Custom Component
82
83- struct: The 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.
84  > **NOTE**
85  >
86  > The name or its class or function name of a custom component must be different from that of any built-in components.
87
88- \@Component: The \@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.
89  > **NOTE**
90  >
91  > This decorator can be used in ArkTS widgets since API version 9.
92  >
93  > An optional parameter of the Boolean type can be used in the \@Component since API version 11.
94
95  ```ts
96  @Component
97  struct MyComponent {
98  }
99  ```
100
101  ### freezeWhenInactive<sup>11+</sup>
102  Describes the [custom component freezing](arkts-custom-components-freeze.md) option.
103
104  | Name  | Type  | Mandatory| Description                                                        |
105  | ------ | ------ | ---- | ------------------------------------------------------------ |
106  | freezeWhenInactive | bool | No| Whether to enable the component freezing.|
107
108  ```ts
109  @Component({ freezeWhenInactive: true })
110  struct MyComponent {
111  }
112  ```
113
114- build(): The **build()** function is used to define the declarative UI description of a custom component. Every custom component must define a **build()** function.
115
116  ```ts
117  @Component
118  struct MyComponent {
119    build() {
120    }
121  }
122  ```
123
124- \@Entry: A custom component decorated with \@Entry is used as the default entry component of the page. Only one component can be decorated with \@Entry in a single page. The \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md).
125
126  > **NOTE**
127  >
128  > This decorator can be used in ArkTS widgets since API version 9.
129  >
130  > Since API version 10, the \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md) or type [EntryOptions](#entryOptions).
131  >
132  > This decorator can be used in atomic services since API version 11.
133
134  ```ts
135  @Entry
136  @Component
137  struct MyComponent {
138  }
139  ```
140
141  ### EntryOptions<sup>10+</sup>
142
143  Describes the named route options.
144
145  | Name  | Type  | Mandatory| Description                                                        |
146  | ------ | ------ | ---- | ------------------------------------------------------------ |
147  | routeName | string | No| Name of the target named route.|
148  | storage | [LocalStorage](arkts-localstorage.md) | No| Storage of the page-level UI state.|
149  | useSharedStorage<sup>12+</sup> | boolean | No| Whether to use the [LocalStorage](arkts-localstorage.md) object returned by the **LocalStorage.getShared()** API.<br>Default value: **false**|
150
151  > **NOTE**
152  >
153  > When **useSharedStorage** is set to **true** and **storage** is assigned a value, the value of **useSharedStorage** has a higher priority.
154
155  ```ts
156  @Entry({ routeName : 'myPage' })
157  @Component
158  struct MyComponent {
159  }
160  ```
161
162
163- \@Reusable: Custom components decorated by \@Reusable can be reused.
164
165  > **NOTE**
166  >
167  > This decorator can be used in ArkTS widgets since API version 10.
168
169  ```ts
170  @Reusable
171  @Component
172  struct MyComponent {
173  }
174  ```
175
176
177## Member Functions/Variables
178
179In addition to the mandatory **build()** function, a custom component may implement other member functions with the following restrictions:
180
181
182- Access to the member functions is private. Avoid declaring the member functions as static functions.
183
184
185A custom component can also implement member variables with the following restrictions:
186
187
188- Access to the member variables is private. Avoid declaring the member variables as static variables.
189
190- 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).
191
192
193## Rules for Custom Component Parameters
194
195As can be learnt from preceding examples, a custom component can be created from a **build** method. During the creation, the custom component's parameters are initialized based on the decorator rules.
196
197
198```ts
199@Component
200struct MyComponent {
201  private countDownFrom: number = 0;
202  private color: Color = Color.Blue;
203
204  build() {
205  }
206}
207
208@Entry
209@Component
210struct ParentComponent {
211  private someColor: Color = Color.Pink;
212
213  build() {
214    Column() {
215      // Create an instance of MyComponent and initialize its countDownFrom variable with the value 10 and its color variable with the value this.someColor.
216      MyComponent({ countDownFrom: 10, color: this.someColor })
217    }
218  }
219}
220```
221
222In the following example, a function in the parent component is passed to the child component and called therein.
223
224```ts
225@Entry
226@Component
227struct Parent {
228  @State cnt: number = 0
229  submit: () => void = () => {
230    this.cnt++;
231  }
232
233  build() {
234    Column() {
235      Text(`${this.cnt}`)
236      Son({ submitArrow: this.submit })
237    }
238  }
239}
240
241@Component
242struct Son {
243  submitArrow?: () => void
244
245  build() {
246    Row() {
247      Button('add')
248        .width(80)
249        .onClick(() => {
250          if (this.submitArrow) {
251            this.submitArrow()
252          }
253        })
254    }
255    .justifyContent(FlexAlign.SpaceBetween)
256    .height(56)
257  }
258}
259```
260
261## build() Function
262
263Whatever declared in the **build()** function 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 the **build()** function. 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 the **build()** function. 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 a: 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. Use **if** instead. The following is an example:
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. The following example should be avoided:
377
378  ```ts
379  build() {
380    Column() {
381      // Avoid: expressions.
382      (this.aVar > 10) ? Text('...') : Image('...')
383    }
384  }
385  ```
386
387- Directly changing a state variable is not allowed. The following example should be avoided:
388
389  ```ts
390  @Component
391  struct CompA {
392    @State col1: Color = Color.Yellow;
393    @State col2: Color = Color.Green;
394    @State count: number = 1;
395    build() {
396      Column() {
397        // Avoid: directly changing the value of count in the <Text> component.
398        Text(`${this.count++}`)
399          .width(50)
400          .height(50)
401          .fontColor(this.col1)
402          .onClick(() => {
403            this.col2 = Color.Red;
404          })
405        Button("change col1").onClick(() =>{
406          this.col1 = Color.Pink;
407        })
408      }
409      .backgroundColor(this.col2)
410    }
411  }
412  ```
413
414  In ArkUI state management, UI re-render is driven by state.
415
416  ![en-us_image_0000001651365257](figures/en-us_image_0000001651365257.png)
417
418  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:
419
420  - 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.col2** 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.
421  - Minimized update (API version 9 or later): When **this.col2** is changed, only the **Column** component is updated, and the **Text** component is not changed. When **this.col1** is changed, the entire **Text** component is updated and all of its attribute functions are executed. As a result, the value of **${this.count++}** in the **Text** component is changed. 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.col1** = **Color.Pink** - > **Text** component re-render - > **this.count++** - > **Text** component 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.
422
423  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:
424
425  - Changing the state variable within the \@Builder, \@Extend, or \@Styles decorated method
426
427  - Changing the application state variable in the function called during parameter calculation, for example, **Text('${this.calcLabel()}')**
428
429  - Modifying the current array: In the following code snippet, **sort()** changes the array **this.arr**, and the subsequent **filter** method returns a new array.
430
431    ```ts
432    // Avoid the usage below.
433    @State arr : Array<...> = [ ... ];
434    ForEach(this.arr.sort().filter(...),
435      item => {
436      ...
437    })
438    // Prefer: Call filter before sort() to return a new array. In this way, sort() does not change this.arr.
439    ForEach(this.arr.filter(...).sort(),
440      item => {
441      ...
442    })
443    ```
444
445## Universal Style of a Custom Component
446
447The universal style of a custom component is configured by invoking chainable attribute methods.
448
449
450```ts
451@Component
452struct MyComponent2 {
453  build() {
454    Button(`Hello World`)
455  }
456}
457
458@Entry
459@Component
460struct MyComponent {
461  build() {
462    Row() {
463      MyComponent2()
464        .width(200)
465        .height(300)
466        .backgroundColor(Color.Red)
467    }
468  }
469}
470```
471
472> **NOTE**
473>
474> When ArkUI sets styles for custom components, an invisible container component is set for **MyComponent2**. These styles are set on the container component instead of the **Button** component of **MyComponent2**. As seen from the rendering result, the red background color is not directly applied to the button. Instead, it is applied to the container component that is invisible to users where the button is located.
475
476<!--no_check-->
477