在開始之前,我們先明確自定義組件和頁面的關係:
- 自定義組件:@Component 裝飾的 UI 單元,可以組合多個系統組件實現 UI 的重用,可以調用組件的生命週期。
- 頁面:即應用的 UI 頁面。可以由一個或者多個自定義組件組成,@Entry 裝飾的自定義組件為頁面的入口組件,即頁面的根節點,一個頁面有且僅能有一個 @Entry。只有被 @Entry 裝飾的組件才可以調用頁面的生命週期。
頁面生命週期,即被 @Entry 裝飾的組件生命週期,提供以下生命週期接口:
- onPageShow:頁面每次顯示時觸發一次,包括路由過程、應用進入前台等場景。
- onPageHide:頁面每次隱藏時觸發一次,包括路由過程、應用進入後台等場景。
- onBackPress:當用戶點擊返回按鈕時觸發。
組件生命週期,即一般用 @Component 裝飾的自定義組件的生命週期,提供以下生命週期接口:
- aboutToAppear:組件即將出現時回調該接口,具體時機為在創建自定義組件的新實例後,在執行其 build () 函數之前執行。
- onDidBuild:組件 build () 函數執行完成之後回調該接口,不建議在 onDidBuild 函數中更改狀態變量、使用 animateTo 等功能,這可能會導致不穩定的 UI 表現。
- aboutToDisappear:aboutToDisappear 函數在自定義組件析構銷毀之前執行。不允許在 aboutToDisappear 函數中改變狀態變量,特別是 @Link 變量的修改可能會導致應用程序行為不穩定。
自定義組件的創建和渲染流程#
- 自定義組件的創建:自定義組件的實例由 ArkUI 框架創建。
- 初始化自定義組件的成員變量:通過本地默認值或者構造方法傳遞參數來初始化自定義組件的成員變量,初始化順序為成員變量的定義順序。
- 如果開發者定義了 aboutToAppear,則執行 aboutToAppear 方法。
- 在首次渲染的時候,執行 build 方法渲染系統組件,如果子組件為自定義組件,則創建自定義組件的實例。在首次渲染的過程中,框架會記錄狀態變量和組件的映射關係,當狀態變量改變時,驅動其相關的組件刷新。
- 如果開發者定義了 onDidBuild,則執行 onDidBuild 方法
自定義組件重新渲染#
當事件句柄被觸發(比如設置了點擊事件,即觸發點擊事件)改變了狀態變量時,或者 LocalStorage / AppStorage 中的屬性更改,並導致綁定的狀態變量更改其值時:
- 框架觀察到了變化,將啟動重新渲染。
- 根據框架持有的兩個 map(自定義組件的創建和渲染流程中第 4 步),框架可以知道該狀態變量管理了哪些 UI 組件,以及這些 UI 組件對應的更新函數。執行這些 UI 組件的更新函數,實現最小化更新。
自定義組件的刪除#
如果 if 組件的分支改變,或者 ForEach 循環渲染中數組的個數改變,組件將被刪除:
- 在刪除組件之前,將調用其 aboutToDisappear 生命週期函數,標記著該節點將要被銷毀。ArkUI 的節點刪除機制是:後端節點直接從組件樹上摘下,後端節點被銷毀,對前端節點解引用,前端節點已經沒有引用時,將被 JS 虛擬機垃圾回收。
- 自定義組件和它的變量將被刪除,如果其有同步的變量,比如 @Link、@Prop、@StorageLink,將從同步源上取消註冊。
不建議在生命週期 aboutToDisappear 內使用 async await,如果在生命週期的 aboutToDisappear 使用異步操作(Promise 或者回調方法),自定義組件將被保留在 Promise 的閉包中,直到回調方法被執行完,這個行為阻止了自定義組件的垃圾回收。
以下示例展示了生命週期的調用時機:
// Index.ets
import { router } from '@kit.ArkUI';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor:string = "#FF007DFF";
// 只有被@Entry裝飾的組件才可以調用頁面的生命週期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry裝飾的組件才可以調用頁面的生命週期
onPageHide() {
console.info('Index onPageHide');
}
// 只有被@Entry裝飾的組件才可以調用頁面的生命週期
onBackPress() {
console.info('Index onBackPress');
this.btnColor ="#FFEE0606";
return true // 返回true表示頁面自己處理返回邏輯,不進行頁面路由;返回false表示使用默認的路由返回邏輯,不設置返回值按照false處理
}
// 組件生命週期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// 組件生命週期
onDidBuild() {
console.info('MyComponent onDidBuild');
}
// 組件生命週期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild為true,創建Child子組件,執行Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild為false,刪除Child子組件,執行Child aboutToDisappear
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
this.showChild = false;
})
// push到page頁面,執行onPageHide
Button('push to next page')
.onClick(() => {
router.pushUrl({ url: 'pages/page' });
})
}
}
}
@Component
struct Child {
@State title: string = 'Hello World';
// 組件生命週期
aboutToDisappear() {
console.info('[lifeCycle] Child aboutToDisappear')
}
// 組件生命週期
onDidBuild() {
console.info('[lifeCycle] Child onDidBuild');
}
// 組件生命週期
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() { // 不設置返回值按照false處理
this.textColor = Color.Grey;
this.num = 0;
}
aboutToAppear() {
this.textColor = Color.Blue;
}
build() {
Column() {
Text(`num 的值為:${this.num}`)
.fontSize(30)
.fontWeight(FontWeight.Bold)
.fontColor(this.textColor)
.margin(20)
.onClick(() => {
this.num += 5;
})
}
.width('100%')
}
}
以上示例中,Index 頁面包含兩個自定義組件,一個是被 @Entry 裝飾的 MyComponent,也是頁面的入口組件,即頁面的根節點;一個是 Child,是 MyComponent 的子組件。只有 @Entry 裝飾的節點才可以使頁面級別的生命週期方法生效,因此在 MyComponent 中聲明當前 Index 頁面的頁面生命週期函數(onPageShow /onPageHide/onBackPress)。MyComponent 和其子組件 Child 分別聲明了各自的組件級別生命週期函數(aboutToAppear /onDidBuild/aboutToDisappear)。
-
應用冷啟動的初始化流程為:MyComponent aboutToAppear --> MyComponent build --> MyComponent onDidBuild--> Child aboutToAppear --> Child build --> Child onDidBuild --> Index onPageShow。
-
點擊 “delete Child”,if 綁定的 this.showChild 變成 false,刪除 Child 組件,會執行 Child aboutToDisappear 方法。
-
點擊 “push to next page”,調用 router.pushUrl 接口,跳轉到另外一個頁面,當前 Index 頁面隱藏,執行頁面生命週期 Index onPageHide。此處調用的是 router.pushUrl 接口,Index 頁面被隱藏,並沒有銷毀,所以只調用 onPageHide。跳轉到新頁面後,執行初始化新頁面的生命週期的流程。
-
如果調用的是 router.replaceUrl,則當前 Index 頁面被銷毀,執行的生命週期流程將變為:Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已經提到,組件的銷毀是從組件樹上直接摘下子樹,所以先調用父組件的 aboutToDisappear,再調用子組件的 aboutToDisappear,然後執行初始化新頁面的生命週期流程。
-
點擊返回按鈕,觸發頁面生命週期 Index onBackPress,且觸發返回一個頁面後會導致當前 Index 頁面被銷毀。
-
最小化應用或者應用進入後台,觸發 Index onPageHide。當前 Index 頁面沒有被銷毀,所以並不會執行組件的 aboutToDisappear。應用回到前台,執行 Index onPageShow。
-
退出應用,執行 Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
自定義組件监听页面生命週期#
使用無感监听页面路由的能力,能夠實現在自定義組件中监听页面的生命週期。
// 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`)
}
}
}
此文由 Mix Space 同步更新至 xLog 原始鏈接為 http://www.sroxck.top/posts/harmony/arkts-life