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  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