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