提交 c616179e 编写于 作者: E ester.zhou

Update docs (17050)

Signed-off-by: Nester.zhou <ester.zhou@huawei.com>
上级 cd29a802
......@@ -39,17 +39,37 @@
- [Resource Categories and Access](resource-categories-and-access.md)
- Learning ArkTS
- [Getting Started with ArkTS](arkts-get-started.md)
- ArkTS Syntax (Declarative UI)
- [Basic UI Description](arkts-basic-ui-description.md)
- Basic Syntax
- [Basic Syntax Overview](arkts-basic-syntax-overview.md)
- [Declarative UI Description](arkts-declarative-ui-description.md)
- Custom Component
- [Creating a Custom Component](arkts-create-custom-components.md)
- [Page and Custom Component Lifecycle](arkts-page-custom-components-lifecycle.md)
- [\@Builder: Custom Builder Function](arkts-builder.md)
- [\@BuilderParam: @Builder Function Reference](arkts-builderparam.md)
- [\@Styles: Definition of Resusable Styles](arkts-style.md)
- [\@Extend: Extension of Built-in Components](arkts-extend.md)
- [stateStyles: Polymorphic Style](arkts-statestyles.md)
- State Management
- [Basic Concepts](arkts-state-mgmt-concepts.md)
- [State Management with Page-level Variables](arkts-state-mgmt-page-level.md)
- [State Management with Application-level Variables](arkts-state-mgmt-application-level.md)
- [Dynamic UI Element Building](arkts-dynamic-ui-elememt-building.md)
- [Rendering Control](arkts-rendering-control.md)
- [Restrictions and Extensions](arkts-restrictions-and-extensions.md)
- FAQs
- [Full SDK Compilation Guide](full-sdk-compile-guide.md)
- [Guide to Switching to Full SDK](full-sdk-switch-guide.md)
- Tools
- [DevEco Studio (OpenHarmony) User Guide](deveco-studio-user-guide-for-openharmony.md)
\ No newline at end of file
- [State Management Overview](arkts-state-management-overview.md)
- Component State Management
- [\@State: State Owned by Component](arkts-state.md)
- [\@Prop: One-Way Synchronization from Parent to Child Components](arkts-prop.md)
- [\@Link: Two-Way Synchronization Between Parent and Child Components](arkts-link.md)
- [\@Provide and \@Consume: Two-Way Synchronization with Descendant Components](arkts-provide-and-consume.md)
- [\@Observed and \@ObjectLink: Observing Attribute Changes in Nested Class Objects](arkts-observed-and-objectlink.md)
- Application State Management
- [Application State Management Overview](arkts-application-state-management-overview.md)
- [LocalStorage: UI State Storage](arkts-localstorage.md)
- [AppStorage: Application-wide UI State Storage](arkts-appstorage.md)
- [PersistentStorage: Application State Persistence](arkts-persiststorage.md)
- [Environment: Device Environment Query](arkts-environment.md)
- Other State Management Features
- [Overview of Other State Management Features](arkts-other-state-mgmt-functions-overview.md)
- [\@Watch: Getting Notified of State Variable Changes](arkts-watch.md)
- [$$ Syntax: Two-Way Synchronization of Built-in Components](arkts-two-way-sync.md)
- Rendering Control
- [Rendering Control Overview](arkts-rendering-control-overview.md)
- [if/else: Conditional Rendering](arkts-rendering-control-ifelse.md)
- [ForEach: Rendering of Repeated Content](arkts-rendering-control-foreach.md)
- [LazyForEach: Lazy Data Loading](arkts-rendering-control-lazyforeach.md)
# Application State Management Overview
The decorators described in the previous topics are used to share state variables within a page, that is, within a component tree. If you want to share state data at the application level or across multiple pages, you would need to apply application-level state management. ArkTS provides a wide variety of application state management capabilities:
- [LocalStorage](arkts-localstorage.md): API for storing the UI state, usually used for state sharing within a [UIAbility](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis/js-apis-app-ability-uiAbility.md) or between pages.
- [AppStorage](arkts-appstorage.md): special, singleton LocalStorage object within the application, which is created by the UI framework at application startup and provides the central storage for application UI state attributes.
- [PersistentStorage](arkts-persiststorage.md): API for persisting application attributes. It is usually used together with AppStorage to persist selected AppStorage attributes to the disk so that their values are the same upon application re-start as they were when the application was closed.
- [Environment](arkts-environment.md): a range of environment parameters regarding the device where the application runs. The environment parameters are synchronized to the AppStorage and can be used together with the AppStorage.
# AppStorage: Application-wide UI State Storage
AppStorage provides the central storage for mutable application UI state attributes. It is bound to the application process and is created by the UI framework at application startup.
Unlike LocalStorage, which is usually used for page-level state sharing, AppStorage enables application-wide UI state sharing. AppStorage is equivalent to the hub of the entire application. [PersistentStorage](arkts-persiststorage.md) and [Environment](arkts-environment.md) data is passed first to AppStorage and then from AppStorage to the UI component.
This topic describes only the AppStorage application scenarios and related decorators: \@StorageProp and \@StorageLink.
## Overview
AppStorage is a singleton LocalStorage object that is created by the UI framework at application startup. Its purpose is to provide the central storage for mutable application UI state attributes. AppStorage retains all those attributes and their values as long as the application remains running. Attributes are accessed using a unique key string value.
UI components synchronize application state attributes with the AppStorage. Implementation of application business logic can access AppStorage as well.
Selected state attributes of AppStorage can be synched with different data sources or data sinks. Those data sources and sinks can be on a local or remote device, and have different capabilities, such as data persistence (see [PersistentStorage](arkts-persiststorage.md)). These data sources and sinks are implemented in the business logic, separate from the UI. Link those AppStorage attributes to [@StorageProp](#storageprop) and [@StorageLink](#storagelink) whose values should be kept until application re-start.
## \@StorageProp
As mentioned above, if you want to establish a binding between AppStorage and a custom component, you need to use the \@StorageProp and \@StorageLink decorators. Use \@StorageProp(key) or \@StorageLink(key) to decorate variables in the component. **key** identifies the attribute in AppStorage.
When a custom component is initialized, the \@StorageProp(key)/\@StorageLink(key) decorated variable is initialized with the value of the attribute with the given key in AppStorage. Local initialization is mandatory. If an attribute with the given key is missing from AppStorage, it will be added with the stated initializing value. (Whether the attribute with the given key exists in AppStorage depends on the application logic.)
By decorating a variable with \@StorageProp(key), a one-way data synchronization is established with the attribute with the given key in AppStorage. A local change can be made, but it will not be synchronized to AppStorage. An update to the attribute with the given key in AppStorage will overwrite local changes.
### Rules of Use
| \@StorageProp Decorator| Description |
| ------------------ | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | One-way: from the attribute in AppStorage to the component variable.<br>The component variable can be changed locally, but an update from AppStorage will overwrite local changes.|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in AppStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize a n \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 1** \@StorageProp initialization rule
![en-us_image_0000001552978157](figures/en-us_image_0000001552978157.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
- When the value change of the \@StorageProp(key) decorated variable is observed, the change is not synchronized to the attribute with the give key value in AppStorage.
- The value change of the \@StorageProp(key) decorated variable only applies to the private member variables of the current component, but not other variables bound to the key.
- When the data decorated by \@StorageProp(key) is a state variable, the change of the data is not synchronized to AppStorage, but the owning custom component is re-rendered.
- When the attribute with the given key in AppStorage is updated, the change is synchronized to all the \@StorageProp(key) decorated data, and the local changes of the data are overwritten.
## \@StorageLink
\@StorageLink(key) creates a two-way data synchronization with the attribute with the given key in AppStorage.
1. If a local change occurs, it is synchronized to AppStorage.
2. Changes in AppStorage are synchronized to all attributes with the given key, including one-way bound variables (\@StorageProp decorated variables and one-way bound variables created through \@Prop), two-way bound variables (\@StorageLink decorated variables and two-way bound variables created through \@Link), and other instances (such as PersistentStorage).
### Rules of Use
| \@StorageLink Decorator| Description |
| ------------------ | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in AppStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | Two-way: from the attribute in AppStorage to the custom component variable and back|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in AppStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden. |
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 2** \@StorageLink initialization rule
![en-us_image_0000001501938718](figures/en-us_image_0000001501938718.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
1. When the value change of the \@StorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the give key value in AppStorage.
2. Once the attribute with the given key in AppStorage is updated, all the data (including \@StorageLink and \@StorageProp decorated variables) bound to the attribute key is changed synchronously.
3. When the data decorated by \@StorageLink(key) is a state variable, the change of the data is synchronized to AppStorage, and the owning custom component is re-rendered.
## Application Scenarios
### Example of Using AppStorage and LocalStorage from Application Logic
Since AppStorage is a singleton, its APIs are all static ones. How these APIs work resembles the non-static APIs of LocalStorage.
```ts
AppStorage.SetOrCreate('PropA', 47);
let storage: LocalStorage = new LocalStorage({ 'PropA': 17 });
let propA: number = AppStorage.Get('PropA') // propA in AppStorage == 47, propA in LocalStorage == 17
var link1: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link1.get() == 47
var link2: SubscribedAbstractProperty<number> = AppStorage.Link('PropA'); // link2.get() == 47
var prop: SubscribedAbstractProperty<number> = AppStorage.Prop('PropA'); // prop.get() = 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get()=1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
storage.get('PropA') // == 17
storage.set('PropA', 101);
storage.get('PropA') // == 101
AppStorage.Get('PropA') // == 49
link1.get() // == 49
link2.get() // == 49
prop.get() // == 49
```
### Example of Using AppStorage and LocalStorage from Inside the UI
\@StorageLink works together with the AppStorage in the same way as \@LocalStorageLink works together with LocalStorage. It creates two-way data synchronization with an attribute in AppStorage.
```ts
AppStorage.SetOrCreate('PropA', 47);
let storage = new LocalStorage({ 'PropA': 48 });
@Entry(storage)
@Component
struct CompA {
@StorageLink('PropA') storLink: number = 1;
@LocalStorageLink('PropA') localStorLink: number = 1;
build() {
Column({ space: 20 }) {
Text(`From AppStorage ${this.storLink}`)
.onClick(() => this.storLink += 1)
Text(`From LocalStorage ${this.localStorLink}`)
.onClick(() => this.localStorLink += 1)
}
}
}
```
## Restrictions
When using AppStorage together with [PersistentStorage](arkts-persiststorage.md) and [Environment](arkts-environment.md), pay attention to the following:
- A call to **PersistentStorage.PersistProp()** after creating the attribute in AppStorage uses the type and value in AppStorage and overwrites any attribute with the same name in PersistentStorage. In light of this, the opposite order of calls is recommended. For an example of incorrect usage, see [Accessing Attribute in AppStorage Before PersistentStorage](arkts-persiststorage.md#accessing-attribute-in-appstorage-before-persistentstorage).
- A call to **Environment.EnvProp()** after creating the attribute in AppStorage will fail. This is because AppStorage already has an attribute with the same name, and the environment variable will not be written into AppStorage. Therefore, you are advised not to use the preset environment variable name in AppStorage.
# Basic Syntax Overview
With a basic understanding of the ArkTS language, let's look into the basic composition of ArkTS through an example. As shown below, when the user clicks the button, the text content changes from **Hello World** to **Hello ArkUI**.
**Figure 1** Example effect drawing
![Video_2023-03-06_152548](figures/Video_2023-03-06_152548.gif)
In this example, the basic composition of ArkTS is as follows.
**Figure 2** Basic composition of ArkTS
![arkts-basic-grammar](figures/arkts-basic-grammar.png)
- Decorator: design pattern used to decorate classes, structures, methods, and variables to assign special meanings to them. In the preceding sample code, \@Entry, \@Component, and \@State are decorators. \@Component indicates a custom component, \@Entry indicates that the custom component is an entry component, and \@State indicates a state variable in the component, whose change will trigger the UI to re-render.
- [UI description](arkts-declarative-ui-description.md): declarative description of the UI structure, such as the code block of the **build()** method.
- [Custom component](arkts-create-custom-components.md): reusable UI unit, which can be combined with other components, such as the struct **Hello** decorated by @Component.
- Built-in component: default basic or container component preset in ArkTS, which can be directly invoked, such as** \<Column>**,** \<Text>**, **\<Divider>**, and **\<Button>** components in the sample code.
- Attribute method: method used to configure component attributes, such as **fontSize()**, **width()**, **height()**, and **color()**. You can configure multiple attributes of a component in method chaining mode.
- Event method: method used to add the logic for a component to respond to an event. In the sample code, **onClick()** following **Button** is an event method. You can configure response logic for multiple events in method chaining mode.
ArkTS extends multiple syntax paradigms to make development a more enjoyable experience.
- [@Builder](arkts-builder.md)/[@BuilderParam](arkts-builderparam.md): special method for encapsulating UI descriptions. It enables UI descriptions to be encapsulated and reused in a fine-grained manner.
- [@Extend](arkts-extend.md)/[@Style](arkts-style.md): decorator that extends built-in components and encapsulates attribute styles to combine built-in components more flexibly.
- [stateStyles](arkts-statestyles.md): polymorphic style, which can be set based on the internal state of the component.
# Basic UI Description
In ArkTS, you define a custom component by using decorators **@Component** and **@Entry** to decorate a data structure declared with the **struct** keyword. A custom component provides a **build** function, where you must write the basic UI description in chain call mode. For details about the UI description, see [UI Description Specifications](#ui-description-specifications).
## Basic Concepts
- struct: a data structure that can be used to implement custom components and cannot have inheritance. The **new** keyword can be omitted when initializing a struct.
- Decorator: a special type of declaration that can be applied to classes, structures, or class attributes to add new functionality to them. Multiple decorators can be applied to the same target element and defined on a single line or multiple lines. It is recommended that the decorators be defined on multiple lines.
```ts
@Entry
@Component
struct MyComponent {
}
```
- **build** function: A custom component must implement the **build** function and must implement no constructor. The **build** function meets the definition of the **Builder** API and is used to define the declarative UI description of components.
```ts
interface Builder {
build: () => void
}
```
- **@Component**: a decorator applied to a struct to equip it with the component-based capability. The **build** method must be implemented for UI creation.
- **@Entry**: a decorator applied to a struct to make it the entry to a page, which is rendered and displayed when the page is loaded.
- **@Preview**: a decorator applied to struct to make it previewable in the DevEco Studio Previewer. The decorated component is created and displayed when the residing page is loaded.
> **NOTE**
>
> In a single source file, you can use up to 10 **@Preview** decorators to decorate custom components. For details, see [Previewing ArkTS Components](https://developer.harmonyos.com/en/docs/documentation/doc-guides/ohos-previewing-app-service-0000001218760596#section146052489820).
- Chain call: a syntax for configuring the attribute methods, event methods, and more of UI components by using the dot notation.
## UI Description Specifications
### Structs Without Parameters
A struct without parameters is a component whose API definition has empty parentheses. No parameter needs to be passed to this type of component, for example, the **Divider** component in the following snippet:
```ts
Column() {
Text('item 1')
Divider()
Text('item 2')
}
```
### Structs with Parameters
A struct with parameters is a component whose API definition expects parameters enclosed in the parentheses. You can use constants to assign values to the parameters.
Sample code:
- Set the mandatory parameter **src** of the **\<Image>** component as follows:
```ts
Image('https://xyz/test.jpg')
```
- Set the optional parameter **content** of the **\<Text>** component as follows:
```ts
Text('test')
```
You can use variables or expressions to assign values to parameters. The result type returned by an expression must meet the parameter type requirements. For details about the variables, see [State Management with Page-level Variables](arkts-state-mgmt-page-level.md) and [State Management with Application-level Variables](arkts-state-mgmt-application-level.md). For example, set a variable or expression to construct the **\<Image>** and **\<Text>** components:
```ts
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
```
### Attribute Configuration
Component attributes are configured using an attribute method, which follows the corresponding component and is bound to the component using the "**.**" operator.
- Example of configuring the font size attribute of the **\<Text>** component:
```ts
Text('test')
.fontSize(12)
```
- Example of configuring multiple attributes at the same time by using the "**.**" operator to implement chain call:
```ts
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
```
- Example of passing variables or expressions in addition to constants:
```ts
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
```
- For attributes of built-in components, ArkUI also provides some predefined [enumeration types](../reference/arkui-ts/ts-appendix-enums.md), which you can pass as parameters to methods if they meet the parameter type requirements. For example, you can configure the font color and weight attributes of the **\<Text>** component as follows:
```ts
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
```
### Event Configuration
Events supported by components are configured using event methods, which each follow the corresponding component and are bound to the component using the "**.**" operator.
- Example of using a lambda expression to configure the event of a component:
```ts
Button('add counter')
.onClick(() => {
this.counter += 2;
})
```
- Example of using an anonymous function expression to configure the event of a component (**bind** must be used to ensure that the contained components are referenced by **this** in the function body):
```ts
Button('add counter')
.onClick(function () {
this.counter += 2;
}.bind(this))
```
- Example of using a component's member function to configure the event of the component:
```ts
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
```
### Child Component Configuration
For a component that supports child components, for example, a container component, add the UI descriptions of the child components inside parentheses. The **\<Column>**, **\<Row>**, **\<Stack>**, **\<Grid>**, and **\<List>** components are all container components.
- Simple example of the **\<Column>** component:
```ts
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
```
- Example of nesting multiple child components in the **\<Column>** component:
```ts
Column() {
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
Divider()
Row() {
Image('test2.jpg')
.width(100)
.height(100)
Button('click +2')
.onClick(() => {
console.info('+2 clicked!');
})
}
Divider()
Row() {
Image('test3.jpg')
.width(100)
.height(100)
Button('click +3')
.onClick(() => {
console.info('+3 clicked!');
})
}
}
```
# \@Builder: Custom Builder Function
After a custom component is created, its internal UI structure is fixed and allows only data passing with its caller. ArkUI also provides a more lightweight mechanism for reusing UI elements: \@Builder. An \@Builder decorated function is a special function that serves similar purpose as the build function. The \@Builder function body follows the same syntax rules as the **build** function. You can abstract reusable UI elements into a method and call the method in **build**.
To simplify language, here we refer to an \@Builder decorated function also as a custom builder function.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Custom Builder Function
Syntax:
```ts
@Builder myBuilderFunction({ ... })
```
Usage:
```ts
this.myBuilderFunction({ ... })
```
- Defining one or more custom builder (\@Builder decorated) functions inside a custom component is allowed. Such a custom builder function can be considered as a private, special type of member functions of that component.
- The custom builder function can be called from the owning component's** build** or another custom builder (within that custom component) function only.
- Inside the custom builder function body, **this** refers to the owning component. Component state variables are accessible from within the custom builder function implementation. Using the custom components' state variables is recommended over parameter passing.
### Global Custom Builder Function
Syntax:
```ts
@Builder function MyGlobalBuilderFunction({ ... })
```
Usage:
```ts
MyGlobalBuilderFunction()
```
- A global custom builder function is accessible from the entire application. **this** and the **bind** method are not allowed.
- Use of a global custom builder function is recommended if no own state is required.
## Parameter Passing Rules
There are two types of parameter passing for custom builder functions: [by-value parameter passing](#by-value-parameter-passing) and [by-reference parameter passing](#by-reference-parameter-passing). Both of them must comply with the following rules:
- The parameter type must be the same as the declared parameter type. The **undefined** or **null** constants as well as expressions evaluating to these values are not allowed.
- All parameters are immutable inside the custom builder function. If mutability and synchronization of the mutation is required, the custom builder should be replaced by a custom component with a [@Link](arkts-link.md) decorated variable.
- The \@Builder function body follows the same [syntax rules](arkts-create-custom-components.md#build-function) as the **build** function.
### By-Reference Parameter Passing
In by-reference parameter passing, the passed parameters can be state variables, and the change of these state variables causes the UI re-rendering in the \@Builder method. ArkUI provides $$ as a paradigm for by-reference parameter passing.
```ts
ABuilder( $$ : { paramA1: string, paramB1 : string } );
```
```ts
@Builder function ABuilder($$: { paramA1: string }) {
Row() {
Text(`UseStateVarByReference: ${$$.paramA1} `)
}
}
@Entry
@Component
struct Parent {
@State label: string = 'Hello';
build() {
Column() {
// Pass the this.label reference to the ABuilder component when the ABuilder component is called in the Parent component.
ABuilder({ paramA1: this.label })
Button('Click me').onClick(() => {
// After Click me is clicked, the UI text changes from Hello to ArkUI.
this.label = 'ArkUI';
})
}
}
}
```
### By-Value Parameter Passing
By default, parameters in the \@Builder decorated functions are passed by value. When the passed parameter is a state variable, the change of the state variable does not cause the UI re-rendering in the \@Builder method. Therefore, when using state variables, you are advised to use [by-reference parameter passing](#by-reference-parameter-passing).
```ts
@Builder function ABuilder(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
}
@Entry
@Component
struct Parent {
label: string = 'Hello';
build() {
Column() {
ABuilder(this.label)
}
}
}
```
# \@BuilderParam: @Builder Function Reference
In certain circumstances, you may need to add a specific function, such as a click-to-jump action, to a custom component. However, embedding an event method directly inside of the component will add the function to all places where the component is imported. This is where the \@BuilderParam decorator comes into the picture. \@BuilderParam is used to decorate a custom component member variable of type reference to an \@Builder method. When initializing a custom component, you can assign a value to the variable, thereby adding the specific function to the custom component. This decorator can be used to declare an element of any UI description, similar to a slot placeholder.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Initializing \@BuilderParam Decorated Method
An \@BuildParam decorated method can be initialized only by an \@Builder function reference.
- Local initialization with the owning component's custom \@Builder function or a global \@Builder function reference
```ts
@Builder function GlobalBuilder0() {}
@Component
struct Child {
@Builder doNothingBuilder() {};
@BuilderParam aBuilder0: () => void = this.doNothingBuilder;
@BuilderParam aBuilder1: () => void = GlobalBuilder0;
build(){}
}
```
- Initialization from the parent component
```ts
@Component
struct Child {
@BuilderParam aBuilder0: () => void;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
Child({ aBuilder0: this.componentBuilder })
}
}
}
```
- **this** in the function body points to the correct object.
In the following example, when the **Parent** component calls **this.componentBuilder()**, **this.label** points to the owning component, that is, **Parent**. With **\@BuilderParam aBuilder0** passed to the **Child** component from **\@Builder componentBuilder()**, when the **Child** component calls **this.aBuilder0()**, **this.label** points to the label of the **Child** component, that is, **Child**.
> **NOTE**
>
> Exercise caution when using **bind** to change the context of function invoking, which may cause **this** to point to an incorrect object.
```ts
@Component
struct Child {
label: string = `Child`
@BuilderParam aBuilder0: () => void;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
label: string = `Parent`
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder })
}
}
}
```
## Application Scenarios
### Component Initialization Through Parameters
An \@BuilderParam decorated method can be a method with or without parameters. Whether it contains parameters should match that of the assigned \@Builder method. The type of the \@BuilderParam decorated method must also match that of the assigned \@Builder method.
```ts
@Builder function GlobalBuilder1($$ : {label: string }) {
Text($$.label)
.width(400)
.height(50)
.backgroundColor(Color.Blue)
}
@Component
struct Child {
label: string = 'Child'
// Without parameters. The pointed componentBuilder is also without parameters.
@BuilderParam aBuilder0: () => void;
// With parameters. The pointed GlobalBuilder1 is also with parameters.
@BuilderParam aBuilder1: ($$ : { label : string}) => void;
build() {
Column() {
this.aBuilder0()
this.aBuilder1({label: 'global Builder label' } )
}
}
}
@Entry
@Component
struct Parent {
label: string = 'Parent'
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 })
}
}
}
```
### Example of Component Initialization Through Trailing Closure
In a custom component, the \@BuilderParam decorated attribute can be initialized using a trailing closure. During initialization, the component name is followed by a pair of braces ({}) to form a trailing closure.
> **NOTE**
> In this scenario, the custom component has one and only one \@BuilderParam decorated attribute.
You can pass the content in the trailing closure to \@BuilderParam as an \@Builder decorated method. Example:
```ts
// xxx.ets
@Component
struct CustomContainer {
@Prop header: string;
@BuilderParam closer: () => void
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// Create the CustomContainer component. During initialization, append a pair of braces ({}) to the component name to form a trailing closure.
// Used as the parameter passed to CustomContainer @BuilderParam closer: () => void.
CustomContainer({ header: this.text }) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader';
})
}
}
}
}
```
# Creating a Custom Component
In ArkUI, components are what's displayed on the UI. They can be classified as built-in components – those directly provided by 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.
The custom component has the following features:
- Combinable: allows you to combine built-in components and other components, as well as their attributes and methods.
- Reusable: can be reused by other components and used as different instances in different parent components or containers.
- Data-driven update: holds some state and triggers UI re-rendering with the change of state variables.
The following example shows the basic usage of a custom component.
```ts
@Component
struct HelloComponent {
@State message: string = 'Hello, World!';
build() {
// The HelloComponent custom component combines the <Row> and <Text> built-in components.
Row() {
Text(this.message)
.onClick(() => {
// 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!".
this.message = 'Hello, ArkUI!';
})
}
}
}
```
Multiple **HelloComponent** instances can be created in the **build()** function of other custom components. In this way, **HelloComponent** is reused by those custom components.
```ts
@Entry
@Component
struct ParentComponent {
build() {
Column() {
Text('ArkUI message')
HelloComponent({ message: 'Hello, World!' });
Divider()
HelloComponent ({ message: 'Hello!' });
}
}
}
```
To fully understand the preceding example, a knowledge of the following concepts is essential:
- [Basic Structure of a Custom Component](#basic-structure-of-a-custom-component)
- [Member Functions/Variables](#member-functionsvariables)
- [Rules of for Custom Component Parameters](#rules-of-for-custom-component-parameters)
- [build Function](#build-function)
- [Universal Style of a Custom Component](#universal-style-of-a-custom-component)
- [Custom Attribute Methods](#custom-attribute-methods)
## Basic Structure of a Custom Component
- 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.
> **NOTE**
>
> The name or its class or function name of a custom component must be different from that of any built-in components.
- \@Component: The \@Component decorator can decorate only the data structures declared by the **struct** keyword. After being decorated by \@Component, a struct has the componentization capability. It must implement the **build** function to describe the UI. One struct can be decorated by only one \@Component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
```ts
@Component
struct MyComponent {
}
```
- build(): The **build()** function is used to define the declarative UI description of a custom component. Every custom component must define a **build()** function.
```ts
@Component
struct MyComponent {
build() {
}
}
```
- \@Entry: A custom component decorated with \@Entry is used as the default entry component of the page. At most one component can be decorated with \@Entry in a single source file. The \@Entry decorator accepts an optional parameter of type [LocalStorage](arkts-localstorage.md).
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
```ts
@Entry
@Component
struct MyComponent {
}
```
## Member Functions/Variables
In addition to the mandatory** build()** function, a custom component may implement other member functions with the following restrictions:
- Static functions are not supported.
- Access to the member functions is always private. Defining **private** access is optional. Defining access other than **private** is a syntax error.
A custom component can also implement member variables with the following restrictions:
- Static member variables are not supported.
- Access to the member variables is always private.The access rules of member variables are the same as those of member functions.
- 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).
## Rules of for Custom Component Parameters
As can be learnt from preceding examples, a custom component can be created from a **build** or [@Builder](arkts-builder.md) function, and during the creation, parameters can be supplied to the component.
```ts
@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue;
build() {
}
}
@Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink;
build() {
Column() {
// Create an instance of MyComponent and initialize its countDownFrom variable with the value 10 and its color variable with the value this.someColor.
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
```
## build Function
All languages declared in the **build** function are called UI description languages. The UI description languages must comply with the following rules:
- 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.
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.
```ts
@Entry
@Component
struct MyComponent {
build() {
// Exactly one root component is required, and it must be a container component.
Row() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
build() {
// Exactly one root component is required, and it is not necessarily a container component.
Image('test.jpg')
}
}
```
- Local variable declaration is not allowed. The following example is invalid:
```ts
build() {
// Invalid: Local variable declaration is not allowed.
let a: number = 1;
}
```
- **console.info** cannot be directly used in the UI description, but can be used in methods or functions. The following is an example:
```ts
build() {
// Invalid: Use of console.info is not allowed.
console.info('print debug log');
}
```
- Creation of a local scope is not allowed. The following example is invalid:
```ts
build() {
// Invalid: Creation of local scope is not allowed.
{
...
}
}
```
- Calling a function other than the \@Builder decorated is not allowed. The parameters of built-in components can be the return values of TS methods.
```ts
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// Invalid: No function calls except @Builder functions.
this.doSomeCalculations();
// Valid: The function can be called.
this.doSomeRender();
// Valid: The parameter can be the return value of a TS method.
Text(this.calcTextValue())
}
}
}
```
- The **switch** syntax is not allowed. Use **if** instead. The following example is invalid:
```ts
build() {
Column() {
// Invalid: The switch syntax is not allowed.
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
}
}
```
- Expressions are not allowed. The following example is invalid:
```ts
build() {
Column() {
// Invalid: Expressions are not allowed.
(this.aVar > 10) ? Text('...') : Image('...')
}
}
```
## Universal Style of a Custom Component
The universal style of a custom component is configured by invoking chainable attribute methods.
```ts
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
```
> **NOTE**
>
> 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.
## Custom Attribute Methods
Custom components do not support custom attribute methods. You can use the Controller capability to implement custom APIs.
```ts
// Custom controller
export class MyComponentController {
item: MyComponent = null;
setItem(item: MyComponent) {
this.item = item;
}
changeText(value: string) {
this.item.value = value;
}
}
// Custom component
@Component
export default struct MyComponent {
public controller: MyComponentController = null;
@State value: string = 'Hello World';
build() {
Column() {
Text(this.value)
.fontSize(50)
}
}
aboutToAppear() {
if (this.controller)
this.controller.setItem (this); // Link to the controller.
}
}
// Processing logic
@Entry
@Component
struct StyleExample {
controller = new MyComponentController();
build() {
Column() {
MyComponent({ controller: this.controller })
}
.onClick(() => {
this.controller.changeText('Text');
})
}
}
```
In the preceding example:
1. The **aboutToAppear** method of the **MyComponent** child component passes the current **this** pointer to the **item** member variable of **MyComponentController**.
2. The **StyleExample** parent component holds a **Controller** instance and with which calls the **changeText** API of **Controller**. That is, the value of the state variable **value** of **MyComponent** is changed through the **this** pointer of the **MyComponent** child component held by the controller.
Through the encapsulation of the controller, **MyComponent** exposes the **changeText** API. All instances that hold the controller can call the **changeText** API to change the value of the **MyComponent** state variable **value**.
# Declarative UI Description
ArkTS declaratively combines and extends components to describe the UI of an application. It also provides basic methods for configuring attributes, events, and child components to help you implement application interaction logic.
## Creating a Component
Depending on the builder, you can create components with or without mandatory parameters.
> **NOTE**
>
> The **new** operator is not required when you create a component.
### Without Mandatory Parameters
A struct without mandatory parameters is a component whose API definition has empty parentheses. No parameter needs to be passed to this type of component, for example, the **Divider** component in the following snippet:
```ts
Column() {
Text('item 1')
Divider()
Text('item 2')
}
```
### With Mandatory Parameters
A struct with mandatory parameters is a component whose API definition expects parameters enclosed in the parentheses.
- Set the mandatory parameter **src** of the **\<Image>** component as follows:
```ts
Image('https://xyz/test.jpg')
```
- Set the optional parameter **content** of the **\<Text>** component.
```ts
// Parameter of the string type
Text('test')
// Add application resources in $r format, which can be used in multi-language scenarios.
Text($r('app.string.title_value'))
// No mandatory parameters
Text()
```
- You can also use variables or expressions to assign values to parameters. The result type returned by an expression must meet the parameter type requirements.
For example, to set a variable or expression to construct the **\<Image>** and **\<Text>** components:
```ts
Image(this.imagePath)
Image('https://' + this.imageUrl)
Text(`count: ${this.count}`)
```
## Configuring Attributes
Use chainable attribute methods to configure the style and other attributes of built-in components. It is recommended that a separate line be used for each attribute method.
- Example of configuring the **fontSize** attribute for the **\<Text>** component:
```ts
Text('test')
.fontSize(12)
```
- Example of configuring multiple attributes for the **\<Image>** component:
```ts
Image('test.jpg')
.alt('error.jpg')
.width(100)
.height(100)
```
- Attribute methods accept expressions and variables as well constant parameters.
```ts
Text('hello')
.fontSize(this.size)
Image('test.jpg')
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset + 100)
```
- For built-in components, ArkUI also predefines some enumeration types. These enumeration types can be passed as parameters, as long as they meet the parameter type requirements.
Example of configuring the font color and style of the **\<Text>** component:
```ts
Text('hello')
.fontSize(20)
.fontColor(Color.Red)
.fontWeight(FontWeight.Bold)
```
## Handling Events
Use chainable event methods to configure events supported by built-in components. It is recommended that a separate line be used for each event method.
- Example of using a lambda expression to configure the event of a component:
```ts
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
```
- Example of using an anonymous function expression to configure the event of a component (**bind** must be used to ensure that the current components are referenced by **this **in the function body):
```ts
Button('add counter')
.onClick(function(){
this.counter += 2;
}.bind(this))
```
- Example of using a component's member function to configure the event of the component:
```ts
myClickHandler(): void {
this.counter += 2;
}
...
Button('add counter')
.onClick(this.myClickHandler.bind(this))
```
## Configuring Child Components
For a component that supports child components, for example, a container component, add the UI descriptions of the child components inside parentheses. The **\<Column>**, **\<Row>**, **\<Stack>**, **\<Grid>**, and **\<List>** components are all container components.
- Simple example of configuring child components for the **\<Column>** component:
```ts
Column() {
Text('Hello')
.fontSize(100)
Divider()
Text(this.myText)
.fontSize(100)
.fontColor(Color.Red)
}
```
- Example of nesting multiple child components in the **\<Column>** component:.
```ts
Column() {
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
}
```
# Dynamic UI Element Building
After you've created a custom component (as described in [Basic UI Description](arkts-basic-ui-description.md)), you can customize the internal UI structure for the component, by drawing on the capability of dynamic UI element building.
## @Builder
The **@Builder** decorator is used to decorate a function for quickly generating multiple layouts in a custom component. This function can be declared outside the **build** function and used in the **build** function or other **@Builder** decorated functions. The following example shows how to use **@Builder**.
```ts
// xxx.ets
@Component
struct CompB {
@State CompValue: string = ''
aboutToAppear() {
console.info('CompB aboutToAppear.')
}
aboutToDisappear() {
console.info('CompB aboutToDisappear.')
}
build() {
Column() {
Button(this.CompValue)
.margin(5)
}
}
}
@Entry
@Component
struct CompA {
size1: number = 100
@State CompValue1: string = "Hello,CompValue1"
@State CompValue2: string = "Hello,CompValue2"
@State CompValue3: string = "Hello,CompValue3"
// Use the custom component CompB in the @Builder decorated function CompC.
@Builder CompC(value: string) {
CompB({ CompValue: value })
}
@Builder SquareText(label: string) {
Text(label)
.fontSize(18)
.width(1 * this.size1)
.height(1 * this.size1)
}
// Use the @Builder decorated function SquareText in the @Builder decorated function RowOfSquareTexts.
@Builder RowOfSquareTexts(label1: string, label2: string) {
Row() {
this.SquareText(label1)
this.SquareText(label2)
}
.width(2 * this.size1)
.height(1 * this.size1)
}
build() {
Column() {
Row() {
this.SquareText("A")
this.SquareText("B")
}
.width(2 * this.size1)
.height(1 * this.size1)
this.RowOfSquareTexts("C", "D")
Column() {
// Use the @Builder decorated custom components three times.
this.CompC(this.CompValue1)
this.CompC(this.CompValue2)
this.CompC(this.CompValue3)
}
.width(2 * this.size1)
.height(2 * this.size1)
}
.width(2 * this.size1)
.height(2 * this.size1)
}
}
```
![builder](figures/builder.PNG)
## @BuilderParam<sup>8+</sup>
The **@BuilderParam** decorator is used to decorate the function type attributes (for example, **@BuilderParam noParam: () => void**) in a custom component. When the custom component is initialized, the attributes decorated by **@BuilderParam** must be assigned values.
### Background
In certain circumstances, you may need to add a specific function, such as a click-to-jump action, to a custom component. However, embedding an event method directly inside of the component will add the function to all places where the component is imported. This is where the **@BuilderParam** decorator comes into the picture. When initializing a custom component, you can assign a **@Builder** decorated method to the **@BuilderParam** decorated attribute, thereby adding the specific function to the custom component.
### Component Initialization Through Parameters
When initializing a custom component through parameters, assign a **@Builder** decorated method to the **@BuilderParam** decorated attribute — **content**, and call the value of **content** in the custom component. If no parameter is passed when assigning a value to the **@BuilderParam** decorated attribute (for example, **noParam: this.specificNoParam**), define the type of the attribute as a function without a return value (for example, **@BuilderParam noParam: () => void**). If any parameter is passed when assigning a value to the **@BuilderParam** decorated attribute (for example, **withParam: this.SpecificWithParam('WithParamA')**), define the type of the attribute as **any** (for example, **@BuilderParam withParam: any**).
```ts
// xxx.ets
@Component
struct CustomContainer {
header: string = ''
@BuilderParam noParam: () => void
@BuilderParam withParam: any
footer: string = ''
build() {
Column() {
Text(this.header)
.fontSize(30)
this.noParam()
this.withParam()
Text(this.footer)
.fontSize(30)
}
}
}
@Entry
@Component
struct CustomContainerUser {
@Builder specificNoParam() {
Column() {
Text('noParam').fontSize(30)
}
}
@Builder SpecificWithParam(label: string) {
Column() {
Text(label).fontSize(30)
}
}
build() {
Column() {
CustomContainer({
header: 'HeaderA',
noParam: this.specificNoParam,
withParam: this.SpecificWithParam('WithParamA'),
footer: 'FooterA'
})
Divider()
.strokeWidth(3)
.margin(10)
CustomContainer({
header: 'HeaderB',
noParam: this.specificNoParam,
withParam: this.SpecificWithParam('WithParamB'),
footer: 'FooterB'
})
}
}
}
```
![builder1](figures/builder1.PNG)
### Component Initialization Through Trailing Closure
In a custom component, the **@BuilderParam** decorated attribute can be initialized using a trailing closure. During initialization, the component name is followed by a pair of braces ({}) to form a trailing closure (**CustomContainer(){}**). You can consider a trailing closure as a container and add content to it. For example, you can add a component (**{Column(){...}**) to the closure. The syntax of the closure is the same as that of **build**. In this scenario, the custom component has one and only one **@BuilderParam** decorated attribute.
Example: Add a **\<Column>** component and a click event to the closure, and call the **specificParam** method decorated by **@Builder** in the new **\<Column>** component. After the **\<Column>** component is clicked, the value of the **CustomContainer** component's **header** attribute will change from **header** to **changeHeader**. When the component is initialized, the content of the trailing closure will be assigned to the **closer** attribute decorated by **@BuilderParam**.
```ts
// xxx.ets
@Component
struct CustomContainer {
header: string = ''
@BuilderParam closer: () => void
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header'
build() {
Column() {
CustomContainer({
header: this.text,
}) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader'
})
}
}
}
}
```
![builder2](figures/builder2.gif)
## @Styles
The **@Styles** decorator helps avoid repeated style setting, by extracting multiple style settings into one method. When declaring a component, you can invoke this method and use the **@Styles** decorator to quickly define and reuse the custom styles of a component. **@Styles** supports only universal attributes.
**@Styles** can be defined inside or outside a component declaration. When it is defined outside a component declaration, the component name must be preceded by the keyword **function**.
```ts
// xxx.ets
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@Styles componentFancy() {
.width(100)
.height(200)
.backgroundColor(Color.Yellow)
}
build() {
Column({ space: 10 }) {
Text('FancyA')
.globalFancy()
.fontSize(30)
Text('FancyB')
.globalFancy()
.fontSize(20)
Text('FancyC')
.componentFancy()
.fontSize(30)
Text('FancyD')
.componentFancy()
.fontSize(20)
}
}
}
```
![styles](figures/styles.PNG)
**@Styles** can also be used inside the **[StateStyles](../reference/arkui-ts/ts-universal-attributes-polymorphic-style.md)** attribute declaration of a component, to assign state-specific attributes to the component.
In **StateStyles**, **@Styles** decorated methods defined outside the component can be directly called, while those defined inside can be called only with the keyword **this**.
```ts
// xxx.ets
@Styles function globalFancy () {
.width(120)
.height(120)
.backgroundColor(Color.Green)
}
@Entry
@Component
struct FancyUse {
@Styles componentFancy() {
.width(80)
.height(80)
.backgroundColor(Color.Red)
}
build() {
Row({ space: 10 }) {
Button('Fancy')
.stateStyles({
normal: {
.width(100)
.height(100)
.backgroundColor(Color.Blue)
},
disabled: this.componentFancy,
pressed: globalFancy
})
}
}
}
```
![styles1](figures/styles1.gif)
## @Extend
The **@Extend** decorator adds new attribute methods to built-in components, such as **\<Text>**, **\<Column>**, and **\<Button>**. In this way, the built-in components are extended instantly.
```ts
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
.fontStyle(FontStyle.Italic)
.fontWeight(600)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text("Fancy")
.fancy(16)
Text("Fancy")
.fancy(24)
Text("Fancy")
.fancy(32)
}
}
}
```
> **NOTE**
>
> - The **@Extend** decorator cannot be defined inside the struct of a custom component.
> - The **@Extend** decorator supports only attribute methods.
![extend](figures/extend.PNG)
## @CustomDialog
The **@CustomDialog** decorator is used to decorate custom dialog boxes, enabling their content and styles to be dynamically set.
```ts
// xxx.ets
@CustomDialog
struct DialogExample {
controller: CustomDialogController
action: () => void
build() {
Row() {
Button('Close CustomDialog')
.onClick(() => {
this.controller.close()
this.action()
})
}.padding(20)
}
}
@Entry
@Component
struct CustomDialogUser {
dialogController: CustomDialogController = new CustomDialogController({
builder: DialogExample({ action: this.onAccept }),
cancel: this.existApp,
autoCancel: true
});
onAccept() {
console.info('onAccept');
}
existApp() {
console.info('Cancel dialog!');
}
build() {
Column() {
Button('Click to open Dialog')
.onClick(() => {
this.dialogController.open()
})
}
}
}
```
![customdialog](figures/customDialog.gif)
# Environment: Device Environment Query
You may want your application to behave differently based on the device environment where the application is running, for example, switching to dark mode or a specific language. In this case, you need Environment for device environment query.
Environment is a singleton object created by the ArkUI framework at application start. It provides a range of application state attributes to AppStorage that describe the device environment in which the application is running. Environment and its attributes are immutable. All property values are of simple type only.
## Application Scenarios
### Accessing Environment Parameters from UI
- Use **Environment.EnvProp** to save the environment variables of the device to AppStorage.
```ts
// Save the language code of the device to AppStorage. The default value is en.
// Whenever its value changes in the device environment, it will update its value in AppStorage.
Environment.EnvProp('languageCode', 'en');
```
- To keep a component variable updated with changes in the device environment, this variable should be decorated with \@StorageProp.
```ts
@StorageProp('languageCode') lang : string = 'en';
```
The chain of updates is as follows: Environment > AppStorage > Component.
> **NOTE**
>
> An \@StorageProp decorated variable can be locally modified, but the change will not be updated to AppStorage. This is because the environment variable parameters are read-only to the application.
```ts
// Save the device language code to AppStorage.
Environment.EnvProp('languageCode', 'en');
let enable = AppStorage.Get('languageCode');
@Entry
@Component
struct Index {
@StorageProp('languageCode') languageCode: string = 'en';
build() {
Row() {
Column() {
// Output the current device language code.
Text(this.languageCode)
}
}
}
}
```
### Using Environment in Application Logic
```ts
// Use Environment.EnvProp to save the device language code to AppStorage.
Environment.EnvProp('languageCode', 'en');
// Obtain the one-way bound languageCode variable from AppStorage.
const lang: SubscribedAbstractProperty<string> = AppStorage.Prop('languageCode');
if (lang.get() === 'en') {
console.info('Hi');
} else {
console.info('Hello!');
}
```
# \@Extend: Extension of Built-in Components
Apart from\@Styles used to extend styles, AkrUI also provides \@Extend, which allows you to add a new attribute feature to a built-in component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Rules of Use
### Syntax
```ts
@Extend(UIComponentName) function functionName { ... }
```
### Rules of Use
- Unlike \@Styles, \@Extend can be defined only globally, that is, outside a component declaration.
- Unlike \@Styles, \@Extend can encapsulate private attributes and events of specified components and predefine \@Extend decorated methods of the same component.
```ts
// @Extend(Text) supports the private attribute fontColor of the <Text> component.
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText can call the predefined fancy method.
@Extend(Text) function superFancyText(size:number) {
.fontSize(size)
.fancy()
}
```
- Unlike \@Styles, \@Extend decorated methods support parameters. You can pass parameters when calling such methods. Regular TypeScript provisions for method parameters apply.
```ts
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(16)
Text('Fancy')
.fancy(24)
}
}
}
```
- A function can be passed as a parameter in an \@Extend decorated method to be used as the handler of the event.
```ts
@Extend(Text) function makeMeClick(onClick: () => void) {
.backgroundColor(Color.Blue)
.onClick(onClick)
}
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World';
onClickHandler() {
this.label = 'Hello ArkUI';
}
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.makeMeClick(this.onClickHandler.bind(this))
}
}
}
```
- A [state variable](arkts-state-management-overview.md) can be passed as a parameter in an \@Extend decorated method. When the state variable changes, the UI is updated and re-rendered.
```ts
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
@State fontSizeValue: number = 20
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(this.fontSizeValue)
.onClick(() => {
this.fontSizeValue = 30
})
}
}
}
```
## Application Scenarios
The following example declares three **\<Text>** components. The **fontStyle**, **fontWeight**, and **backgroundColor** styles are set for each **\<Text>** component.
```ts
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(100)
.backgroundColor(Color.Blue)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(200)
.backgroundColor(Color.Pink)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(300)
.backgroundColor(Color.Orange)
}.margin('20%')
}
}
```
\@Extend combines and reuses styles. The following is an example:
```ts
@Extend(Text) function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
```
With the use of \@Extend, the code readability is enhanced.
```ts
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(200, Color.Orange)
}.margin('20%')
}
}
```
# Getting Started with ArkTS
As its name implies, ArkTS is a superset of TypeScript. It is the preferred, primary programming language for application development in OpenHarmony.
- ArkTS offers all the features of TS.
ArkTS is the preferred main programming language for application development in OpenHarmony. ArkTS is a superset of [TypeScript](https://www.typescriptlang.org/) (TS for short). It contains all TS features and added features. Before getting started with ArkTS, it would be helpful if you have experience with TS development.
- ArkTS extends TS mainly by adding [declarative UI](arkts-basic-ui-description.md) capabilities, which allow you to develop high-performance applications in a more natural and intuitive manner.
The declarative UI capabilities offered by ArkTS include the following:
The added features offered by ArkTS include the following:
- [Basic UI description](arkts-basic-ui-description.md): A wide variety of decorators, custom components, and UI description mechanisms work with the built-in components, event methods, and attribute methods in ArkUI, jointly underpinning UI development.
- [State management](arkts-state-mgmt-page-level.md): In the multi-dimensional state management mechanism for ArkUI, UI-related data can be used not only within a component, but also be transferred between different component levels (for example, between parent and child components, between grandparent and grandchild components, or globally) in a device or even across devices. In addition, data transfer can be classified as one-way (read-only) or two-way (mutable). You can use these capabilities at your disposal to implement linkage between data and the UI.
- [Dynamic UI element building](arkts-dynamic-ui-elememt-building.md): In ArkTS, you can dynamically build UI elements, customizing the internal UI structure of components or extending the native components with custom component styles.
- [Rendering control](arkts-rendering-control.md): ArkTS provides conditional rendering and loop rendering. Conditional rendering can render state-specific content based on the application status. Loop rendering iteratively obtains data from the data source and creates the corresponding component during each iteration.
- [Restrictions and extensions](arkts-restrictions-and-extensions.md): ArkTS provides extensions, such as two-way binding. However, it is not without its restrictions.
- ArkTS will continue to evolve to accommodate changing application development and running requirements, and gradually adds more features, such as parallelism and concurrency enhancement, typed system enhancement, and distributed development paradigm.
- [Basic syntax](arkts-basic-syntax-overview.md): ArkTS defines declarative UI description, custom components, and dynamic extension of UI elements. All these, together with built-in components, event methods, and attribute methods in ArkUI, jointly underpin UI development.
Below is sample code to illustrate the building blocks of ArkTS. It implements a simple UI with two text segments, one divider, and one button. When the user clicks the button, the text content changes from "Hello World" to "Hello ArkUI".
- [State management](arkts-state-management-overview.md): In the multi-dimensional state management mechanism for ArkUI, UI-related data can be used not only within a component, but also be transferred between different component levels (for example, between parent and child components, between grandparent and grandchild components, or globally) in a device or even across devices. In addition, data transfer can be classified as one-way (read-only) or two-way (mutable). You can use these capabilities at your disposal to implement linkage between data and the UI.
![arkts-get-started](figures/arkts-get-started.png)
- [Rendering control](arkts-rendering-control-overview.md): ArkTS provides rendering control. Conditional rendering can render state-specific content based on the application status. **ForEach** iteratively obtains data from the data source and creates the corresponding component during each iteration. **LazyForEach** iterates over provided data sources and creates corresponding components during each iteration.
As shown above, ArkTS has the following building blocks:
- Decorators: used to decorate classes, structures, methods, and variables for custom definitions. In the preceding sample code, **@Entry**, **@Component**, and **@State** are decorators. Specifically, **@Component** indicates a custom component, **@Entry** indicates an entry component, and **@State** indicates a state variable in the component, whose change will trigger re-rendering of the UI.
- Custom components: reusable UI units that can be used in flexible combinations. In the preceding sample code, the structure **Hello** decorated by **@Component** is a custom component.
- UI description: declarative description of the UI structure, such as the code block of the **build()** method.
- Built-in components: basic, container, media, drawing, and canvas components preset in ArkTS. You can directly invoke such components as **\<Column>**, **\<Text>**, **\<Divider>**, and **\<Button>** components in the sample code.
- Attribute methods: methods used to configure component attributes, such as **fontSize()**, **width()**, **height()**, and **color()**. You can configure multiple attributes of a component in method chaining mode.
- Event methods: methods used to add the logic for a component to respond to an event. In the sample code, **onClick()** following **Button** is an event method. You can configure response logic for multiple events in method chaining mode.
ArkTS will continue to evolve to accommodate changing application development and running requirements, and gradually adds more features, such as parallelism and concurrency enhancement, typed system enhancement, and distributed development paradigm.
# \@Link: Two-Way Synchronization Between Parent and Child Components
An \@Link decorated variable can create two-way synchronization with a variable of its parent component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Overview
An \@Link decorated variable in a child component shares the same value with a variable in its parent component.
## Rules of Use
| \@Link Decorator| Description |
| ----------- | ---------------------------------------- |
| Decorator parameters | None. |
| Synchronization type | Two-way:<br>from an \@State, \@StorageLink, or \@Link decorated variable in the parent component to this variable; and the other way around.|
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).<br>The type must be specified and must be the same as that of the counterpart variable of the parent component.<br>**any** is not supported. A combination of simple and complex types is not supported. The **undefined** and **null** values are not allowed.<br>**NOTE**<br>The Length, ResourceStr, and ResourceColor types are a combination of simple and complex types and therefore not supported.|
| Initial value for the decorated variable | Forbidden. |
## Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Mandatory. A two-way synchronization relationship can be established with the @State, @StorageLink, or \@Link decorated variable in the parent component. An \@Link decorated variable can be initialized from an \@State, \@Link, \@Prop, \@Provide, \@Consume, \@ObjectLink, \@StorageLink, \@StorageProp, \@LocalStorageLink, or \@LocalStorageProp decorated variable in the parent component.<br>Since API version 9, the syntax is **Comp({&nbsp;aLink:&nbsp;this.aState&nbsp;})** for initializing an \@Link decorated variable in the child component from an @State decorated variable in its parent component. The **Comp({aLink:&nbsp;$aState})** syntax is also supported|
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | Private, accessible only within the component. |
**Figure 1** Initialization rule
![en-us_image_0000001502092556](figures/en-us_image_0000001502092556.png)
## Observed Changes and Behavior
### Observed Changes
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types).
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types).
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. For details, see [Array Type \@Link](#array-type-link).
### Framework Behavior
An \@Link decorated variable shares the lifecycle of its owning component.
To understand the value initialization and update mechanism of the \@Link decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the \@Link decorated variable (in this example, the \@State decorated variable in the parent component is used).
1. Initial render: The execution of the parent component's **build()** function creates a new instance of the child component. The initialization process is as follows:
1. An \@State decorated variable of the parent component must be specified to initialize the child component's \@Link decorated variable. The child component's \@Link decorated variable value and its source variable are kept in sync (two-way data synchronization).
2. The \@State state variable wrapper class of the parent component is passed to the child component through the build function. After obtaining the \@State state variable of the parent component, the \@Link wrapper class of the child component registers the **this** pointer to the current \@Link wrapper class with the \@State variable of the parent component.
2. Update of the \@Link source: When the state variable in the parent component is updated, the \@Link decorated variable in the related child component is updated. Processing steps:
1. As indicated in the initial rendering step, the child component's \@Link wrapper class registers the current **this** pointer with the parent component. When the \@State decorated variable in the parent component is changed, all system components (**elementid**) and state variables (such as the \@Link wrapper class) that depend on the parent component are traversed and updated.
2. After the \@Link wrapper class is updated, all system components (**elementId**) that depend on the \@Link decorated variable in the child component are notified of the update. In this way, the parent component has the state data of the child components synchronized.
3. Update of \@Link: After the \@Link decorated variable in the child component is updated, the following steps are performed (the \@State decorated variable in the parent component is used):
1. After the \@Link decorated variable is updated, the **set** method of the \@State wrapper class in the parent component is called to synchronize the updated value back to the parent component.
2. The \@Link in the child component and \@State in the parent component traverse the dependent system components and update the corresponding UI. In this way, the \@Link decorated variable in the child component is synchronized back to the \@State decorated variable in the parent component.
## Application Scenarios
### Example for @Link with Simple and Class Types
The following example is for \@Link of both the simple type and class type. After **Parent View: Set yellowButton** and **Parent View: Set GreenButton** in the parent component **ShufflingContainer** are clicked, the change in the parent component is synchronized to the child components. The change of the \@Link decorated variable in the child components **GreenButton** and **YellowButton** is also synchronized to the parent component.
```ts
class GreenButtonState {
width: number = 0;
constructor(width: number) {
this.width = width;
}
}
@Component
struct GreenButton {
@Link greenButtonState: GreenButtonState;
build() {
Button('Green Button')
.width(this.greenButtonState.width)
.height(150.0)
.backgroundColor('#00ff00')
.onClick(() => {
if (this.greenButtonState.width < 700) {
// Update the attribute of the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState.width += 125;
} else {
// Update the class. The change can be observed and synchronized back to the parent component.
this.greenButtonState = new GreenButtonState(100);
}
})
}
}
@Component
struct YellowButton {
@Link yellowButtonState: number;
build() {
Button('Yellow Button')
.width(this.yellowButtonState)
.height(150.0)
.backgroundColor('#ffff00')
.onClick(() => {
// The change of the decorated variable of a simple type in the child component can be synchronized back to the parent component.
this.yellowButtonState += 50.0;
})
}
}
@Entry
@Component
struct ShufflingContainer {
@State greenButtonState: GreenButtonState = new GreenButtonState(300);
@State yellowButtonProp: number = 100;
build() {
Column() {
// Simple type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set yellowButton')
.onClick(() => {
this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 100 : 100;
})
// Class type @Link in the child component synchronized from @State in the parent component.
Button('Parent View: Set GreenButton')
.onClick(() => {
this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
})
// Initialize the class type @Link.
GreenButton({ greenButtonState: $greenButtonState })
// Initialize the simple type @Link.
YellowButton({ yellowButtonState: $yellowButtonProp })
}
}
}
```
### Array Type \@Link
```ts
@Component
struct Child {
@Link items: number[];
build() {
Column() {
Button(`Button1: push`).onClick(() => {
this.items.push(this.items.length + 1);
})
Button(`Button2: replace whole item`).onClick(() => {
this.items = [100, 200, 300];
})
}
}
}
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3];
build() {
Column() {
Child({ items: $arr })
ForEach(this.arr,
item => {
Text(`${item}`)
},
item => item.toString()
)
}
}
}
```
As described above, the ArkUI framework can observe the addition, deletion, and replacement of array items. It should be noted that, in the preceding example, the type of the \@Link and \@State decorated variables is the same: number[]. It is not allowed to define the \@Link decorated variable in the child component as type number (**\@Link item: number**), and create child components for each array item in the \@State decorated array in the parent component. [\@Prop](arkts-prop.md) or \@Observed should be used depending on application semantics.
# LocalStorage: UI State Storage
LocalStorage provides storage for the page-level UI state. The parameters of the LocalStorage type accepted through the \@Entry decorator share the same LocalStorage instance on the page. LocalStorage also allows for state sharing between pages within a UIAbility.
This topic describes only the LocalStorage application scenarios and related decorators: \@LocalStorageProp and \@LocalStorageLink.
> **NOTE**
>
> This module is supported since API version 9.
## Overview
LocalStorage is an in-memory "database" that ArkTS provides for storing state variables that are required to build pages of the application UI.
- An application can create multiple LocalStorage instances. These instances can be shared on a page or, by using the **GetShared** API from the UIAbility, across pages in a UIAbility.
- The root node of a component tree, that is, the \@Component decorated by \@Entry, can be assigned to a LocalStorage instance. All child instances of this custom component automatically gain access to the same LocalStorage instance.
- An \@Component decorated component has access to at most one LocalStorage instance and to [AppStorage](arkts-appstorage.md). A component not decorated with \@Entry cannot be assigned a LocalStorage instance. It can only accept a LocalStorage instance passed from its parent component through \@Entry. A LocalStorage instance can be assigned to multiple components in the component tree.
- All attributes in LocalStorage are mutable.
The application determines the lifecycle of a LocalStorage object. The JS Engine will garbage collect a LocalStorage object when the application releases the last reference to it, which includes deleting the last custom component.
LocalStorage provides two decorators based on the synchronization type of the component decorated with \@Component:
- [@LocalStorageProp](#localstorageprop): \@LocalStorageProp creates a one-way data synchronization from the named attribute in LocalStorage to the \@LocalStorageProp decorated variable.
- [@LocalStorageLink](#localstoragelink): \@LocalStorageLink creates a two-way data synchronization with the named attribute in the \@Component's LocalStorage.
## Restrictions
Once created, the type of a named attribute cannot be changed. Subsequent calls to **Set** must set a value of same type.
## \@LocalStorageProp
As mentioned above, if you want to establish a binding between LocalStorage and a custom component, you need to use the \@LocalStorageProp and \@LocalStorageLink decorators. Use \@LocalStorageProp(key) or \@LocalStorageLink(key) to decorate variables in the component. **key** identifies the attribute in LocalStorage.
When a custom component is initialized, the \@LocalStorageProp(key)/\@LocalStorageLink(key) decorated variable is initialized with the value of the attribute with the given key in LocalStorage. Local initialization is mandatory. If an attribute with the given key is missing from LocalStorage, it will be added with the stated initializing value. (Whether the attribute with the given key exists in LocalStorage depends on the application logic.)
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
By decorating a variable with \@LocalStorageProp(key), a one-way data synchronization is established with the attribute with the given key in LocalStorage. A local change can be made, but it will not besynchronized to LocalStorage. An update to the attribute with the given key in LocalStorage will overwrite local changes.
### Rules of Use
| \@LocalStorageProp Decorator| Description |
| ----------------------- | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | One-way: from the attribute in LocalStorage to the component variable. The component variable can be changed locally, but an update from LocalStorage will overwrite local changes.|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in LocalStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 1** \@LocalStorageProp initialization rule
![en-us_image_0000001501936014](figures/en-us_image_0000001501936014.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
- When the value change of the \@LocalStorageProp(key) decorated variable is observed, the change is not synchronized to the attribute with the give key value in LocalStorage.
- The value change of the \@LocalStorageProp(key) decorated variable only applies to the private member variables of the current component, but not other variables bound to the key.
- When the data decorated by \@LocalStorageProp(key) is a state variable, the change of the data is not synchronized to LocalStorage, but the owning custom component is re-rendered.
- When the attribute with the given key in LocalStorage is updated, the change is synchronized to all the \@LocalStorageProp(key) decorated data, and the local changes of the data are overwritten.
## \@LocalStorageLink
\@LocalStorageLink is required if you need to synchronize the changes of the state variables in a custom component back to LocalStorage.
\@LocalStorageLink(key) creates a two-way data synchronization with the attribute with the given key in LocalStorage.
1. If a local change occurs, it is synchronized to LocalStorage.
2. Changes in LocalStorage are synchronized to all attributes with the given key, including one-way bound variables (\@LocalStorageProp decorated variables and one-way bound variables created through \@Prop) and two-way bound variables (\@LocalStorageLink decorated variables and two-way bound variables created through \@Link).
### Rules of Use
| \@LocalStorageLink Decorator| Description |
| ----------------------- | ---------------------------------------- |
| Decorator parameters | **key**: constant string, mandatory (note, the string is quoted) |
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested objects, see [Observed Changes and Behavior](#observed-changes-and-behavior).<br>The type must be specified and must be the same as the corresponding attribute in LocalStorage. **any** is not supported. The **undefined** and **null** values are not allowed.|
| Synchronization type | Two-way: from the attribute in LocalStorage to the custom component variable and back|
| Initial value for the decorated variable | Mandatory. It is used as the default value for initialization if the attribute does not exist in LocalStorage.|
### Variable Transfer/Access Rules
| Transfer/Access | Description |
| ---------- | ---------------------------------------- |
| Initialization and update from the parent component| Forbidden.|
| Subnode initialization | Supported; can be used to initialize a n \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access | None. |
**Figure 2** \@LocalStorageLink initialization rule
![en-us_image_0000001552855957](figures/en-us_image_0000001552855957.png)
### Observed Changes and Behavior
**Observed Changes**
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
**Framework Behavior**
1. When the value change of the \@LocalStorageLink(key) decorated variable is observed, the change is synchronized to the attribute with the give key value in LocalStorage.
2. Once the attribute with the given key in LocalStorage is updated, all the data (including \@LocalStorageLink and \@LocalStorageProp decorated variables) bound to the attribute key is changed synchronously.
3. When the data decorated by \@LocalStorageProp(key) is a state variable, the change of the data is synchronized to LocalStorage, and the owning custom component is re-rendered.
## Application Scenarios
### Example of Using LocalStorage in Application Logic
```ts
let storage = new LocalStorage({ 'PropA': 47 }); // Create a new instance and initialize it with the given object.
let propA = storage.get('PropA') // propA == 47
let link1 = storage.link('PropA'); // link1.get() == 47
let link2 = storage.link('PropA'); // link2.get() == 47
let prop = storage.prop('PropA'); // prop.get() = 47
link1.set(48); // two-way sync: link1.get() == link2.get() == prop.get() == 48
prop.set(1); // one-way sync: prop.get()=1; but link1.get() == link2.get() == 48
link1.set(49); // two-way sync: link1.get() == link2.get() == prop.get() == 49
```
### Example for Using LocalStorage from Inside the UI
The two decorators \@LocalStorageProp and \@LocalStorageLink can work together to obtain the state variable stored in a LocalStorage instance in the UI component.
This example uses \@LocalStorage as an example to show how to:
- Use the **build** function to create a LocalStorage instance named **storage**.
- Use the \@Entry decorator to add **storage** to the top-level component **CompA**.
- Use \@LocalStorageLink to create a two-way data synchronization with the given attribute in LocalStorage.
```ts
// Create a new instance and initialize it with the given object.
let storage = new LocalStorage({ 'PropA': 47 });
@Component
struct Child {
// @LocalStorageLink creates a two-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageLink('PropA') storLink2: number = 1;
build() {
Button(`Child from LocalStorage ${this.storLink2}`)
// The changes will be synchronized to ProA in LocalStorage and with Parent.storLink1.
.onClick(() => this.storLink2 += 1)
}
}
// Make LocalStorage accessible from the @Component decorated component.
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink creates a two-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageLink('PropA') storLink1: number = 1;
build() {
Column({ space: 15 }) {
Button(`Parent from LocalStorage ${this.storLink1}`) // initial value from LocalStorage will be 47, because 'PropA' initialized already
.onClick(() => this.storLink1 += 1)
// The @Component decorated child component automatically obtains access to the CompA LocalStorage instance.
Child()
}
}
}
```
### Simple Example of Using \@LocalStorageProp with LocalStorage
In this example, the **CompA** and **Child** components create local data that is one-way synced with the PropA attribute in the LocalStorage instance **storage**.
- The change of **this.storProp1** in **CompA** takes effect only in **CompA** and is not synchronized to **storage**.
- In the **Child** component, the value of **storProp2** bound to **Text** is still 47.
```ts
// Create a new instance and initialize it with the given object.
let storage = new LocalStorage({ 'PropA': 47 });
// Make LocalStorage accessible from the @Component decorated component.
@Entry(storage)
@Component
struct CompA {
// @LocalStorageProp creates a one-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageProp('PropA') storProp1: number = 1;
build() {
Column({ space: 15 }) {
// The initial value is 47. After the button is clicked, the value is incremented by 1. The change takes effect only in storProp1 in the current component and is not synchronized to LocalStorage.
Button(`Parent from LocalStorage ${this.storProp1}`)
.onClick(() => this.storProp1 += 1)
Child()
}
}
}
@Component
struct Child {
// @LocalStorageProp creates a one-way data synchronization with the ProA attribute in LocalStorage.
@LocalStorageProp('PropA') storProp2: number = 2;
build() {
Column({ space: 15 }) {
// When CompA changes, the current storProp2 does not change, and 47 is displayed.
Text(`Parent from LocalStorage ${this.storProp2}`)
}
}
}
```
### Simple Example of Using \@LocalStorageLink and LocalStorage
This example shows how to create a two-way data synchronization between an \@LocalStorageLink decorated variable and LocalStorage.
```ts
// Create a LocalStorage instance.
let storage = new LocalStorage({ 'PropA': 47 });
// Invoke the link9+ API to create a two-way data synchronization with PropA. linkToPropA is a global variable.
let linkToPropA = storage.link('PropA');
@Entry(storage)
@Component
struct CompA {
// @LocalStorageLink('PropA') creates a two-way synchronization with PropA in the CompA custom component. The initial value is 47, because PropA has been set to 47 during LocalStorage construction.
@LocalStorageLink('PropA') storLink: number = 1;
build() {
Column() {
Text(`incr @LocalStorageLink variable`)
// Clicking incr @LocalStorageLink variable increases the value of this.storLink by 1. The change is synchronized back to the storage. The global variable linkToPropA also changes.
.onClick(() => this.storLink += 1)
// You are not advised to use the global variable linkToPropA.get() in the component because errors may occur due to different life cycles.
Text(`@LocalStorageLink: ${this.storLink} - linkToPropA: ${linkToPropA.get()}`)
}
}
}
```
### State Variable Synchronization Between Sibling Nodes
This example shows how to use \@LocalStorageLink to create a two-way synchronization for the state between sibling nodes.
Check the changes in the **Parent** custom component.
1. Clicking **countStorage ${this.playCount} incr by 1** decreases the value of **this.playCount** by 1. This change is synchronized to LocalStorage and to the components bound to **playCountLink** in the **Child** component.
2. Click **countStorage ${this.playCount} incr by 1** to call the **set** API in LocalStorage to update the attributes corresponding to **countStorage** in LocalStorage. The components bound to** playCountLink** in the **Child** component are updated synchronously.
3. The **playCount in LocalStorage for debug ${storage.get&lt;number&gt;('countStorage')}** **\<Text>** component is not updated synchronously, because **storage.get<number>('countStorage')** returns a regular variable. The update of a regular variable does not cause the **\<Text>** component to be re-rendered.
Changes in the **Child** custom component:
1. The update of **playCountLink** is synchronized to LocalStorage, and the parent and sibling child custom components are re-rendered accordingly.
```ts
let storage = new LocalStorage({ countStorage: 1 });
@Component
struct Child {
// Name the child component instance.
label: string = 'no name';
// Two-way synchronization with countStorage in LocalStorage.
@LocalStorageLink('countStorage') playCountLink: number = 0;
build() {
Row() {
Text(this.label)
.width(50).height(60).fontSize(12)
Text(`playCountLink ${this.playCountLink}: inc by 1`)
.onClick(() => {
this.playCountLink += 1;
})
.width(200).height(60).fontSize(12)
}.width(300).height(60)
}
}
@Entry(storage)
@Component
struct Parent {
@LocalStorageLink('countStorage') playCount: number = 0;
build() {
Column() {
Row() {
Text('Parent')
.width(50).height(60).fontSize(12)
Text(`playCount ${this.playCount} dec by 1`)
.onClick(() => {
this.playCount -= 1;
})
.width(250).height(60).fontSize(12)
}.width(300).height(60)
Row() {
Text('LocalStorage')
.width(50).height(60).fontSize(12)
Text(`countStorage ${this.playCount} incr by 1`)
.onClick(() => {
storage.set<number>('countStorage', 1 + storage.get<number>('countStorage'));
})
.width(250).height(60).fontSize(12)
}.width(300).height(60)
Child({ label: 'ChildA' })
Child({ label: 'ChildB' })
Text(`playCount in LocalStorage for debug ${storage.get<number>('countStorage')}`)
.width(300).height(60).fontSize(12)
}
}
}
```
### Sharing a LocalStorage Instance from UIAbility to One or More Pages
In the preceding examples, the LocalStorage instance is shared only in an \@Entry decorated component and its owning child component (a page). To enable a LocalStorage instance to be shared across pages, you can create a LocalStorage instance in the owning UIAbility and call windowStage.[loadContent](https://gitee.com/openharmony/docs/blob/master/en/application-dev/reference/apis/js-apis-window.md#loadcontent9).
```ts
// EntryAbility.ts
import UIAbility from '@ohos.app.ability.UIAbility';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
storage: LocalStorage = new LocalStorage({
'PropA': 47
});
onWindowStageCreate(windowStage: window.WindowStage) {
windowStage.loadContent('pages/Index', this.storage);
}
}
```
On the page, call the **GetShared** API to obtain the LocalStorage instance shared through **loadContent**.
```ts
// Use the GetShared API to obtain the Storage instance shared by stage.
let storage = LocalStorage.GetShared()
@Entry(storage)
@Component
struct CompA {
// can access LocalStorage instance using
// @LocalStorageLink/Prop decorated variables
@LocalStorageLink('PropA') varA: number = 1;
build() {
Column() {
Text(`${this.varA}`).fontSize(50)
}
}
}
```
> **NOTE**
>
> It is good practice to always create a LocalStorage instance with meaningful default values, which serve as a backup when execution exceptions occur and are also useful for unit testing of pages.
# \@Observed and \@ObjectLink: Observing Attribute Changes in Nested Class Objects
The decorators described above can observe only the changes of the first layer. However, in real-world application development, the application may encapsulate its own data model based on development requirements. In the case of multi-layer nesting, for example, a two-dimensional array, an array item class, or a class insider another class as an attribute, the attribute changes at the second layer cannot be observed. This is where the \@Observed and \@ObjectLink decorators come in handy.
> **NOTE**
>
> Since API version 9, these two decorators are supported in ArkTS widgets.
## Overview
\@ObjectLink and \@Observed class decorators are used for two-way data synchronization in scenarios involving nested objects or arrays:
- Regarding classes decorated by \@Observed, the attribute changes can be observed.
- The \@ObjectLink decorated state variable in the child component is used to accept the instance of the \@Observed decorated class and establish two-way data binding with the corresponding state variable in the parent component. The instance can be an \@Observed decorated item in the array or an \@Observeddecorated attribute in the class object.
- Using \@Observed alone has no effect. Combined use with \@ObjectLink for two-way synchronization or with [\@Prop](arkts-prop.md) for one-way synchronization is required.
## Decorator Description
| \@Observed Decorator| Description |
| -------------- | --------------------------------- |
| Decorator parameters | None. |
| Class decorator | Decorates a class. You must use **new** to create a class object before defining the class.|
| \@ObjectLink Decorator| Description |
| ----------------- | ---------------------------------------- |
| Decorator parameters | None. |
| Synchronization type | No synchronization with the parent component. |
| Allowed variable types | Objects of \@Observed decorated classes. The type must be specified.<br>Simple type variables are not supported. Use [\@Prop](arkts-prop.md) instead.<br>An \@ObjectLink decorated variable accepts changes to its attributes, but assignment is not allowed. In other words, an \@ObjectLink decorated variable is read-only and cannot be changed.|
| Initial value for the decorated variable | Not allowed. |
Example of a read-only \@ObjectLink decorated variable:
```ts
// The \@ObjectLink decorated variable accepts changes to its attribute.
this.objLink.a= ...
// Value assignment is not allowed for the \@ObjectLink decorated variable.
this.objLink= ...
```
> **NOTE**
>
> Value assignment is not allowed for the \@ObjectLink decorated variable. To assign a value, use [@Prop](arkts-prop.md) instead.
>
> - \@Prop creates a one-way synchronization from the data source to the decorated variable. It takes a copy of its source tp enable changes to remain local. When \@Prop observes a change to its source, the local value of the \@Prop decorated variable is overwritten.
>
> - \@ObjectLink creates a two-way synchronization between the data source and the decorated variable. An \@ObjectLink decorated variable can be considered as a pointer to the source object inside the parent component. If value assignment of an \@ObjectLink decorated variable occurs, the synchronization chain is interrupted.
## Variable Transfer/Access Rules
| \@ObjectLink Transfer/Access| Description |
| ----------------- | ---------------------------------------- |
| Initialization from the parent component | Mandatory.<br>To initialize an \@ObjectLink decorated variable, a variable in the parent component must meet all the following conditions:<br>- The variable type is an \@Observed decorated class.<br>- The initialized value must be an array item or a class attribute.<br>- The class or array of the synchronization source must be decorated by \@State, \@Link, \@Provide, \@Consume, or \@ObjectLink.<br>For an example where the synchronization source is an array item, see [Object Array](#object-array). For an example of the initialized class, see [Nested Object](#nested-object).|
| Synchronize with the source | Two-way. |
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
**Figure 1** Initialization rule
![en-us_image_0000001502255262](figures/en-us_image_0000001502255262.png)
## Observed Changes and Behavior
### Observed Changes
If the attribute of an \@Observed decorated class is not of the simple type, such as class, object, or array, it must be decorated by \@Observed. Otherwise, the attribute changes cannot be observed.
```ts
class ClassA {
public c: number;
constructor(c: number) {
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
public b: number;
constructor(a: ClassA, b: number) {
this.a = a;
this.b = b;
}
}
```
In the preceding example, **ClassB** is decorated by \@Observed, and the value changes of its member variables can be observed. In contrast, **ClassA** is not decorated by \@Observed, and therefore its attribute changes cannot be observed.
```ts
@ObjectLink b: ClassB
// The value assignment can be observed.
this.b.a = new ClassA(5)
this.b.b = 5
// ClassA is not decorated by @Observed, and its attribute changes cannot be observed.
this.b.a.c = 5
```
\@ObjectLink: \@ObjectLink can only accept instances of classes decorated by \@Observed. The following can be observed:
- Value changes of the attributes that **Object.keys(observedObject)** returns. For details, see [Nested Object](#nested-object).
- Replacement of array items for the data source of an array and changes of class attributes for the data source of a class. For details, see [Object Array](#object-array).
### Framework Behavior
1. Initial render:
1. \@Observed causes all instances of the decorated class to be wrapped with an opaque proxy object, which takes over the setter and getter methods of the attributes on the class.
2. The \@ObjectLink decorated variable in the child component is initialized from the parent component and accepts the instance of the \@Observed decorated class. The \@ObjectLink decorated wrapped object registers itself with the \@Observed decorated class.
2. Attribute update: When the attribute of the \@Observed decorated class is updated, the system uses the setter and getter of the proxy, traverses the \@ObjectLink decorated wrapped objects that depend on it, and notifies the data update.
## Application Scenarios
### Nested Object
The following is the data structure of a nested class object.
```ts
// objectLinkNestedObjects.ets
let NextID: number = 1;
@Observed
class ClassA {
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Observed
class ClassB {
public a: ClassA;
constructor(a: ClassA) {
this.a = a;
}
}
```
The following component hierarchy presents this data structure.
```ts
@Component
struct ViewA {
label: string = 'ViewA1';
@ObjectLink a: ClassA;
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c=${this.a.c} +1`)
.onClick(() => {
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
@State b: ClassB = new ClassB(new ClassA(0));
build() {
Column() {
ViewA({ label: 'ViewA #1', a: this.b.a })
ViewA({ label: 'ViewA #2', a: this.b.a })
Button(`ViewB: this.b.a.c+= 1`)
.onClick(() => {
this.b.a.c += 1;
})
Button(`ViewB: this.b.a = new ClassA(0)`)
.onClick(() => {
this.b.a = new ClassA(0);
})
Button(`ViewB: this.b = new ClassB(ClassA(0))`)
.onClick(() => {
this.b = new ClassB(new ClassA(0));
})
}
}
}
```
Event handlers in **ViewB**:
- this.b.a = new ClassA(0) and this.b = new ClassB(new ClassA(0)): Change to the \@State decorated variable **b** and its attributes.
- this.b.a.c = ... : Second change. [@State](arkts-state.md# observe the change) cannot observe the change of the second layer, but ClassA is decorated by \@Observed, and therefore the change of its attribute c can be observed by \@ObjectLink.
Event handlers in **ViewA**:
- this.a.c += 1: Changes to the \@ObjectLink decorated variable which cause the button label to be updated. Unlike \@Prop, \@ObjectLink does not have a copy of its source. Instead, \@ObjectLink creates a reference to its source.
- The \@ObjectLink decorated variable is read-only. Assigning **this.a = new ClassA(...)** is not allowed. Once value assignment occurs, the reference to the data source is reset and the synchronization is interrupted.
### Object Array
An object array is a frequently used data structure. The following example shows the usage of array objects.
```ts
@Component
struct ViewA {
// The type of @ObjectLink of the child component ViewA is ClassA.
@ObjectLink a: ClassA;
label: string = 'ViewA1';
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c = ${this.a.c} +1`)
.onClick(() => {
this.a.c += 1;
})
}
}
}
@Entry
@Component
struct ViewB {
// ViewB has the @State decorated ClassA[].
@State arrA: ClassA[] = [new ClassA(0), new ClassA(0)];
build() {
Column() {
ForEach(this.arrA,
(item) => {
ViewA({ label: `#${item.id}`, a: item })
},
(item) => item.id.toString()
)
// Initialize the @ObjectLink decorated variable using the array item in the @State decorated array, which is an instance of ClassA decorated by @Observed.
ViewA({ label: `ViewA this.arrA[first]`, a: this.arrA[0] })
ViewA({ label: `ViewA this.arrA[last]`, a: this.arrA[this.arrA.length-1] })
Button(`ViewB: reset array`)
.onClick(() => {
this.arrA = [new ClassA(0), new ClassA(0)];
})
Button(`ViewB: push`)
.onClick(() => {
this.arrA.push(new ClassA(0))
})
Button(`ViewB: shift`)
.onClick(() => {
this.arrA.shift()
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)].c = 10;
})
Button(`ViewB: chg item property in middle`)
.onClick(() => {
this.arrA[Math.floor(this.arrA.length / 2)] = new ClassA(11);
})
}
}
}
```
- this.arrA[Math.floor(this.arrA.length/2)] = new ClassA(..): The change of this state variable triggers two updates.
1. ForEach: The value assignment of the array item causes the change of [itemGenerator](arkts-rendering-control-foreach.md#api-description) of **ForEach**. Therefore, the array item is identified as changed, and the item builder of ForEach is executed to create a **ViewA** component instance.
2. ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] }): The preceding update changes the first element in the array. Therefore, the **ViewA** component instance bound to **this.arrA[0]** is updated.
- this.arrA.push(new ClassA(0)): The change of this state variable triggers two updates with different effects.
1. ForEach: The newly added Class A object is unknown to the **ForEach** [itemGenerator](arkts-rendering-control-foreach.md#api-description). The item builder of **ForEach** will be executed to create a **View A** component instance.
2. ViewA({ label: ViewA this.arrA[last], a: this.arrA[this.arrA.length-1] }): The last item of the array is changed. As a result, the second **View A** component instance is changed. For **ViewA({ label: ViewA this.arrA[first], a: this.arrA[0] })**, a change to the array does not trigger a change to the array item, so the first **View A** component instance is not refreshed.
- this.arrA[Math.floor (this.arrA.length/2)].c: [@State] (arkts-state.md#observe-changes) cannot observe changes in the second layer. However, as **ClassA** is decorated by \@Observed, the change of its attributes will be observed by \@ObjectLink.
### Two-Dimensional Array
@Observed class decoration is required for a two-dimensional array. You can declare an \@Observed decorated class that extends from **Array**.
```ts
@Observed
class StringArray extends Array<String> {
}
```
Declare a class that extends from** Array**: **class StringArray extends Array\<String> {}** and create an instance of **StringArray**. The use of the **new** operator is required for the \@Observed class decorator to work properly.
```ts
@Observed
class StringArray extends Array<String> {
}
@Component
struct ItemPage {
@ObjectLink itemArr: StringArray;
build() {
Row() {
Text('ItemPage')
.width(100).height(100)
ForEach(this.itemArr,
item => {
Text(item)
.width(100).height(100)
},
item => item
)
}
}
}
@Entry
@Component
struct IndexPage {
@State arr: Array<StringArray> = [new StringArray(), new StringArray(), new StringArray()];
build() {
Column() {
ItemPage({ itemArr: this.arr[0] })
ItemPage({ itemArr: this.arr[1] })
ItemPage({ itemArr: this.arr[2] })
Divider()
ForEach(this.arr,
itemArr => {
ItemPage({ itemArr: itemArr })
},
itemArr => itemArr[0]
)
Divider()
Button('update')
.onClick(() => {
console.error('Update all items in arr');
if (this.arr[0][0] !== undefined) {
// We should have a real ID to use with ForEach, but we do no.
// Therefore, we need to make sure the pushed strings are unique.
this.arr[0].push(`${this.arr[0].slice(-1).pop()}${this.arr[0].slice(-1).pop()}`);
this.arr[1].push(`${this.arr[1].slice(-1).pop()}${this.arr[1].slice(-1).pop()}`);
this.arr[2].push(`${this.arr[2].slice(-1).pop()}${this.arr[2].slice(-1).pop()}`);
} else {
this.arr[0].push('Hello');
this.arr[1].push('World');
this.arr[2].push('!');
}
})
}
}
}
```
# Overview of Other State Management Features
In addition to the component state and application state management mentioned in previous topics, ArkTS also provides \@Watch and $$ for state management.
- [\@Watch](arkts-watch.md) is used to listen for the changes of state variables.
- [$$operator](arkts-two-way-sync.md) provides a TS variable by-reference to a built-in component so that the variable value and the internal state of that component are kept in sync.
# Page and Custom Component Lifecycle
Before we dive into the page and custom component lifecycle, it would be helpful to learn the relationship between custom components and pages.
- Custom component: \@Component decorated UI unit, which can combine multiple built-in components for component reusability.
- Page: UI page of an application. A page can consist of one or more custom components. A custom component decorated with \@Entry is used as the default entry component of the page. Exactly one component can be decorated with \@Entry in a single source file. Only components decorated by \@Entry can call the lifecycle callbacks of a page.
The following lifecycle callbacks are provided for the lifecycle of a page, that is, the lifecycle of a custom component decorated with \@Entry:
- [onPageShow](../reference/arkui-ts/ts-custom-component-lifecycle.md#onpageshow): Invoked when the page is displayed.
- [onPageHide](../reference/arkui-ts/ts-custom-component-lifecycle.md#onpagehide): Invoked when the page is hidden.
- [onBackPress](../reference/arkui-ts/ts-custom-component-lifecycle.md#onbackpress): Invoked when the user clicks the Back button.
The following lifecycle callbacks are provided for the lifecycle of a custom component, which is one decorated with \@Component:
- [aboutToAppear](../reference/arkui-ts/ts-custom-component-lifecycle.md#abouttoappear): Invoked when the custom component is about to appear. Specifically, it is invoked after a new instance of the custom component is created and before its **build** function is executed.
- [aboutToDisappear](../reference/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear): Invoked before the destructor of the custom component is consumed.
The following figure shows the lifecycle of a component (home page) decorated with \@Entry.
![en-us_image_0000001502372786](figures/en-us_image_0000001502372786.png)
Based on the preceding figure, let's look into the initial creation, re-rendering, and deletion of a custom component.
## Custom Component Creation and Rendering
1. Custom component creation: An instance of a custom component is created by the ArkUI framework.
2. Initialization of custom component member variables: The member variables are initialized with locally defined default values or component constructor parameters. The initialization happens in the document order, which is the order in which the member variables are defined.
3. If defined, the component's **aboutToAppear** callback is invoked.
4. On initial render, the **build** function of a component is executed for rendering. The rendering creates instances of further child components. While executing the **build** function, the framework observes read access on each state variable and then constructs two mapping tables:
1. State variable -> UI component (including **ForEach** and **if**)
2. UI component -> Update function for this component, which is a lambda. As a subset of the **build** function, the lambda creates one UI component and executes its attribute methods.
```ts
build() {
...
this.observeComponentCreation(() => {
Button.create();
})
this.observeComponentCreation(() => {
Text.create();
})
...
}
```
When the application is started in the background, since the application process is not destroyed, only the **onPageShow** callback is invoked.
## Custom Component Re-rendering
When an event handle is triggered (for example, the click event is triggered), the state variable of the component is changed, or the attribute in LocalStorage or AppStorage is changed, which causes the value of the linked state variable to be changed.
1. The framework observes the variable change and marks the component for re-rendering.
2. Using the two mapping tables created in step 4 of the custom component creation and rendering process, the framework knows which UI components are managed by the state variable and the update functions corresponding to these UI components. With this knowledge, the framework executes only the update functions of these UI components.
## Custom Component Deletion
A custom component is deleted when the branch of the **if** statement or the number of arrays in **ForEach** changes.
1. Before the component is deleted, the **aboutToDisappear** callback is invoked to mark the component for deletion. The component deletion mechanism of ArkUI is as follows: (1) The backend component is directly removed from the component tree and destroyed; (2) The reference to the destroyed component is released from the frontend components; (3) The JS Engine garbage collects the destroyed component.
2. The custom component and all its variables are deleted. Any variables linked to this component, such as [@Link](arkts-link.md), [@Prop](arkts-prop.md), or [@StorageLink](arkts-appstorage.md#storagelink) decorated variables, are unregistered from their [synchronization sources](arkts-state-management-overview.md#basic-concepts).
Use of **async await** is not recommended inside the **aboutToDisappear** callback. In case of an asynchronous operation (a promise or a callback) being started from the **aboutToDisappear** callback, the custom component will remain in the Promise closure until the function has been called, which prevents the component from being garbage collected.
The following example when the lifecycle callbacks are invoked:
```ts
// Index.ets
import router from '@ohos.router';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
// Only components decorated by @Entry can call the lifecycle callbacks of a page.
onPageShow() {
console.info('Index onPageShow');
}
// Only components decorated by @Entry can call the lifecycle callbacks of a page.
onPageHide() {
console.info('Index onPageHide');
}
// Only components decorated by @Entry can call the lifecycle callbacks of a page.
onBackPress() {
console.info('Index onBackPress');
}
// Component lifecycle
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// Component lifecycle
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// When this.showChild is true, the Child child component is created, and Child aboutToAppear is invoked.
if (this.showChild) {
Child()
}
// When this.showChild is false, the Child child component is deleted, and Child aboutToDisappear is invoked.
Button('create or delete Child').onClick(() => {
this.showChild = false;
})
// Because of the pushing from the current page to Page2, onPageHide is invoked.
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/Page2' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// Component lifecycle
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// Component lifecycle
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear')
}
build() {
Text(this.title).fontSize(50).onClick(() => {
this.title = 'Hello ArkUI';
})
}
}
```
In the preceding example, the **Index** page contains two custom components. One is **MyComponent** decorated with \@Entry, which is also the entry component (root node) of the page. The other is **Child**, which is a child component of **MyComponent**. Only components decorated by \@Entry can call the lifecycle callbacks of a page.Therefore, the page lifecycle callbacks of the **Index** page are declared in **MyComponent**. **MyComponent** and its child components also declare the lifecycle callbacks of the respective component.
- The initialization process of application cold start is as follows: MyComponent aboutToAppear -> MyComponent build -> Child aboutToAppear -> Child build -> Child build execution completed -> MyComponent build execution completed -> Index onPageShow.
- When **delete Child** is clicked, the value of **this.showChild** linked to **if** changes to **false**. As a result, the **Child** component is deleted, and the **Child aboutToDisappear** callback is invoked.
- When **push to next page** is clicked, the **router.pushUrl** API is called to jump to the next page. As a result, the **Index** page is hidden, and the **Index onPageHide** callback is invoked. As the called API is **router.pushUrl**, which results in the Index page being hidden, but not destroyed, only the **onPageHide** callback is invoked. After a new page is displayed, the process of initializing the lifecycle of the new page is executed.
- If **router.replaceUrl** is called, the **Index** page is destroyed. In this case, the execution of lifecycle callbacks changes to: Index onPageHide -> MyComponent aboutToDisappear -> Child aboutToDisappear. As aforementioned, a component is destroyed by directly removing it from the component tree. Therefore, **aboutToDisappear** of the parent component is called first, followed by **aboutToDisAppear** of the child component, and then the process of initializing the lifecycle of the new page is executed.
- When the Back button is clicked, the **Index onBackPress** callback is invoked. When the application is minimized or switched to the background, the **Index onPageHide** callback is invoked. The application is not destroyed in these two states. Therefore, the **aboutToDisappear** callback of the component is not executed. When the application returns to the foreground, the **Index onPageShow** callback is invoked.
- When the application exits, the following callbacks are executed in order: Index onPageHide -> MyComponent aboutToDisappear -> Child aboutToDisappear.
# PersistentStorage: Application State Persistence
During application development, you may want selected attributes to persist even when the application is closed. In this case, you'll need PersistentStorage.
PersistentStorage is an optional singleton object within an application. Its purpose is to persist selected AppStorage attributes so that their values are the same upon application re-start as they were when the application was closed.
## Overview
PersistentStorage retains the selected AppStorage attributes on the device disk. The application uses the API to determine which AppStorage attributes should be persisted with PersistentStorage. The UI and business logic do not directly access attributes in PersistentStorage. All attribute access is to AppStorage. Changes in AppStorage are automatically synchronized to PersistentStorage.
PersistentStorage creates a two-way synchronization with attributes in AppStorage. A frequently used API function is to access AppStorage through PersistentStorage. Additional API functions include managing persisted attributes. The business logic always obtains or sets attributes through AppStorage.
## Restrictions
Persistence of data is a relatively slow operation. Applications should avoid the following situations:
- Persistence of large data sets
- Persistence of variables that change frequently
The preceding situations may overload the change process of persisted data. As a result, the PersistentStorage implementation may limit the change frequency of persisted attributes.
## Application Scenarios
### Accessing PersistentStorage Initialized Attribute from AppStorage
1. Initialize the PersistentStorage instance.
```ts
PersistentStorage.PersistProp('aProp', 47);
```
2. Obtain the corresponding attribute from AppStorage.
```ts
AppStorage.Get('aProp'); // returns 47
```
Alternatively, apply local definition within the component:
```ts
@StorageLink('aProp') aProp: number = 48;
```
The complete code is as follows:
```ts
PersistentStorage.PersistProp('aProp', 47);
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
@StorageLink('aProp') aProp: number = 48
build() {
Row() {
Column() {
Text(this.message)
// The current result is saved when the application exits. After the restart, the last saved result is displayed.
Text(`${this.aProp}`)
.onClick(() => {
this.aProp += 1;
})
}
}
}
}
```
- First running after fresh application installation:
1. **PersistProp** is called to initialize PersistentStorage. A search for the **aProp** attribute on the PersistentStorage disk returns no result, because the application has just been installed.
2. A search for the attribute **aProp** in AppStorage still returns no result.
3. Create the **aProp** attribute of the number type in AppStorge and initialize it with the value 47.
4. PersistentStorage writes the **aProp** attribute and its value **47** to the disk. The value of **aProp** in AppStorage and its subsequent changes are persisted.
5. Create the state variable **\@StorageLink('aProp') aProp** in the **\<Index>** component, which creates a two-way synchronization with the **aProp** attribute in AppStorage. During the creation, the search in AppStorage for the **aProp** attribute succeeds, and therefore, the state variable is initialized with the value **47** found in AppStorage.
**Figure 1** PersistProp initialization process
![en-us_image_0000001553348833](figures/en-us_image_0000001553348833.png)
- After a click event is triggered:
1. The state variable **\@StorageLink('aProp') aProp** is updated, triggering the **\<Text>** component to be re-rendered.
2. The two-way synchronization between the \@StorageLink decorated variable and AppStorage results in the change of the **\@StorageLink('aProp') aProp** being synchronized back to AppStorage.
3. The change of the **aProp** attribute in AppStorage triggers any other one-way or two-way bound variables to be updated. (In this example, there are no such other variables.)
4. Because the attribute corresponding to **aProp** has been persisted, the change of the **aProp** attribute in AppStorage triggers PersistentStorage to write the attribute and its changed value to the device disk.
- Subsequent application running:
1. **PersistentStorage.PersistProp('aProp', 47)** is called. A search for the **aProp** attribute on the PersistentStorage disk succeeds.
2. The attribute is added to AppStorage with the value found on the PersistentStorage disk.
3. In the **\<Index>** component, the value of the @StorageLink decorated **aProp** attribute is the value written by PersistentStorage to AppStorage, that is, the value stored when the application was closed last time.
### Accessing Attribute in AppStorage Before PersistentStorage
This example is an incorrect use. It is incorrect to use the API to access the attributes in AppStorage before calling **PersistentStorage.PersistProp** or **PersistProps**, because such a call sequence will result in loss of the attribute values used in the previous application run:
```ts
let aProp = AppStorage.SetOrCreate('aProp', 47);
PersistentStorage.PersistProp('aProp', 48);
```
**AppStorage.SetOrCreate('aProp', 47)**: The **aProp** attribute is created in AppStorage, its type is number, and its value is set to the specified default value **47**. **aProp** is a persisted attribute. Therefore, it is written back to the PersistentStorage disk, and the value stored in the PersistentStorage disk from the previous run is lost.
**PersistentStorage.PersistProp('aProp', 48)**: An attribute with same name **aProp** is available in PersistentStorage.
# \@Prop: One-Way Synchronization from Parent to Child Components
An \@Prop decorated variable can create one-way synchronization with a variable of its parent component. \@Prop decorated variables are mutable, but changes are not synchronized to the parent component.
> **NOTE**
>
> Since API version 9, this decorator is supported in ArkTS widgets.
## Overview
For an \@Prop decorated variable, the value synchronization is uni-directional from the parent component to the owning component.
- An @Prop variable is allowed to be modified locally, but the change does not propagate back to its parent component.
- Whenever that data source changes, the @Prop decorated variable gets updated, and any locally made changes are overwritten.
## Rules of Use
| \@Prop Decorator| Description |
| ----------- | ---------------------------------------- |
| Decorator parameters | None. |
| Synchronization type | One-way: from the data source provided by the parent component to the @Prop decorated variable.|
| Allowed variable types | string, number, boolean, or enum type.<br>**any** is not supported. The **undefined** and **null** values are not allowed.<br>The type must be specified.<br>Negative examples:<br>CompA&nbsp;({&nbsp;aProp:&nbsp;undefined&nbsp;})<br>CompA&nbsp;({&nbsp;aProp:&nbsp;null&nbsp;})<br>The type must be the same as that of the [data source](arkts-state-management-overview.md#basic-concepts). There are three cases (\@State is used as an example of the data source):<br>- The type of the \@Prop decorated variable is the same as that of the state variable of the parent component, that is, \@Prop: S and \@State: S. For an example, see [Simple Type @Prop Synced from @State in Parent Component](#simple-type-prop-synced-from-state-in-parent-component).<br>- When the state variable of the parent component is an array, the type of the \@Prop decorated variable is the same as that of the array item of the state variable of the parent component, that is, \@Prop: S and \@State: Array\<S>. For examples, see [Simple Type @Prop Synched from @State Array Item in Parent Component](#simple-type-prop-synched-from-state-array-item-in-parent-component).<br>- When the state variable of the parent component is Object or class, the type of the \@Prop decorated variableis the same as the attribute type of the state variable of the parent component, that is, \@Prop: S and \@State: { propA: S }. For examples, see [Class Object Type @Prop Synchedd from @State Class Object Property in Parent Component](#class-object-type-prop-synchedd-from-state-class-object-property-in-parent-component).|
| Initial value for the decorated variable | Local initialization is allowed. |
## Variable Transfer/Access Rules
| Transfer/Access | Description |
| --------- | ---------------------------------------- |
| Initialization from the parent component | Optional. Initialization from the parent component or local initialization can be used. An \@Prop decorated variable can be initialized from a regular variable or an \@State, \@Link, \@Prop, \@Provide, \@Consume, \@ObjectLink, \@StorageLink, \@StorageProp, \@LocalStorageLink, or \@LocalStorageProp decorated variable in its parent component.|
| Subnode initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Access| Private, accessible only within the component. |
**Figure 1** Initialization rule
![en-us_image_0000001552972029](figures/en-us_image_0000001552972029.png)
## Observed Changes and Behavior
### Observed Changes
\@Prop decorated variables can observe the following changes:
- When the decorated variable is of the string, number, Boolean, or enum type, its value change can be observed.
```ts
// Simple type
@Prop count: number;
// The value assignment can be observed.
this.count = 1;
```
For synchronization between \@State and \@Prop decorated variables:
- The value of an \@State decorated variable in the parent component initializes an \@Prop decorated variable in the child component. The \@State decorated variable also updates the @Prop decorated variable whenever the value of the former changes.
- Changes to the @Prop decorated variable do not affect the value of its source @State decorated variable.
- In addition to \@State, the source can also be decorated with \@Link or \@Prop, where the mechanisms for syncing the \@Prop would be the same.
- The type of the source and the @Prop decorated variable must be the same.
### Framework Behavior
To understand the value initialization and update mechanism of the \@Prop decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the \@Prop decorated variable.
1. Initial render:
1. The execution of the parent component's **build()** function creates a new instance of the child component, and the parent component provides a source for the @Prop decorated variable.
2. The @Prop decorated variable is initialized.
2. Update:
1. When the @Prop decorated variable is modified locally, the change remains local and does not propagate back to its parent component.
2. When the data source of the parent component is updated, the \@Prop decorated variable in the child component is reset, and its local value changes are overwritten.
## Application Scenarios
### Simple Type @Prop Synced from @State in Parent Component
In this example, the \@Prop decorated **count** variable in the **CountDownComponent** child component is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**. When **Try again** is touched, the value of the **count** variable is modified, but the change remains within the **CountDownComponent** and does not affect the **CountDownComponent**.
Updating **countDownStartValue** in the **ParentComponent** will update the value of the @Prop decorated **count**.
```ts
@Component
struct CountDownComponent {
@Prop count: number;
costOfOneAttempt: number = 1;
build() {
Column() {
if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`)
} else {
Text('Game over!')
}
// Changes to the @Prop decorated variables are not synchronized to the parent component.
Button(`Try again`).onClick(() => {
this.count -= this.costOfOneAttempt;
})
}
}
}
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10;
build() {
Column() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`)
// Changes to the data source provided by the parent component are synchronized to the child component.
Button(`+1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue += 1;
})
// Updating the parent component will also update the child component.
Button(`-1 - Nuggets in New Game`).onClick(() => {
this.countDownStartValue -= 1;
})
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
}
}
}
```
In the preceding example:
1. On initial render, when the **CountDownComponent** child component is created, its @Prop decorated **count** variable is initialized from the \@State decorated **countDownStartValue** variable in the **ParentComponent**.
2. When the "+1" or "-1" button is touched, the @State decorated **countDownStartValue** of the **ParentComponent** changes. This will cause the **ParentComponent** to re-render. At the minumum, the **CountDownComponent** will be updated because of the changed **count** variable value.
3. Because of the changed **count** variable value, the **CountDownComponent** child component will re-render. At a minimum, the **if** statement's conditions (**this.counter> 0**) is-evaluated and the **\<Text>** child component inside the **if** would be updated.
4. When **Try again** in the **CountDownComponent** child component is touched, the value of the **count** variable is modified, but the change remains within the child component and does not affect the **countDownStartValue** in the parenet component.
5. Updating **countDownStartValue** will overwrite the local value changes of the @Prop decorated **count** in the **CountDownComponent** child component.
### Simple Type @Prop Synched from @State Array Item in Parent Component
The \@State decorated array an array item in the parent component can be used as data source to initialize and update a @Prop decorated variable. In the following example, the \@State decorated array **arr** in the parent component **Index** initializes the \@Prop decorated **value** variable in the child component **Child**.
```ts
@Component
struct Child {
@Prop value: number;
build() {
Text(`${this.value}`)
.fontSize(50)
.onClick(()=>{this.value++})
}
}
@Entry
@Component
struct Index {
@State arr: number[] = [1,2,3];
build() {
Row() {
Column() {
Child({value: this.arr[0]})
Child({value: this.arr[1]})
Child({value: this.arr[2]})
Divider().height(5)
ForEach(this.arr,
item => {
Child({value: item})
},
item => item.toString()
)
Text('replace entire arr')
.fontSize(50)
.onClick(()=>{
// Both arrays contain item "3".
this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
})
}
}
}
}
```
Initial render creates six instances of the **Child** component. Each \@Prop decorated variable is initialized with a copy of an array item. The **onclick** event handler of the **Child** component changes the local variable value.
Assume that we clicked so many times that all local values be '7'.
```
7
7
7
----
7
7
7
```
After **replace entire arr** is clicked, the following information is displayed:
```
3
4
5
----
7
4
5
```
- Changes made in the **Child** component are not synchronized to the parent component **Index**. Therefore, even if the values of the six intances of the **Child** component are 7, the value of **this.arr** in the **Index** component is still **[1,2,3]**.
- After **replace entire arr** is clicked, if **this.arr[0] == 1** is true, **this.arr** is set to **[3, 4, 5]**.
- Because **this.arr[0]** has been changed, the **Child({value: this.arr[0]})** component synchronizes the update of **this.arr[0]** to the instance's \@Prop decorated variable. The same happens for **Child({value: this.arr[1]})** and **Child({value: this.arr[2]})**.
- The change of **this.arr** causes **ForEach** to update: The array item with the ID **3** is retained in this update, array items with IDs **1** and **2** are deleted, and array items with IDs **4** and **5** are added. The array before and after the update is **[1, 2, 3]** and **[3, 4, 5]**, respectively. This implies that the **Child** instance generated for item **3** will be moved to the first place, but not updated. In this case, the component value corresponding to **3** is **7**, and the final render result of **ForEach** is **7**, **4**, and **5**.
### Class Object Type @Prop Synchedd from @State Class Object Property in Parent Component
In a library with one book and two users, each user can mark the book as read, but this does not affect the other user reader. Technically speaking, local changes to the \@Prop decorated **book** object do not sync back to the @State decorated **book** in the **Library** component.
```ts
class Book {
public title: string;
public pages: number;
public readIt: boolean = false;
constructor(title: string, pages: number) {
this.title = title;
this.pages = pages;
}
}
@Component
struct ReaderComp {
@Prop title: string;
@Prop readIt: boolean;
build() {
Row() {
Text(this.title)
Text(`... ${this.readIt ? 'I have read' : 'I have bot read it'}`)
.onClick(() => this.readIt = true)
}
}
}
@Entry
@Component
struct Library {
@State book: Book = new Book('100 secrets of C++', 765);
build() {
Column() {
ReaderComp({ title: this.book.title, readIt: this.book.readIt })
ReaderComp({ title: this.book.title, readIt: this.book.readIt })
}
}
}
```
### Simple Type @Prop with Local Initialization and No Sync from Parent Parent
To enable an \@Component decorated component to be reusable, \@Prop allows for optional local initialization. This makes the synchronization with a variable in the parent component a choice, rather than mandatory. Providing a data source in the parent component is optional only when local initialization is provided for the \@Prop decorated variable.
The following example includes two @Prop decorated variables in child component.
- The @Prop decorated variable **customCounter** has no local initialization, and therefore it requires a synchronization source in its parent component. When the source value changes, the @Prop decorated variable is udpated.
- The @Prop decorated variable **customCounter2** has local initialization. In this case, specifying a synchronization source in the parent component is allowed but not mandatory.
```ts
@Component
struct MyComponent {
@Prop customCounter: number;
@Prop customCounter2: number = 5;
build() {
Column() {
Row() {
Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010')
}
Row() {
Button('Click to change locally !').width(480).height(60).margin({ top: 10 })
.onClick(() => {
this.customCounter2++
})
}.height(100).width(480)
Row() {
Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010')
}
}
}
}
@Entry
@Component
struct MainProgram {
@State mainCounter: number = 10;
build() {
Column() {
Row() {
Column() {
Button('Click to change number').width(480).height(60).margin({ top: 10, bottom: 10 })
.onClick(() => {
this.mainCounter++
})
}
}
Row() {
Column(
// customCounter must be initialized from the parent component due to lack of local initialization. Here, customCounter2 does not need to be initialized.
MyComponent({ customCounter: this.mainCounter })
// customCounter2 of the child component can also be initialized from the parent component. The value from the parent component overwrites the locally assigned value of customCounter2 during initialization.
MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
}.width('40%')
}
Row() {
Text('').width(480).height(10)
}
}
}
}
```
# \@Provide and \@Consume: Two-Way Synchronization with Descendant Components
\@Provide and \@Consume are used for two-way data synchronization with descendant components in scenarios where state data needs to be transferred between multiple levels. They do not involve passing a variable from component to component multiple times.
An \@Provide decorated state variable exists in the ancestor component and is said to be "provided" to descendent components. An \@Consume decorated state variable is used in a descendent component. It is linked to ("consumes") the provided state variable in its ancestor component.
> **NOTE**
>
> Since API version 9, these two decorators are supported in ArkTS widgets.
## Overview
\@Provide/\@Consume decorated state variables have the following features:
- An \@Provide decorated state variable becomes available to all descendent components of the providing component automatically. The variable is said to be "provided" to other components. This means that you do not need to pass a variable from component to component multiple times.
- A descendent component gains access to the provided state variable by decorating a variable with \@Consume. This establishes a two-way data synchronization between the provided and the consumed variable. This synchronization works the same as a combination of \@State and \@Link does. The only difference is that the former allows transfer across multiple levels of the UI parent-child hierarchy.
- \@Provide and \@Consume can be bound using the same variable name or variable alias. The variable types must be the same.
```ts
// Binding through the same variable name
@Provide a: number = 0;
@Consume a: number;
// Binding through the same variable alias
@Provide('a') b: number = 0;
@Consume('a') c: number;
```
When \@Provide and \@Consume are bound through the same variable name or variable alias, the variables decorated by \@Provide and \@Consume are in a one-to-many relationship. A custom component, including its child components, cannot contain multiple \@Provide decorated variables under the same name or alias.
## Decorator Description
The rules of \@State also apply to \@Provide. The difference is that \@Provide also functions as a synchronization source for multi-layer descendants.
| \@Provide Decorator| Description |
| -------------- | ---------------------------------------- |
| Decorator parameters | Alias: constant string, optional.<br>If the alias is specified, the variable is provided under the alias name only. If the alias is not specified, the variable is provided under the variable name.|
| Synchronization type | Two-way:<br>from the \@Provide decorated variable to all \@Consume decorated variables; and the other way around. The two-way synchronization behaviour is the same as that of the combination of \@State and \@Link.|
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).<br>**any** is not supported. A combination of simple and complex types is not supported. The **undefined** and **null** values are not allowed.<br>The type must be specified. The type of the provided and the consumed variables must be the same.<br>**NOTE**<br>The Length, ResourceStr, and ResourceColor types are a combination of simple and complex types and therefore not supported.|
| Initial value for the decorated variable | Mandatory. |
| \@Consume Decorator| Description |
| -------------- | ---------------------------------------- |
| Decorator parameters | Alias: constant string, optional.<br>If the alias is specified, the alias name is used for matching with the \@Provide decorated variable. Otherwise, the variable name is used.|
| Synchronization type | from the \@Provide decorated variable to all \@Consume decorated variables; and the other way around. The two-way synchronization behaviour is the same as that of the combination of \@State and \@Link.|
| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types. For details about the scenarios of nested types, see [Observed Changes](#observed-changes).<br>**any** is not supported. The **undefined** and **null** values are not allowed.<br>The type must be specified. The type of the provided and the consumed variables must be the same.<br>**NOTE**<br>An \@Consume decorated variable must have a matching \@Provide decorated variable with the corresponding attribute and alias on its parent or ancestor node.|
| Initial value for the decorated variable | Forbidden. |
## Variable Transfer/Access Rules
| \@Provide Transfer/Access| Description |
| -------------- | ---------------------------------------- |
| Initialization and update from the parent component | Optional. An \@Provide decorated variable can be initialized from a regular variable or an \@State, \@Link, \@Prop, \@Provide, \@Consume, \@ObjectLink, \@StorageLink, \@StorageProp, \@LocalStorageLink, or \@LocalStorageProp decorated variable in its parent component.|
| Subnode initialization | Supported; can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Synchronization with the parent component | None. |
| Synchronization with descendant components | Two-way with @Consume decorated variables in descendant components. |
| Access | Private, accessible only within the component. |
**Figure 1** \@Provide initialization rule
![en-us_image_0000001552614217](figures/en-us_image_0000001552614217.png)
| \@Consume Transfer/Access| Description |
| -------------- | ---------------------------------------- |
| Initialization and update from the parent component | Forbidden. Initialized from the \@Provide decorated variable with the same name or alias. |
| Subnode initialization | Supported; can be used to initialize an \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
| Synchronization with the ancestor component | Two-way with the @Provide decorated variable in the ancestor component. |
| Access | Private, accessible only within the component. |
**Figure 2** \@Consume initialization rule
![en-us_image_0000001502094666](figures/en-us_image_0000001502094666.png)
## Observed Changes and Behavior
### Observed Changes
- When the decorated variable is of the Boolean, string, or number type, its value change can be observed.
- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns.
- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed.
### Framework Behavior
1. Initial render:
1. The \@Provide decorated variable is passed to all child components of the owning component in map mode.
2. If an \@Consume decorated variable is used in a child component, the system checks the map for a matching \@Provide decorated variable based on the variable name or alias. If no matching variable is found, the framework throws a JS error.
3. The process of initializing the \@Consume decorated variable is similar to that of initializing the \@State/\@Link decorated variable. The \@Consume decorated variable saves the matching \@Provide decorated variable found in the map and registers itself with the \@Provide decorated variable.
2. When the \@Provide decorated variable is updated:
1. The system traverses and updates all system components (**elementid**) and state variable (\@Consume) that depend on the \@Provide decorated variable, with which the \@Consume decorated variable has registered itself on initial render.
2. After the \@Consume decorated variable is updated in all owning child components, all system components (**elementId**) that depend on the \@Consume decorated variable are updated. In this way, changes to the \@Provide decorated variable are synchronized to the \@Consume decorated variable.
3. When the \@Consume decorated variable is updated:
As can be learned from the initial render procedure, the \@Consume decorated variable holds an instance of \@Provide. After the \@Consume decorated variable is updated, the update method of \@Provide is called to synchronize the changes to \@Provide.
## Application Scenarios
The following example shows the two-way synchronization between \@Provide and \@Consume decorated variables. When the buttons in the **CompA** and **CompD** components are clicked, the changes to **reviewVotes** are synchronized to the **CompA** and **CompD** components.
```ts
@Component
struct CompD {
// The @Consume decorated variable is bound to the @Provide decorated variable in its ancestor component CompA under the same attribute name.
@Consume reviewVotes: number;
build() {
Column() {
Text(`reviewVotes(${this.reviewVotes})`)
Button(`reviewVotes(${this.reviewVotes}), give +1`)
.onClick(() => this.reviewVotes += 1)
}
.width('50%')
}
}
@Component
struct CompC {
build() {
Row({ space: 5 }) {
CompD()
CompD()
}
}
}
@Component
struct CompB {
build() {
CompC()
}
}
@Entry
@Component
struct CompA {
// @Provide decorated variable reviewVotes is provided by the entry component CompA.
@Provide reviewVotes: number = 0;
build() {
Column() {
Button(`reviewVotes(${this.reviewVotes}), give +1`)
.onClick(() => this.reviewVotes += 1)
CompB()
}
}
}
```
# ForEach: Rendering of Repeated Content
**ForEach** enables repeated content based on array-type data.
## API Description
```ts
ForEach(
arr: any[],
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)
```
| Name | Type | Mandatory | Description |
| ------------- | ---------------------------------------- | ---- | ---------------------------------------- |
| arr | Array | Yes | An array, which can be empty, in which case no child component is created. The functions that return array-type values are also allowed, for example, **arr.slice (1, 3)**. The set functions cannot change any state variables including the array itself, such as **Array.splice**, **Array.sort**, and **Array.reverse**.|
| itemGenerator | (item:&nbsp;any,&nbsp;index?:&nbsp;number)&nbsp;=&gt;&nbsp;void | Yes | A lambda function used to generate one or more child components for each data item in an array. Each component and its child component list must be contained in parentheses.<br>**NOTE**<br>- The type of the child component must be the one allowed inside the parent container component of **ForEach**. For example, a **\<LitemItem>** child component is allowed only when the parent container component of **ForEach** is **\<List>**.<br>- The child build function is allowed to return an **if** or another **ForEach**. **ForEach** can be placed inside **if**.<br>- The optional **index** parameter should only be specified in the function signature if used in its body.|
| keyGenerator | (item:&nbsp;any,&nbsp;index?:&nbsp;number)&nbsp;=&gt;&nbsp;string | No | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **ForEach**.<br>**NOTE**<br>- Two items inside the same array must never work out the same ID.<br>- If **index** is not used, an item's ID must not change when the item's position within the array changes. However, if **index** is used, then the ID must change when the item is moved within the array.<br>- When an item is replaced by a new one (with a different value), the ID of the replaced and the ID of the new item must be different.<br>- When **index** is used in the build function, it should also be used in the ID generation function.<br>- The ID generation function is not allowed to mutate any component state.|
## Restrictions
- **ForEach** must be used in container components.
- The type of the child component must be the one allowed inside the parent container component of **ForEach**.
- The **itemGenerator** function can contain an **if/else** statement, and an **if/else** statement can contain **ForEach**.
- The call sequence of **itemGenerator** functions may be different from that of the data items in the array. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. For example, the following example may not run properly:
```ts
ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
item => Text(`${item.i}. item.data.label`),
item => item.data.id.toString())
```
## Recommendations
- Make no assumption on the order of item build functions. The execution order may not be the order of items inside the array.
- Make no assumption either when items are built the first time. Currently initial render of **ForEach** builds all array items when the \@Component decorated component is rendered at the first time, but future framework versions might change this behaviour to a more lazy behaviour.
- Using the **index** parameter has severe negative impact on the UI update performance. Avoid using this parameter whenever possible.
- If the **index** parameter is used in the item generator function, it must also be used in the item index function. Otherwise, the framework counts in the index when generating the ID. By default, the index is concatenated to the end of the ID.
## Application Scenarios
### Simple ForEach Example
This example creates three **\<Text>** and **\<Divide>** components based on the **arr** data.
```ts
@Entry
@Component
struct MyComponent {
@State arr: number[] = [10, 20, 30];
build() {
Column({ space: 5 }) {
Button('Reverse Array')
.onClick(() => {
this.arr.reverse();
})
ForEach(this.arr, (item: number) => {
Text(`item value: ${item}`).fontSize(18)
Divider().strokeWidth(2)
}, (item: number) => item.toString())
}
}
}
```
### Complex ForEach Example
```ts
@Component
struct CounterView {
label: string;
@State count: number = 0;
build() {
Button(`${this.label}-${this.count} click +1`)
.width(300).height(40)
.backgroundColor('#a0ffa0')
.onClick(() => {
this.count++;
})
}
}
@Entry
@Component
struct MainView {
@State arr: number[] = Array.from(Array(10).keys()); // [0.,.9]
nextUnused: number = this.arr.length;
build() {
Column() {
Button(`push new item`)
.onClick(() => {
this.arr.push(this.nextUnused++)
})
.width(300).height(40)
Button(`pop last item`)
.onClick(() => {
this.arr.pop()
})
.width(300).height(40)
Button(`prepend new item (unshift)`)
.onClick(() => {
this.arr.unshift(this.nextUnused++)
})
.width(300).height(40)
Button(`remove first item (shift)`)
.onClick(() => {
this.arr.shift()
})
.width(300).height(40)
Button(`insert at pos ${Math.floor(this.arr.length / 2)}`)
.onClick(() => {
this.arr.splice(Math.floor(this.arr.length / 2), 0, this.nextUnused++);
})
.width(300).height(40)
Button(`remove at pos ${Math.floor(this.arr.length / 2)}`)
.onClick(() => {
this.arr.splice(Math.floor(this.arr.length / 2), 1);
})
.width(300).height(40)
Button(`set at pos ${Math.floor(this.arr.length / 2)} to ${this.nextUnused}`)
.onClick(() => {
this.arr[Math.floor(this.arr.length / 2)] = this.nextUnused++;
})
.width(300).height(40)
ForEach(this.arr,
(item) => {
CounterView({ label: item.toString() })
},
(item) => item.toString()
)
}
}
}
```
**MainView** has an \@State decorated array of numbers. Adding, deleting, and replacing array items are observed mutation events. Whenever one of these events occurs, **ForEach** in **MainView** is updated.
The item index function creates a unique and persistent ID for each array item. The ArkUI framework uses this ID to determine whether an item in the array changes. As long as the ID is the same, the item value is assumed to remain unchanged, but its index position may have changed. For this mechanism to work, different array items cannot have the same ID.
Using the item ID obtained through computation, the framework can distinguish newly created, removed, and retained array items.
1. The framework removes UI components for a removed array item.
2. The framework executes the item build function only for newly added array items.
3. The framework does not execute the item build function for retained array items. If the item index within the array has changed, the framework will just move its UI components according to the new order, but will not update that UI components.
The item index function is recommended, but optional. The generated IDs must be unique. This means that the same ID must not be computed for any two items within the array. The ID must be different even if the two array items have the same value.
If the array item value changes, the ID must be changed.
As mentioned earlier, the ID generation function is optional. The following example shows **ForEach** without the item index function:
```ts
ForEach(this.arr,
(item) => {
CounterView({ label: item.toString() })
}
)
```
If no item ID function is provided, the framework attempts to intelligently detect array changes when updating **ForEach**. However, it might remove child components and re-execute the item build function for array items that have been moved (with indexes changed). In the preceding example, this changes the application behavior in regard to the **counter** state of **CounterView**. When a new **CounterView** instance is created, the value of **counter** is initialized with **0**.
### Example of ForEach Using \@ObjectLink
If your application needs to preserve the state of repeated child components, you can use \@ObjectLink to enable the state to be "pushed up the component tree."
```ts
let NextID: number = 0;
@Observed
class MyCounter {
public id: number;
public c: number;
constructor(c: number) {
this.id = NextID++;
this.c = c;
}
}
@Component
struct CounterView {
@ObjectLink counter: MyCounter;
label: string = 'CounterView';
build() {
Button(`CounterView [${this.label}] this.counter.c=${this.counter.c} +1`)
.width(200).height(50)
.onClick(() => {
this.counter.c += 1;
})
}
}
@Entry
@Component
struct MainView {
@State firstIndex: number = 0;
@State counters: Array<MyCounter> = [new MyCounter(0), new MyCounter(0), new MyCounter(0),
new MyCounter(0), new MyCounter(0)];
build() {
Column() {
ForEach(this.counters.slice(this.firstIndex, this.firstIndex + 3),
(item) => {
CounterView({ label: `Counter item #${item.id}`, counter: item })
},
(item) => item.id.toString()
)
Button(`Counters: shift up`)
.width(200).height(50)
.onClick(() => {
this.firstIndex = Math.min(this.firstIndex + 1, this.counters.length - 3);
})
Button(`counters: shift down`)
.width(200).height(50)
.onClick(() => {
this.firstIndex = Math.max(0, this.firstIndex - 1);
})
}
}
}
```
When the value of **firstIndex** is increased, **ForEach** within **Mainview** is updated, and the **CounterView** child component associated with the item ID **firstIndex-1** is removed. For the array item whose ID is **firstindex + 3**, a new** CounterView** child component instance is created. The value of the state variable **counter** of the **CounterView** child component is preserved by the **Mainview** parent component. Therefore, **counter** is not rebuilt when the **CounterView** child component instance is rebuilt.
> **NOTE**
>
> The most common mistake application developers make in connection with **ForEach** is that the ID generation function returns the same value for two array items, especially in the Array\<number> scenario.
### Nested Use of ForEach
While nesting **ForEach** inside another **ForEach** in the same component is allowed, it is not recommended. It is better to split the component into two and have each build function include just one ForEach. The following is a poor example of nested use of **ForEach**.
```ts
class Month {
year: number;
month: number;
days: number[];
constructor(year: number, month: number, days: number[]) {
this.year = year;
this.month = month;
this.days = days;
}
}
@Component
struct CalendarExample {
// Simulate with six months.
@State calendar : Month[] = [
new Month(2020, 1, [...Array(31).keys()]),
new Month(2020, 2, [...Array(28).keys()]),
new Month(2020, 3, [...Array(31).keys()]),
new Month(2020, 4, [...Array(30).keys()]),
new Month(2020, 5, [...Array(31).keys()]),
new Month(2020, 6, [...Array(30).keys()])
]
build() {
Column() {
Button() {
Text('next month')
}.onClick(() => {
this.calendar.shift()
this.calendar.push(new Month(year: 2020, month: 7, days: [...Array(31).keys()]))
})
ForEach(this.calendar,
(item: Month) => {
ForEach(item.days,
(day : number) => {
// Build a date block.
},
(day : number) => day.toString()
)// Inner ForEach
},
(item: Month) => (item.year * 12 + item.month).toString() // This field is used together with the year and month as the unique ID of the month.
)// Outer ForEach
}
}
}
```
The preceding example has two issues:
1. The code readability is poor.
2. For a data structure of months and days of a year, the framework cannot observe the attribute changes to **Month** objects, including any changes to the **days** array. As a result, the inner **ForEach** will not update the date.
The recommended application design is to split **Calendar** into **Year**, **Month**, and **Day** child components. Define a **Day** model class to hold information about a day and decorate the class with \@Observed. The **DayView** component uses an \@ObjectLink decorated variable to link to the data about a day. Perform the same operations on the **MonthView** and **Month** model classes.
### Example of Using the Optional index Parameter in ForEach
You can use the optional **index** parameter in item build and ID generation functions.
```ts
@Entry
@Component
struct ForEachWithIndex {
@State arr: number[] = [4, 3, 1, 5];
build() {
Column() {
ForEach(this.arr,
(it, indx) => {
Text(`Item: ${indx} - ${it}`)
},
(it, indx) => {
return `${indx} - ${it}`
}
)
}
}
}
```
The correct construction of the ID generation function is essential. When **index** is used in the item generation function, it should also be used in the ID generation function to produce unique IDs and an ID for given source array item that changes when its index position within the array changes.
This example also illustrates that the **index** parameter can cause significant performance degradation. If an item is moved in the source array without modification, the dependent UI still requires rebuilding because of the changed index. For example, with the use of index sorting, the array only requires the unmodified child UI node of **ForEach** to be moved to the correct slot, which is a lightweight operation for the framework. When **index** is used, all child UI nodes need to be rebuilt, which is much more heavy weight.
# if/else: Conditional Rendering
ArkTS provides conditional rendering. Use the **if**, **else**, and **else if** statements to enable your application to display different content based on the condition or state.
> **NOTE**
>
> Since API version 9, this API is supported in ArkTS widgets.
## Rules of Use
- The **if**, **else**, and **else if** statements are supported.
- The conditional statements following **if** and **else if** can use state variables.
- Use of the conditional statements within a container component is allowed for building different child components.
- Conditional statements are "transparent" when it comes to the parent-child relationship of components. Rules about permissible child components must be followed when there is one or more **if** statements between the parent and child components.
- The build function inside each branch must follow the special rules for build functions. Each of such build functions must create one or more components. An empty build function that creates no components will result in a syntax error.
- Some container components impose restrictions on the type or number of child components. When conditional statements are used in such components, these restrictions also apply to the components created in conditional statements. For example, when a conditional statement is used in the **\<Grid>** container component, whose child components can only be **\<GridItem>**, only the **\<GridItem>** component can be used in the conditional statement.
## Update Mechanism
A conditional statement updates whenever a state variable used inside the **if** condition or the **else if** condition changes. Specifically:
1. The conditional statement re-evaluates the conditions. If the evaluation of the conditions changes, steps 2 and 3 are performed. Otherwise, no follow-up operation is required.
2. The framework removes all child components that have been built.
3. The framework executes the build function of the branch again to add the generated child component to its parent component. If an applicable **else** branch is missing, no new build function will be executed.
A condition can include Typescript expressions. As for any expression inside build functions, such an expression must not change any application state.
## Application Scenarios
### Using if for Conditional Rendering
```ts
@Entry
@Component
struct ViewA {
@State count: number = 0;
build() {
Column() {
Text(`count=${this.count}`)
if (this.count > 0) {
Text(`count is positive`)
.fontColor(Color.Green)
}
Button('increase count')
.onClick(() => {
this.count++;
})
Button('decrease count')
.onClick(() => {
this.count--;
})
}
}
}
```
Each branch of the **if** statement includes a build function. Each of such build functions must create one or more components. On initial render, **if** will execute a build function and add the generated child component to its parent component.
**if** updates whenever a state variable used inside the **if** condition or the **else if** condition changes and re-evaluates the conditions. If the evaluation of the conditions changes, it means that another branch of **if** needs to be built. In this case, the ArkUI framework will:
1. Remove all previously rendered components (of the earlier branch).
2. Execute the build function of the branch and add the generated child component to its parent component.
In the preceding example, if **count** increases from 0 to 1, then, **if** updates, the condition **count > 0** is re-evaluated, and the evaluation result changes from **false** to **true**. Therefore, the positive branch build function will be executed, which creates a **\<Text>** component and adds it to the **\<Column>** parent component. If **count** changes back to 0 later, then, the **\<Text>** component will be removed from the **\<Column>** component. Since there is no **else** branch, no new build function will be executed.
### if ... else ... and Child Component State
This example involves **if...** **else...** and a child component with an \@State decorated variable.
```ts
@Component
struct CounterView {
@State counter: number = 0;
label: string = 'unknown';
build() {
Row() {
Text(`${this.label}`)
Button(`counter ${this.counter} +1`)
.onClick(() => {
this.counter += 1;
})
}
}
}
@Entry
@Component
struct MainView {
@State toggle: boolean = true;
build() {
Column() {
if (this.toggle) {
CounterView({ label: 'CounterView #positive' })
} else {
CounterView({ label: 'CounterView #negative' })
}
Button(`toggle ${this.toggle}`)
.onClick(() => {
this.toggle = !this.toggle;
})
}
}
}
```
On first render, the **CounterView** (label: **'CounterView \#positive'**) child component is created. This child component carries the \@State decorated variable **counter**. When the **CounterView.counter** state variable is updated, the **CounterView** (label: **'CounterView \#positive'**) child component is re-rendered, with its state variable value preserved. When the value of the **MainView.toggle** state variable changes to **false**, the **if** statement inside the **MainView** parent component gets updated, and subsequently the **CounterView** (label: **'CounterView \#positive'**) child component will be removed. At the same time, a new **CounterView** (label: **'CounterView \#negative'**) child component will be created. Its own **counter** state variable is set to the initial value **0**.
> **NOTE**
>
> **CounterView** (label: **'CounterView \#positive'**) and **CounterView** (label: **'CounterView \#negative'**) are two distinct instances of the same custom component. When the **if** branch changes, there is no updating of an existing child component and no preservation of state.
The following example shows the required modifications if the value of **counter** be preserved when the **if** condition changes:
```
@Component
struct CounterView {
@Link counter: number;
label: string = 'unknown';
build() {
Row() {
Text(`${this.label}`)
Button(`counter ${this.counter} +1`)
.onClick(() => {
this.counter += 1;
})
}
}
}
@Entry
@Component
struct MainView {
@State toggle: boolean = true;
@State counter: number = 0;
build() {
Column() {
if (this.toggle) {
CounterView({ counter: $counter, label: 'CounterView #positive' })
} else {
CounterView({ counter: $counter, label: 'CounterView #negative' })
}
Button(`toggle ${this.toggle}`)
.onClick(() => {
this.toggle = !this.toggle;
})
}
}
}
```
Here, the \@State decorated variable **counter** is owned by the parent component. Therefore, it is not destroyed when a **CounterView** component instance is removed. The **CounterView** component refers to the state by an \@Link decorator. This technique is sometimes referred to as "pushing up the state in the component tree." The state must be moved from a child to its parent (or parent of parent) to avoid losing it when the conditional content (or repeated content) is destroyed.
### Nested if Statements
The nesting of **if** statements makes no difference to the rule about the parent component.
```ts
@Entry
@Component
struct CompA {
@State toggle: boolean = false;
@State toggleColor: boolean = false;
build() {
Column() {
Text('Before')
.fontSize(15)
if (this.toggle) {
Text('Top True, positive 1 top')
.backgroundColor('#aaffaa').fontSize(20)
// Inner if statement
if (this.toggleColor) {
Text('Top True, Nested True, positive COLOR Nested ')
.backgroundColor('#00aaaa').fontSize(15)
} else {
Text('Top True, Nested False, Negative COLOR Nested ')
.backgroundColor('#aaaaff').fontSize(15)
}
} else {
Text('Top false, negative top level').fontSize(20)
.backgroundColor('#ffaaaa')
if (this.toggleColor) {
Text('positive COLOR Nested ')
.backgroundColor('#00aaaa').fontSize(15)
} else {
Text('Negative COLOR Nested ')
.backgroundColor('#aaaaff').fontSize(15)
}
}
Text('After')
.fontSize(15)
Button('Toggle Outer')
.onClick(() => {
this.toggle = !this.toggle;
})
Button('Toggle Inner')
.onClick(() => {
this.toggleColor = !this.toggleColor;
})
}
}
}
```
# LazyForEach: Lazy Data Loading
**LazyForEach** iterates over provided data sources and creates corresponding components during each iteration. When **LazyForEach** is used in a scrolling container, the framework creates components as required within the visible area of the scrolling container. When a component is out of the visible area, the framework destroys and reclaims the component to reduce memory usage.
## API Description
```ts
LazyForEach(
dataSource: IDataSource, // Data source to iterate over
itemGenerator: (item: any) => void, // Child component generation function
keyGenerator?: (item: any) => string // (Optional). ID generation function
): void
interface IDataSource {
totalCount(): number; // Get total count of data
getData(index: number): any; // Get single data by index
registerDataChangeListener(listener: DataChangeListener): void; // Register listener to listening data changes
unregisterDataChangeListener(listener: DataChangeListener): void; // Unregister listener
}
interface DataChangeListener {
onDataReloaded(): void; // Called while data reloaded
onDataAdd(index: number): void; // Called while single data added
onDataMove(from: number, to: number): void; // Called while single data moved
onDataDelete(index: number): void; // Called while single data deleted
onDataChange(index: number): void; // Called while single data changed
}
```
**Parameters**
| Name | Type | Mandatory | Description |
| ------------- | --------------------------------------- | ---- | ---------------------------------------- |
| dataSource | IDataSource | Yes | **LazyForEach** data source. You need to implement related APIs. |
| itemGenerator | (item:&nbsp;any)&nbsp;=&gt;&nbsp;void | Yes | Child component generation function, which generates a child component for each data item in the array.<br>**NOTE**<br>The function body of **itemGenerator** must be included in braces {...}. **itemGenerator** can and must generate only one child component for each iteration. The **if** statement is allowed in **itemGenerator**, but you must ensure that each branch of the **if** statement creates a child component of the same type. **ForEach** and **LazyForEach** statements are not allowed in **itemGenerator**.|
| keyGenerator | (item:&nbsp;any)&nbsp;=&gt;&nbsp;string | No | ID generation function, which generates a unique and fixed ID for each data item in the data source. This ID must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the ID of the new item must be different from that of the replaced item. This ID generation function is optional. However, for performance reasons, it is strongly recommended that the ID generation function be provided, so that the framework can better identify array changes. For example, if no ID generation function is provided, a reverse of an array will result in rebuilding of all nodes in **LazyForEach**.<br>**NOTE**<br>The ID generated for each data item in the data source must be unique.|
## Description of IDataSource
```ts
interface IDataSource {
totalCount(): number;
getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void;
}
```
| Declaration | Parameter Type | Description |
| ---------------------------------------- | ------------------ | ------------------------------------- |
| totalCount():&nbsp;number | - | Obtains the total number of data records. |
| getData(index:&nbsp;number):&nbsp;any | number | Obtains the data record corresponding to the specified index.<br>**index**: index of the data record to obtain.|
| registerDataChangeListener(listener:DataChangeListener):&nbsp;void | DataChangeListener | Registers a listener for data changes.<br>**listener**: listener for data changes. |
| unregisterDataChangeListener(listener:DataChangeListener):&nbsp;void | DataChangeListener | Deregisters the listener for data changes.<br>**listener**: listener for data changes. |
## Description of DataChangeListener
```ts
interface DataChangeListener {
onDataReloaded(): void;
onDataAdded(index: number): void;
onDataMoved(from: number, to: number): void;
onDataDeleted(index: number): void;
onDataChanged(index: number): void;
onDataAdd(index: number): void;
onDataMove(from: number, to: number): void;
onDataDelete(index: number): void;
onDataChange(index: number): void;
}
```
| Declaration | Parameter Type | Description |
| ---------------------------------------- | -------------------------------------- | ---------------------------------------- |
| onDataReloaded():&nbsp;void | - | Invoked when all data is reloaded. |
| onDataAdded(index:&nbsp;number):void | number | Invoked when data is added to the position indicated by the specified index.<br>**index**: index of the position where data is added. |
| onDataMoved(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void | from:&nbsp;number,<br>to:&nbsp;number | Invoked when data is moved.<br>**from**: original position of data; **to**: target position of data.<br>**NOTE**<br>The ID must remain unchanged before and after data movement. If the ID changes, APIs for deleting and adding data must be called.|
| onDataChanged(index:&nbsp;number):&nbsp;void | number | Invoked when data in the position indicated by the specified index is changed.<br>**index**: listener for data changes. |
| onDataAdd(index:&nbsp;number):&nbsp;void | number | Invoked when data is added to the position indicated by the specified index.<br>**index**: index of the position where data is added. |
| onDataMove(from:&nbsp;number,&nbsp;to:&nbsp;number):&nbsp;void | from:&nbsp;number,<br>to:&nbsp;number | Invoked when data is moved.<br>**from**: original position of data; **to**: target position of data.<br>**NOTE**<br>The ID must remain unchanged before and after data movement. If the ID changes, APIs for deleting and adding data must be called.|
| onDataChanged(index:&nbsp;number):&nbsp;void | number | Invoked when data in the position indicated by the specified index is changed.<br>**index**: index of the position where data is changed.|
## Restrictions
- **LazyForEach** must be used in the container component. Only the **\<List>**, **\<Grid>**, and **\<Swiper>** components support lazy loading (that is, only the visible part and a small amount of data before and after the visible part are loaded for caching). For other components, all data is loaded at the same time.
- **LazyForEach** must create one and only one child component in each iteration.
- The generated child components must be allowed in the parent container component of **LazyForEach**.
- **LazyForEach** can be included in an **if/else** statement, and can also contain such a statement.
- The ID generation function must generate a unique value for each piece of data. If the IDs are the same, the framework ignores the UI components with the same key value. As a result, these UI components cannot be displayed in the parent container.
- **LazyForEach** must be updated using a **DataChangeListener** object. When the first parameter **dataSource** uses a state variable, a state variable change does not trigger the UI update of **LazyForEach**.
- For better rendering performance, when the **onDataChange** API of the **DataChangeListener** object is used to update the UI, an ID different from the original one needs to be generated to trigger component re-rendering.
- The call sequence of **itemGenerator** functions may be different from the order of the data items in the data source. Therefore, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. For example, the following example may not run properly:
```ts
LazyForEach(dataSource,
item => Text(`${item.i}. item.data.label`),
item => item.data.id.toString())
```
## Example
```ts
// Basic implementation of IDataSource to handle data listener
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = [];
public totalCount(): number {
return 0;
}
public getData(index: number): any {
return undefined;
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener');
this.listeners.push(listener);
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener');
this.listeners.splice(pos, 1);
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded();
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index);
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index);
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index);
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to);
})
}
}
class MyDataSource extends BasicDataSource {
private dataArray: string[] = ['/path/image0', '/path/image1', '/path/image2', '/path/image3'];
public totalCount(): number {
return this.dataArray.length;
}
public getData(index: number): any {
return this.dataArray[index];
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data);
this.notifyDataAdd(index);
}
public pushData(data: string): void {
this.dataArray.push(data);
this.notifyDataAdd(this.dataArray.length - 1);
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource();
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Image(item).width('30%').height(50)
Text(item).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
this.data.pushData('/path/image' + this.data.totalCount());
})
}, item => item)
}
}
}
```
# Rendering Control Overview
ArkUI uses the **build()** function of [custom components](arkts-create-custom-components.md) and declarative UI description statements in the [@builder decorator](arkts-builder.md) to build the corresponding UI. In declarative description statements, you can use rendering control statements in addition to system components to accelerate UI construction. These rendering control statements include conditional statements that control whether components are displayed, rendering statements for repeated content that quickly generate components based on array data, and lazy loading statements for scenarios involving a large amount of data.
# Rendering Control
ArkTS provides conditional rendering and loop rendering. Conditional rendering can render state-specific UI content based on the application status. Loop rendering iteratively obtains data from the data source and creates the corresponding component during each iteration.
## Conditional Rendering
Use **if/else** for conditional rendering.
> **NOTE**
>
> - State variables can be used in the **if/else** statement.
>
> - The **if/else** statement can be used to implement rendering of child components.
>
> - The **if/else** statement must be used in container components.
>
> - Some container components limit the type or number of subcomponents. When **if/else** is placed in these components, the limitation applies to components created in **if/else** statements. For example, when **if/else** is used in the **\<Grid>** container component, whose child components can only be **\<GridItem>**, only the **\<GridItem>** component can be used in the **if/else** statement.
```ts
Column() {
if (this.count < 0) {
Text('count is negative').fontSize(14)
} else if (this.count % 2 === 0) {
Text('count is even').fontSize(14)
} else {
Text('count is odd').fontSize(14)
}
}
```
## Loop Rendering
You can use **ForEach** to obtain data from arrays and create components for each data item.
```ts
ForEach(
arr: any[],
itemGenerator: (item: any, index?: number) => void,
keyGenerator?: (item: any, index?: number) => string
)
```
Since API version 9, this API is supported in ArkTS widgets.
**Parameters**
| Name | Type | Mandatory| Description |
| ------------- | ------------------------------------- | ---- | ------------------------------------------------------------ |
| arr | any[] | Yes | An array, which can be empty, in which case no child component is created. The functions that return array-type values are also allowed, for example, **arr.slice (1, 3)**. The set functions cannot change any state variables including the array itself, such as **Array.splice**, **Array.sort**, and **Array.reverse**.|
| itemGenerator | (item: any, index?: number) => void | Yes | A lambda function used to generate one or more child components for each data item in an array. A single child component or a list of child components must be included in parentheses.|
| keyGenerator | (item: any, index?: number) => string | No | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key value must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the key value of the new item must be different from that of the existing item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **ForEach**.|
> **NOTE**
>
> - **ForEach** must be used in container components.
>
> - The generated child components should be allowed in the parent container component of **ForEach**.
>
> - The **itemGenerator** function can contain an **if/else** statement, and an **if/else** statement can contain **ForEach**.
>
> - The call sequence of **itemGenerator** functions may be different from that of the data items in the array. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. The following is an example of incorrect usage:
>
> ```ts
> ForEach(anArray.map((item1, index1) => { return { i: index1 + 1, data: item1 }; }),
> item => Text(`${item.i}. item.data.label`),
> item => item.data.id.toString())
> ```
## Example
```ts
// xxx.ets
@Entry
@Component
struct MyComponent {
@State arr: number[] = [10, 20, 30]
build() {
Column({ space: 5 }) {
Button('Reverse Array')
.onClick(() => {
this.arr.reverse()
})
ForEach(this.arr, (item: number) => {
Text(`item value: ${item}`).fontSize(18)
Divider().strokeWidth(2)
}, (item: number) => item.toString())
}
}
}
```
![forEach1](figures/forEach1.gif)
## Lazy Loading
You can use **LazyForEach** to iterate over provided data sources and create corresponding components during each iteration.
```ts
LazyForEach(
dataSource: IDataSource,
itemGenerator: (item: any) => void,
keyGenerator?: (item: any) => string
): void
interface IDataSource {
totalCount(): number;
getData(index: number): any;
registerDataChangeListener(listener: DataChangeListener): void;
unregisterDataChangeListener(listener: DataChangeListener): void;
}
interface DataChangeListener {
onDataReloaded(): void;
onDataAdd(index: number): void;
onDataMove(from: number, to: number): void;
onDataDelete(index: number): void;
onDataChange(index: number): void;
}
```
**Parameters**
| Name | Type | Mandatory| Description |
| ------------- | --------------------- | ---- | ------------------------------------------------------------ |
| dataSource | IDataSource | Yes | Object used to implement the **IDataSource** API. You need to implement related APIs. |
| itemGenerator | (item: any, index?: number) => void | Yes | A lambda function used to generate one or more child components for each data item in an array. A single child component or a list of child components must be included in parentheses.|
| keyGenerator | (item: any, index?: number) => string | No | An anonymous function used to generate a unique and fixed key value for each data item in an array. This key value must remain unchanged for the data item even when the item is relocated in the array. When the item is replaced by a new item, the key value of the new item must be different from that of the existing item. This key-value generator is optional. However, for performance reasons, it is strongly recommended that the key-value generator be provided, so that the development framework can better identify array changes. For example, if no key-value generator is provided, a reverse of an array will result in rebuilding of all nodes in **LazyForEach**.|
### Description of IDataSource
| Name | Description |
| ------------------------------------------------------------ | ---------------------- |
| totalCount(): number | Obtains the total number of data records. |
| getData(index: number): any | Obtains the data corresponding to the specified index. |
| registerDataChangeListener(listener:DataChangeListener): void | Registers a listener for data changes.|
| unregisterDataChangeListener(listener:DataChangeListener): void | Deregisters a listener for data changes.|
### Description of DataChangeListener
| Name | Description |
| -------------------------------------------------------- | -------------------------------------- |
| onDataReloaded(): void | Invoked when all data is reloaded. |
| onDataAdded(index: number): void<sup>deprecated</sup> | Invoked when data is added to the position indicated by the specified index. This API is deprecated since API version 8. You are advised to use **onDataAdd**. |
| onDataMoved(from: number, to: number): void<sup>deprecated</sup> | Invoked when data is moved from the **from** position to the **to** position. This API is deprecated since API version 8. You are advised to use **onDataMove**.|
| onDataDeleted(index: number): void<sup>deprecated</sup> | Invoked when data is deleted from the position indicated by the specified index. This API is deprecated since API version 8. You are advised to use **onDataDelete**. |
| onDataChanged(index: number): void<sup>deprecated</sup> | Invoked when data in the position indicated by the specified index is changed. This API is deprecated since API version 8. You are advised to use **onDataChange**. |
| onDataAdd(index: number): void<sup>8+</sup> | Invoked when data is added to the position indicated by the specified index. |
| onDataMove(from: number, to: number): void<sup>8+</sup> | Invoked when data is moved from the **from** position to the **to** position.|
| onDataDelete(index: number): void<sup>8+</sup> | Invoked when data is deleted from the position indicated by the specified index. |
| onDataChange(index: number): void<sup>8+</sup> | Invoked when data in the position indicated by the specified index is changed. |
## Example
```ts
// xxx.ets
class BasicDataSource implements IDataSource {
private listeners: DataChangeListener[] = []
public totalCount(): number {
return 0
}
public getData(index: number): any {
return undefined
}
registerDataChangeListener(listener: DataChangeListener): void {
if (this.listeners.indexOf(listener) < 0) {
console.info('add listener')
this.listeners.push(listener)
}
}
unregisterDataChangeListener(listener: DataChangeListener): void {
const pos = this.listeners.indexOf(listener);
if (pos >= 0) {
console.info('remove listener')
this.listeners.splice(pos, 1)
}
}
notifyDataReload(): void {
this.listeners.forEach(listener => {
listener.onDataReloaded()
})
}
notifyDataAdd(index: number): void {
this.listeners.forEach(listener => {
listener.onDataAdd(index)
})
}
notifyDataChange(index: number): void {
this.listeners.forEach(listener => {
listener.onDataChange(index)
})
}
notifyDataDelete(index: number): void {
this.listeners.forEach(listener => {
listener.onDataDelete(index)
})
}
notifyDataMove(from: number, to: number): void {
this.listeners.forEach(listener => {
listener.onDataMove(from, to)
})
}
}
class MyDataSource extends BasicDataSource {
// Initialize the data list.
private dataArray: string[] = ['/path/image0.png', '/path/image1.png', '/path/image2.png', '/path/image3.png']
public totalCount(): number {
return this.dataArray.length
}
public getData(index: number): any {
return this.dataArray[index]
}
public addData(index: number, data: string): void {
this.dataArray.splice(index, 0, data)
this.notifyDataAdd(index)
}
public pushData(data: string): void {
this.dataArray.push(data)
this.notifyDataAdd(this.dataArray.length - 1)
}
}
@Entry
@Component
struct MyComponent {
private data: MyDataSource = new MyDataSource()
build() {
List({ space: 3 }) {
LazyForEach(this.data, (item: string) => {
ListItem() {
Row() {
Image(item).width(50).height(50)
Text(item).fontSize(20).margin({ left: 10 })
}.margin({ left: 10, right: 10 })
}
.onClick(() => {
// The count increases by one each time the list is clicked.
this.data.pushData('/path/image' + this.data.totalCount() + '.png')
})
}, item => item)
}.height('100%').width('100%')
}
}
```
> **NOTE**
>
> - **LazyForEach** must be used in the container component. Currently, only the **\<List>**, **\<Grid>**, and **\<Swiper>** components support lazy loading (that is, only the visible part and a small amount of data before and after the visible part are loaded for caching). For other components, all data is loaded at a time.
>
> - **LazyForEach** must create one and only one child component in each iteration.
>
> - The generated child components must be the ones allowed in the parent container component of **LazyForEach**.
>
> - **LazyForEach** can be included in an **if/else** statement.
>
> - For the purpose of high-performance rendering, when the **onDataChange** method of the **DataChangeListener** object is used to update the UI, the component update is triggered only when the state variable is used in the child component created by **itemGenerator**.
>
> - The call sequence of **itemGenerator** functions may be different from that of the data items in the data source. During the development, do not assume whether or when the **itemGenerator** and **keyGenerator** functions are executed. The following is an example of incorrect usage:
>
> ```ts
> LazyForEach(dataSource,
> item => Text(`${item.i}. item.data.label`),
> item => item.data.id.toString())
> ```
![lazyForEach](figures/lazyForEach.gif)
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册