Before we begin, let's clarify the relationship between custom components and pages:
- Custom Component: A UI unit decorated with @Component, which can combine multiple system components to achieve UI reuse and can invoke the component's lifecycle.
- Page: The UI page of the application. It can consist of one or more custom components, with the custom component decorated with @Entry serving as the entry component of the page, i.e., the root node of the page. A page can have only one @Entry. Only components decorated with @Entry can invoke the page's lifecycle.
The page lifecycle, which is the lifecycle of the component decorated with @Entry, provides the following lifecycle interfaces:
- onPageShow: Triggered each time the page is displayed, including routing processes, application entering the foreground, etc.
- onPageHide: Triggered each time the page is hidden, including routing processes, application entering the background, etc.
- onBackPress: Triggered when the user clicks the back button.
The component lifecycle, which refers to the lifecycle of custom components generally decorated with @Component, provides the following lifecycle interfaces:
- aboutToAppear: This interface is called when the component is about to appear, specifically after creating a new instance of the custom component and before executing its build() function.
- onDidBuild: This interface is called after the component's build() function has completed. It is not recommended to change state variables or use functions like animateTo in the onDidBuild function, as this may lead to unstable UI behavior.
- aboutToDisappear: The aboutToDisappear function is executed before the custom component is destructed. It is not allowed to change state variables in the aboutToDisappear function, especially modifications to @Link variables may lead to unstable application behavior.
Creation and Rendering Process of Custom Components#
- Creation of Custom Components: Instances of custom components are created by the ArkUI framework.
- Initializing Member Variables of Custom Components: Member variables of custom components are initialized by passing parameters through local default values or constructors, in the order of their definition.
- If the developer defines aboutToAppear, the aboutToAppear method is executed.
- During the first rendering, the build method is executed to render system components. If the child component is a custom component, an instance of the custom component is created. During the first rendering process, the framework records the mapping relationship between state variables and components. When state variables change, it drives the related components to refresh.
- If the developer defines onDidBuild, the onDidBuild method is executed.
Re-rendering Custom Components#
When an event handler is triggered (for example, setting a click event, which triggers the click event) that changes the state variable, or when properties in LocalStorage / AppStorage change and cause bound state variables to change their values:
- The framework observes the change and will initiate a re-render.
- Based on the two maps held by the framework (from step 4 of the creation and rendering process of custom components), the framework knows which UI components are managed by that state variable and the corresponding update functions of these UI components. It executes the update functions of these UI components to achieve minimal updates.
Deleting Custom Components#
If the branch of an if component changes, or the number of elements in an array changes during a ForEach loop rendering, the component will be deleted:
- Before deleting the component, its aboutToDisappear lifecycle function will be called, marking that the node is about to be destroyed. The node deletion mechanism of ArkUI is: the backend node is directly removed from the component tree, the backend node is destroyed, and when the frontend node is dereferenced, if the frontend node has no references, it will be garbage collected by the JS virtual machine.
- The custom component and its variables will be deleted. If it has synchronous variables, such as @Link, @Prop, @StorageLink, they will be unregistered from the synchronous source.
It is not recommended to use async await in the aboutToDisappear lifecycle. If asynchronous operations (Promise or callback methods) are used in the aboutToDisappear lifecycle, the custom component will be retained in the closure of the Promise until the callback method is completed, which prevents garbage collection of the custom component.
The following example demonstrates the timing of lifecycle calls:
// Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor:string = "#FF007DFF";
// Only components decorated with @Entry can invoke the page's lifecycle
onPageShow() {
console.info('Index onPageShow');
}
// Only components decorated with @Entry can invoke the page's lifecycle
onPageHide() {
console.info('Index onPageHide');
}
// Only components decorated with @Entry can invoke the page's lifecycle
onBackPress() {
console.info('Index onBackPress');
this.btnColor ="#FFEE0606";
return true // Returning true indicates that the page handles the back logic itself, without routing; returning false indicates using the default routing back logic, treated as false if no return value is set
}
// Component lifecycle
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// Component lifecycle
onDidBuild() {
console.info('MyComponent onDidBuild');
}
// Component lifecycle
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild is true, create Child subcomponent, execute Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild is false, delete Child subcomponent, execute Child aboutToDisappear
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
this.showChild = false;
})
// push to page, execute onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/page' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// Component lifecycle
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// Component lifecycle
onDidBuild() {
console.info('[lifeCycle] Child onDidBuild');
}
// Component lifecycle
aboutToAppear() {
console.info('[lifeCycle] Child aboutToAppear')
}
build() {
Text(this.title)
.fontSize(50)
.margin(20)
.onClick(() => {
this.title = 'Hello ArkUI';
})
}
}
// page.ets
@Entry
@Component
struct page {
@State textColor: Color = Color.Black;
@State num: number = 0;
onPageShow() {
this.num = 5;
}
onPageHide() {
console.log("page onPageHide");
}
onBackPress() { // No return value is treated as false
this.textColor = Color.Grey;
this.num = 0;
}
aboutToAppear() {
this.textColor = Color.Blue;
}
build() {
Column() {
Text(`The value of num is: ${this.num}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.textColor)
.margin(20)
.onClick(() => {
this.num += 5;
})
}
.width('100%')
}
}
In the above example, the Index page contains two custom components, one is MyComponent decorated with @Entry, which is also the entry component of the page, i.e., the root node of the page; the other is Child, which is a child component of MyComponent. Only nodes decorated with @Entry can make page-level lifecycle methods effective, so the page lifecycle functions of the current Index page (onPageShow / onPageHide / onBackPress) are declared in MyComponent. MyComponent and its child component Child declare their respective component-level lifecycle functions (aboutToAppear / onDidBuild / aboutToDisappear).
-
The initialization process for a cold start of the application is: MyComponent aboutToAppear --> MyComponent build --> MyComponent onDidBuild --> Child aboutToAppear --> Child build --> Child onDidBuild --> Index onPageShow.
-
Clicking "delete Child", if the binding this.showChild becomes false, deletes the Child component, executing the Child aboutToDisappear method.
-
Clicking "push to next page" calls the router.pushUrl interface to jump to another page, hiding the current Index page, executing the page lifecycle Index onPageHide. Here, the router.pushUrl interface is called, and the Index page is hidden but not destroyed, so only onPageHide is called. After jumping to the new page, the lifecycle initialization process of the new page is executed.
-
If router.replaceUrl is called, the current Index page is destroyed, and the lifecycle process will change to: Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear. As mentioned earlier, the destruction of components is directly removing the subtree from the component tree, so the parent component's aboutToDisappear is called first, followed by the child component's aboutToDisappear, and then the lifecycle initialization process of the new page is executed.
-
Clicking the back button triggers the page lifecycle Index onBackPress, and triggering the return to a page will cause the current Index page to be destroyed.
-
Minimizing the application or sending the application to the background triggers Index onPageHide. The current Index page is not destroyed, so the component's aboutToDisappear will not be executed. When the application returns to the foreground, Index onPageShow is executed.
-
Exiting the application executes Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear.
Custom Components Listening to Page Lifecycle#
Using the unobtrusive ability to listen to page routing, it is possible to listen to the page's lifecycle within custom components.
// Index.ets
import { uiObserver, router, UIObserver } from '@kit.ArkUI';
@Entry
@Component
struct Index {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`Index onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`Index onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`this page is ${this.queryRouterPageInfo()?.pageId}`)
.fontSize(25)
Button("push self")
.onClick(() => {
router.pushUrl({
url: 'pages/Index'
})
})
Column() {
SubComponent()
}
}
}
}
@Component
struct SubComponent {
listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
if (info.pageId == routerInfo?.pageId) {
if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
console.log(`SubComponent onPageShow`);
} else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
console.log(`SubComponent onPageHide`);
}
}
}
aboutToAppear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.on('routerPageUpdate', this.listener);
}
aboutToDisappear(): void {
let uiObserver: UIObserver = this.getUIContext().getUIObserver();
uiObserver.off('routerPageUpdate', this.listener);
}
build() {
Column() {
Text(`SubComponent`)
}
}
}
This article is synchronized and updated to xLog by Mix Space Original link: http://www.sroxck.top/posts/harmony/arkts-life