• 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 **build()** of other custom components. In this way, **HelloComponent** is reused by those custom components.
42
43
44
45```ts
46@Entry
47@Component
48struct ParentComponent {
49  build() {
50    Column() {
51      Text('ArkUI message')
52      HelloComponent({ message: 'Hello World!' });
53      Divider()
54      HelloComponent({message: 'Hello, World!'});
55    }
56  }
57}
58```
59
60
61To fully understand the preceding example, a knowledge of the following concepts is essential:
62
63
64- [Basic Structure of a Custom Component](#basic-structure-of-a-custom-component)
65
66- [Member Functions/Variables](#member-functionsvariables)
67
68- [Rules for Custom Component Parameters](#rules-for-custom-component-parameters)
69
70- [The build Function](#the-build-function)
71
72- [Universal Style of a Custom Component](#universal-style-of-a-custom-component)
73
74
75## Basic Structure of a Custom Component
76
77### struct
78
79The 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.
80
81  > **NOTE**
82  >
83  > The name or its class or function name of a custom component must be different from that of any built-in components.
84
85### \@Component
86
87The \@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.
88
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()
115
116The **build** function is used to define the declarative UI description of a custom component. Every custom component must define a **build** function.
117
118  ```ts
119  @Component
120  struct MyComponent {
121    build() {
122    }
123  }
124  ```
125
126### \@Entry
127
128A 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).
129
130  > **NOTE**
131  >
132  > This decorator can be used in ArkTS widgets since API version 9.
133  >
134  > Since API version 10, the \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md) or type [EntryOptions](#entryoptions10).
135  >
136  > This decorator can be used in atomic services since API version 11.
137
138  ```ts
139  @Entry
140  @Component
141  struct MyComponent {
142  }
143  ```
144
145#### EntryOptions<sup>10+</sup>
146
147  Describes the named route options.
148
149| Name  | Type  | Mandatory| Description                                                        |
150| ------ | ------ | ---- | ------------------------------------------------------------ |
151| routeName | string | No| Name of the target named route.|
152| storage | [LocalStorage](arkts-localstorage.md) | No| Storage of the page-level UI state.|
153| 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**|
154
155  > **NOTE**
156  >
157  > When **useSharedStorage** is set to **true** and **storage** is assigned a value, the value of **useSharedStorage** has a higher priority.
158
159  ```ts
160  @Entry({ routeName : 'myPage' })
161  @Component
162  struct MyComponent {
163  }
164  ```
165
166
167### \@Reusable
168
169Custom components decorated by \@Reusable can be reused. For details, see [\@Reusable Decorator: Reusing Components](./arkts-reusable.md#use-scenario).
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
183## Member Functions/Variables
184
185In addition to the mandatory **build()**, a custom component may implement other member functions with the following restrictions:
186
187
188- Access to the member functions is private. Avoid declaring the member functions as static functions.
189
190
191A custom component can also implement member variables with the following restrictions:
192
193
194- Access to the member variables is private. Avoid declaring the member variables as static variables.
195
196- 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).
197
198
199## Rules for Custom Component Parameters
200
201As 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.
202
203
204```ts
205@Component
206struct MyComponent {
207  private countDownFrom: number = 0;
208  private color: Color = Color.Blue;
209
210  build() {
211  }
212}
213
214@Entry
215@Component
216struct ParentComponent {
217  private someColor: Color = Color.Pink;
218
219  build() {
220    Column() {
221      // Create an instance of MyComponent and initialize its countDownFrom variable with the value 10 and its color variable with the value this.someColor.
222      MyComponent({ countDownFrom: 10, color: this.someColor })
223    }
224  }
225}
226```
227
228In the following example, a function in the parent component is passed to the child component and called therein.
229
230```ts
231@Entry
232@Component
233struct Parent {
234  @State cnt: number = 0
235  submit: () => void = () => {
236    this.cnt++;
237  }
238
239  build() {
240    Column() {
241      Text(`${this.cnt}`)
242      Son({ submitArrow: this.submit })
243    }
244  }
245}
246
247@Component
248struct Son {
249  submitArrow?: () => void
250
251  build() {
252    Row() {
253      Button('add')
254        .width(80)
255        .onClick(() => {
256          if (this.submitArrow) {
257            this.submitArrow()
258          }
259        })
260    }
261    .height(56)
262  }
263}
264```
265
266## The build Function
267
268Whatever declared in **build()** are called UI descriptions. UI descriptions must comply with the following rules:
269
270- 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.
271  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.
272
273  ```ts
274  @Entry
275  @Component
276  struct MyComponent {
277    build() {
278      // Exactly one root component is required, and it must be a container component.
279      Row() {
280        ChildComponent()
281      }
282    }
283  }
284
285  @Component
286  struct ChildComponent {
287    build() {
288      // Exactly one root component is required, and it is not necessarily a container component.
289      Image('test.jpg')
290    }
291  }
292  ```
293
294- Local variable declaration is not allowed. The following example should be avoided:
295
296  ```ts
297  build() {
298    // Avoid: declaring a local variable.
299    let num: number = 1;
300  }
301  ```
302
303- **console.info** can be used in the UI description only when it is in a method or function. The following example should be avoided:
304
305  ```ts
306  build() {
307    // Avoid: using console.info directly in UI description.
308    console.info('print debug log');
309  }
310  ```
311
312- Creation of a local scope is not allowed. The following example should be avoided:
313
314  ```ts
315  build() {
316    // Avoid: creating a local scope.
317    {
318      // ...
319    }
320  }
321  ```
322
323- Only methods decorated by \@Builder can be called. The parameters of built-in components can be the return values of TS methods.
324
325  ```ts
326  @Component
327  struct ParentComponent {
328    doSomeCalculations() {
329    }
330
331    calcTextValue(): string {
332      return 'Hello World';
333    }
334
335    @Builder doSomeRender() {
336      Text(`Hello World`)
337    }
338
339    build() {
340      Column() {
341        // Avoid: calling a method not decorated by @Builder.
342        this.doSomeCalculations();
343        // Prefer: Call an @Builder decorated method.
344        this.doSomeRender();
345        // Prefer: Pass the return value of a TS method as the parameter.
346        Text(this.calcTextValue())
347      }
348    }
349  }
350  ```
351
352- 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.
353
354  ```ts
355  build() {
356    Column() {
357      // Avoid: using the switch syntax.
358      switch (expression) {
359        case 1:
360          Text('...')
361          break;
362        case 2:
363          Image('...')
364          break;
365        default:
366          Text('...')
367          break;
368      }
369      // Correct usage: Use if.
370      if(expression == 1) {
371        Text('...')
372      } else if(expression == 2) {
373        Image('...')
374      } else {
375        Text('...')
376      }
377    }
378  }
379  ```
380
381- Expressions are not allowed except for the **if** component. Refer to the code snippet below.
382
383  ```ts
384  build() {
385    Column() {
386      // Avoid: expressions.
387      (this.aVar > 10) ? Text('...') : Image('...')
388
389      // Positive example: Use if for judgment.
390      if(this.aVar > 10) {
391        Text('...')
392      } else {
393        Image('...')
394      }
395    }
396  }
397  ```
398
399- 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).
400
401  ```ts
402  @Component
403  struct MyComponent {
404    @State textColor: Color = Color.Yellow;
405    @State columnColor: Color = Color.Green;
406    @State count: number = 1;
407    build() {
408      Column() {
409        // Avoid: directly changing the value of count in the <Text> component.
410        Text(`${this.count++}`)
411          .width(50)
412          .height(50)
413          .fontColor(this.textColor)
414          .onClick(() => {
415            this.columnColor = Color.Red;
416          })
417        Button("change textColor").onClick(() =>{
418          this.textColor = Color.Pink;
419        })
420      }
421      .backgroundColor(this.columnColor)
422    }
423  }
424  ```
425
426  In ArkUI state management, UI re-render is driven by state.
427
428  ![en-us_image_0000001651365257](figures/en-us_image_0000001651365257.png)
429
430  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:
431
432  - 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.
433  - Minimized update (API version 9 or later): When **this.columnColor** is changed, only the **Column** component is updated, and the **Text** component is not changed. When **this.textColor** is changed, the entire **Text** component is updated and all of its 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.
434
435  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:
436
437  - Changing the state variable within the \@Builder, \@Extend, or \@Styles decorated method
438
439  - Changing the application state variable in the function called during parameter calculation, for example, **Text('${this.calcLabel()}')**
440
441  - Modifying the current array: In the following code snippet, **sort()** changes the array **this.arr**, and the subsequent **filter** method returns a new array.
442
443    ```ts
444    // Avoid the usage below.
445    @State arr : Array<...> = [ ... ];
446    ForEach(this.arr.sort().filter(...),
447      item => {
448      // ...
449    })
450    // Prefer: Call filter before sort() to return a new array. In this way, sort() does not change this.arr.
451    ForEach(this.arr.filter(...).sort(),
452      item => {
453      // ...
454    })
455    ```
456
457## Universal Style of a Custom Component
458
459The universal style of a custom component is configured by the chain call.
460
461
462```ts
463@Component
464struct ChildComponent {
465  build() {
466    Button(`Hello World`)
467  }
468}
469
470@Entry
471@Component
472struct MyComponent {
473  build() {
474    Row() {
475      ChildComponent()
476        .width(200)
477        .height(300)
478        .backgroundColor(Color.Red)
479    }
480  }
481}
482```
483
484> **NOTE**
485>
486> When ArkUI sets styles for custom components, an invisible container component is set for **ChildComponent**. These styles are set on the container component instead of the **Button** component of **ChildComponent**. 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.
487