• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Page and Custom Component Lifecycle
2
3
4Before we dive into the page and custom component lifecycle, it would be helpful to learn the relationship between custom components and pages.
5
6
7- Custom component: \@Component decorated UI unit, which can combine multiple built-in components for component reusability and invoke component lifecycle callbacks.
8
9- 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.
10
11
12The following lifecycle callbacks are provided for a page, that is, a custom component decorated with \@Entry:
13
14
15- [onPageShow](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpageshow): Invoked each time the page is displayed, for example, during page redirection or when the application is switched to the foreground.
16
17- [onPageHide](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onpagehide): Invoked each time the page is hidden, for example, during page redirection or when the application is switched to the background.
18
19- [onBackPress](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#onbackpress): Invoked when the user clicks the **Back** button.
20
21
22The following lifecycle callbacks are provided for a custom component decorated with \@Component:
23
24
25- [aboutToAppear](../reference/apis-arkui/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.
26
27- [onDidBuild](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#ondidbuild12): This API is called back after **build()** of the component is executed. In this phase, you can report tracking data without affecting the actual UI functions. Do not change state variables or use functions (such as **animateTo**) in **onDidBuild**. Otherwise, unstable UI performance may result.
28
29- [aboutToDisappear](../reference/apis-arkui/arkui-ts/ts-custom-component-lifecycle.md#abouttodisappear): Invoked when the custom component is about to be destroyed. Do not change state variables in the **aboutToDisappear** function as doing this can cause unexpected errors. For example, the modification of the **@Link** decorated variable may cause unstable application running.
30
31
32The following figure shows the lifecycle of a component (page) decorated with \@Entry.
33
34
35![en-us_image_0000001502372786](figures/en-us_image_0000001502372786.png)
36
37
38Based on the preceding figure, let's look into the creation, re-rendering, and deletion of a custom component.
39
40
41## Custom Component Creation and Rendering
42
431. Custom component creation: An instance of a custom component is created by the ArkUI framework.
44
452. Initialization of custom component member variables: The member variables are initialized with locally defined defaults or component constructor parameters. The initialization happens in the document order, which is the order in which the member variables are defined.
46
473. If defined, the component's **aboutToAppear** callback is invoked.
48
494. On initial render, the **build** function of the built-in component is executed for rendering. If the child component is a custom component, the rendering creates an instance of the child component. During initial render, the framework records the mapping between state variables and components. When a state variable changes, the framework drives the related components to update.
50
515. If defined, the component's **onDidBuild** callback is invoked.
52
53
54## Custom Component Re-rendering
55
56Re-rending of a custom component is triggered when its state variable is changed by an event handle (for example, when the click event is triggered) or by an update to the associated attribute in LocalStorage or AppStorage.
57
58
591. The framework observes the state variable change and marks the component for re-rendering.
60
612. Using the mapping tables – created in step 4 of the [custom component creation and rendering process](#custom-component-creation-and-rendering), the framework knows which UI components are managed by the state variable and which update functions are used for these UI components. With this knowledge, the framework executes only the update functions of these UI components.
62
63
64## Custom Component Deletion
65
66A custom component is deleted when the branch of the **if** statement or the number of arrays in **ForEach** changes.
67
68
691. 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:<br>(1) The backend component is directly removed from the component tree and destroyed.<br>(2) The reference to the destroyed component is released from the frontend components.<br>(3) The JS Engine garbage collects the destroyed component.
70
712. 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).
72
73
74Use 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 is executed, which prevents the component from being garbage collected.
75
76
77The following example shows when the lifecycle callbacks are invoked:
78
79
80
81```ts
82// Index.ets
83import { router } from '@kit.ArkUI';
84
85@Entry
86@Component
87struct MyComponent {
88  @State showChild: boolean = true;
89  @State btnColor: string = "#FF007DFF";
90
91  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
92  onPageShow() {
93    console.info('Index onPageShow');
94  }
95
96  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
97  onPageHide() {
98    console.info('Index onPageHide');
99  }
100
101  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
102  onBackPress() {
103    console.info('Index onBackPress');
104    this.btnColor = "#FFEE0606";
105    return true // The value true means that the page executes its own return logic, and false means that the default return logic is used.
106  }
107
108  // Component lifecycle
109  aboutToAppear() {
110    console.info('MyComponent aboutToAppear');
111  }
112
113  // Component lifecycle
114  onDidBuild() {
115    console.info('MyComponent onDidBuild');
116  }
117
118  // Component lifecycle
119  aboutToDisappear() {
120    console.info('MyComponent aboutToDisappear');
121  }
122
123  build() {
124    Column() {
125      // When this.showChild is true, create the Child child component and invoke Child aboutToAppear.
126      if (this.showChild) {
127        Child()
128      }
129      Button('delete Child')
130        .margin(20)
131        .backgroundColor(this.btnColor)
132        .onClick(() => {
133        // When this.showChild is false, delete the Child child component and invoke Child aboutToDisappear.
134        this.showChild = false;
135      })
136      // Push to Page and execute onPageHide.
137      Button('push to next page')
138        .onClick(() => {
139          router.pushUrl({ url: 'pages/Page' });
140        })
141    }
142  }
143}
144
145@Component
146struct Child {
147  @State title: string = 'Hello World';
148  // Component lifecycle
149  aboutToDisappear() {
150    console.info('[lifeCycle] Child aboutToDisappear');
151  }
152
153  // Component lifecycle
154  onDidBuild() {
155    console.info('[lifeCycle] Child onDidBuild');
156  }
157
158  // Component lifecycle
159  aboutToAppear() {
160    console.info('[lifeCycle] Child aboutToAppear');
161  }
162
163  build() {
164    Text(this.title)
165      .fontSize(50)
166      .margin(20)
167      .onClick(() => {
168        this.title = 'Hello ArkUI';
169      })
170  }
171}
172```
173```ts
174// Page.ets
175@Entry
176@Component
177struct Page {
178  @State textColor: Color = Color.Black;
179  @State num: number = 0;
180
181  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
182  onPageShow() {
183    this.num = 5;
184  }
185
186  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
187  onPageHide() {
188    console.log("Page onPageHide");
189  }
190
191  // Only components decorated by @Entry can call the lifecycle callbacks of a page.
192  onBackPress() { // If the value is not set, false is used.
193    this.textColor = Color.Grey;
194    this.num = 0;
195  }
196
197  // Component lifecycle
198  aboutToAppear() {
199    this.textColor = Color.Blue;
200  }
201
202  build() {
203    Column() {
204      Text (`num: ${this.num}`)
205        .fontSize(30)
206        .fontWeight(FontWeight.Bold)
207        .fontColor(this.textColor)
208        .margin(20)
209        .onClick(() => {
210          this.num += 5;
211        })
212    }
213    .width('100%')
214  }
215}
216```
217
218In 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 page lifecycle callbacks. Therefore, the lifecycle callbacks of the **Index** page – **onPageShow**, **onPageHide**, and **onBackPress**, are declared in **MyComponent**. In **MyComponent** and its child components, component lifecycle callbacks – **aboutToAppear**, **onDidBuild**, and **aboutToDisappear** – are also declared.
219
220
221- The initialization process of application cold start is as follows: **MyComponent aboutToAppear** -> **MyComponent build** -> **MyComponent onDidBuild** -> **Child aboutToAppear** -> **Child build** -> **Child onDidBuild** -> **Index onPageShow**
222
223- 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.
224
225
226- 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.
227
228- If **router.replaceUrl** is called, the current index page is destroyed. As mentioned above, the component destruction is to detach the subtree from the component tree. Therefore, the executed lifecycle process is changed to the initialization lifecycle process of the new page and then execute **Index onPageHide** -> **MyComponent aboutToDisappear** -> **Child aboutToDisappear**.
229
230- When the **Back** button is clicked, the **Index onBackPress** callback is invoked, and the current **Index** page is destroyed.
231
232- When the application is minimized or switched to the background, the **Index onPageHide** callback is invoked. As the current **Index** page is not destroyed, **aboutToDisappear** of the component is not executed. When the application returns to the foreground, the **Index onPageShow** callback is invoked.
233
234
235- When the application exits, the following callbacks are executed in order: **Index onPageHide** -> **MyComponent aboutToDisappear** -> **Child aboutToDisappear**.
236
237## Custom Component's Listening for Page Changes
238
239You can use the listener API in [Observer](../reference/apis-arkui/js-apis-arkui-observer.md#observeronrouterpageupdate11) to listen for page changes in custom components.
240
241```ts
242// Index.ets
243import { uiObserver, router, UIObserver } from '@kit.ArkUI';
244
245@Entry
246@Component
247struct Index {
248  listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
249    let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
250    if (info.pageId == routerInfo?.pageId) {
251      if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
252        console.log(`Index onPageShow`);
253      } else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
254        console.log(`Index onPageHide`);
255      }
256    }
257  }
258  aboutToAppear(): void {
259    let uiObserver: UIObserver = this.getUIContext().getUIObserver();
260    uiObserver.on('routerPageUpdate', this.listener);
261  }
262  aboutToDisappear(): void {
263    let uiObserver: UIObserver = this.getUIContext().getUIObserver();
264    uiObserver.off('routerPageUpdate', this.listener);
265  }
266  build() {
267    Column() {
268      Text(`this page is ${this.queryRouterPageInfo()?.pageId}`)
269        .fontSize(25)
270      Button("push self")
271        .onClick(() => {
272          router.pushUrl({
273            url: 'pages/Index'
274          })
275        })
276      Column() {
277        SubComponent()
278      }
279    }
280  }
281}
282@Component
283struct SubComponent {
284  listener: (info: uiObserver.RouterPageInfo) => void = (info: uiObserver.RouterPageInfo) => {
285    let routerInfo: uiObserver.RouterPageInfo | undefined = this.queryRouterPageInfo();
286    if (info.pageId == routerInfo?.pageId) {
287      if (info.state == uiObserver.RouterPageState.ON_PAGE_SHOW) {
288        console.log(`SubComponent onPageShow`);
289      } else if (info.state == uiObserver.RouterPageState.ON_PAGE_HIDE) {
290        console.log(`SubComponent onPageHide`);
291      }
292    }
293  }
294  aboutToAppear(): void {
295    let uiObserver: UIObserver = this.getUIContext().getUIObserver();
296    uiObserver.on('routerPageUpdate', this.listener);
297  }
298  aboutToDisappear(): void {
299    let uiObserver: UIObserver = this.getUIContext().getUIObserver();
300    uiObserver.off('routerPageUpdate', this.listener);
301  }
302  build() {
303    Column() {
304      Text(`SubComponent`)
305    }
306  }
307}
308```
309