目錄
自定義組件的基本用法
自定義組件的基本結構
struct
@Component
freezeWhenInactive
?build()函數
@Entry
EntryOptions
@Reusable
成員函數/變量
自定義組件的參數規定
build()函數
自定義組件生命周期
自定義組件的創建和渲染流程
自定義組件重新渲染
自定義組件的刪除
自定義組件嵌套使用與示例
在ArkUI中,UI顯示的內容均為組件,由框架直接提供的稱為系統組件,由開發者定義的稱為自定義組件。進行UI界面開發時,不僅要組合使用系統組件,還需考慮代碼的可復用性、業務邏輯與UI的分離,以及后續版本的演進等因素。因此,將UI和部分業務邏輯封裝成自定義組件是不可或缺的能力。
自定義組件的特點:
- 可組合:允許開發者組合使用系統組件、及其屬性和方法。
- 可重用:自定義組件可以被其他組件重用,并作為不同的實例在不同的父組件或容器中使用。
- 數據驅動UI更新:通過狀態變量的改變,來驅動UI的刷新。
自定義組件的基本用法
@Component
struct HelloComponent {@State message: string = 'Hello, World!';build() {// HelloComponent自定義組件組合系統組件Row和TextRow() {Text(this.message).onClick(() => {// 狀態變量message的改變驅動UI刷新,UI從'Hello, World!'刷新為'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}
注意
如果在其他文件中引用自定義組件,需要使用export關鍵字導出組件,并在使用的頁面import該自定義組件。
?可以在其他自定義組件的build()函數中多次創建HelloComponent,以實現自定義組件的重用。
@Entry
@Component
struct ParentComponent {build() {Column() {Text('ArkUI message')HelloComponent({ message: 'Hello World!' });Divider()HelloComponent({ message: '你好,世界!' });}}
}
自定義組件的基本結構
struct
自定義組件基于struct實現,struct + 自定義組件名 + {...}的組合構成自定義組件,不能有繼承關系。對于struct的實例化,可以省略new。
說明
自定義組件名、類名、函數名不得與系統組件名重復。
@Component
@Component裝飾器僅能裝飾struct關鍵字聲明的數據結構。struct被@Component裝飾后具備組件化的能力,需要實現build方法描述UI,一個struct只能被一個@Component裝飾。@Component可以接受一個可選的boolean類型參數。
說明
從API version 9開始,該裝飾器支持在ArkTS卡片中使用。
從API version 11開始,@Component可以接受一個可選的boolean類型參數。
@Component
struct MyComponent {
}
freezeWhenInactive
組件凍結選項。
自定義組件凍結功能專為優化復雜UI頁面的性能而設計,尤其適用于包含多個頁面棧、長列表或宮格布局的場景。當狀態變量綁定多個UI組件時,其變化易觸發大量組件刷新,導致界面卡頓與響應延遲。為提升這類高負載UI界面的刷新性能,建議開發者使用自定義組件凍結功能。
組件凍結功能是一種性能優化機制,它會凍結非激活狀態下的組件的刷新能力。當組件處于非激活狀態時,即使其綁定的狀態變量發生變化,也不會觸發該組件的UI重新渲染,從而降低復雜UI場景下的刷新負載。
組件凍結的工作原理是:
- 開發者通過設置freezeWhenInactive屬性,即可激活組件凍結機制。
- 啟用后,系統將僅對處于激活狀態的自定義組件進行更新,這使得UI框架可以盡量縮小更新范圍,僅限于用戶可見范圍內(激活狀態)的自定義組件,從而提高復雜UI場景下的刷新效率。
- 當之前處于inactive狀態的自定義組件重新變為active狀態時,狀態管理框架會對其執行必要的刷新操作,確保UI的正確展示。
需要注意,組件active/inactive并不等同于其可見性。組件凍結目前僅適用于以下場景:
- 頁面路由:當前棧頂頁面為active狀態,非棧頂不可見頁面為inactive狀態。
- TabContent:只有當前顯示的TabContent中的自定義組件處于active狀態,其余則為inactive。
- LazyForEach:僅當前顯示的LazyForEach中的自定義組件為active狀態,而緩存節點的組件則為inactive狀態。
- Navigation:當前顯示的NavDestination中的自定義組件為active狀態,而其他未顯示的NavDestination組件則為inactive狀態。
- 組件復用:進入復用池的組件為inactive狀態,從復用池上樹的節點為active狀態。
- 混用場景:對于以上場景的組合使用,例如TabContent下面使用LazyForEach,切換Tab時,API version 17及以下,LazyForEach中的所有節點都會被設置為active狀態,而從API version 18開始,只有LazyForEach的屏上節點會被設置為active狀態,其余則為inactive狀態。
名稱 | 類型 | 必填 | 說明 |
---|---|---|---|
freezeWhenInactive | boolean | 否 | 是否開啟組件凍結。默認值false。true:開啟組件凍結,false:不開啟組件凍結。 |
@Component({ freezeWhenInactive: true })
struct MyComponent {
}
?build()函數
build()函數用于定義自定義組件的聲明式UI描述,自定義組件必須定義build()函數。
@Component
struct MyComponent {build() {}
}
@Entry
@Entry裝飾的自定義組件將作為UI頁面的入口。在單個UI頁面中,僅允許存在一個由@Entry裝飾的自定義組件作為頁面的入口。@Entry可以接受一個可選的LocalStorage的參數。
說明
從API version 9開始,該裝飾器支持在ArkTS卡片中使用。
從API version 10開始,@Entry可以接受一個可選的LocalStorage的參數或者一個可選的EntryOptions參數。
從API version 11開始,該裝飾器支持在元服務中使用。
@Entry
@Component
struct MyComponent {
}
EntryOptions
命名路由跳轉選項。
名稱 | 類型 | 必填 | 說明 |
---|---|---|---|
routeName | string | 否 | 表示作為命名路由頁面的名字。 |
storage | LocalStorage | 否 | 頁面級的UI狀態存儲。 |
useSharedStorage | boolean | 否 | 是否使用LocalStorage.getShared()接口返回的共享的LocalStorage實例對象。默認值false。true:使用共享的LocalStorage實例對象。false:不使用共享的LocalStorage實例對象。 |
說明
當useSharedStorage設置為true,并且storage也被賦值時,useSharedStorage的值優先級更高。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
@Reusable
@Reusable裝飾的自定義組件具備可復用能力。詳細請參考:@Reusable裝飾器:組件復用。
說明
從API version 10開始,該裝飾器支持在ArkTS卡片中使用。
@Reusable
@Component
struct MyComponent {
}
成員函數/變量
自定義組件除了必須要實現build()函數外,還可以實現其他成員函數,成員函數具有以下約束:
- 自定義組件的成員函數為私有的,且不建議聲明為靜態函數。
自定義組件可以包含成員變量,成員變量具有以下約束:
- 自定義組件的成員變量為私有的,且不建議聲明成靜態變量。
- 自定義組件的成員變量本地初始化有些是可選的,有些是必選的。
自定義組件的參數規定
以上示例中,可以在build方法里創建自定義組件,同時在創建自定義組件的過程中,根據裝飾器的規則來初始化自定義組件的參數。
@Component
struct MyComponent {private countDownFrom: number = 0;private color: Color = Color.Blue;build() {}
}@Entry
@Component
struct ParentComponent {private someColor: Color = Color.Pink;build() {Column() {// 創建MyComponent實例,并將創建MyComponent成員變量countDownFrom初始化為10,將成員變量color初始化為this.someColorMyComponent({ countDownFrom: 10, color: this.someColor })}}
}
以下示例代碼將父組件中的函數傳遞給子組件,并在子組件中調用。
@Entry
@Component
struct Parent {@State cnt: number = 0submit: () => void = () => {this.cnt++;}build() {Column() {Text(`${this.cnt}`)Son({ submitArrow: this.submit })}}
}@Component
struct Son {submitArrow?: () => voidbuild() {Row() {Button('add').width(80).onClick(() => {if (this.submitArrow) {this.submitArrow()}})}.height(56)}
}
build()函數
所有在build()函數中聲明的語句統稱為UI描述,UI描述需要遵循以下規則:
- @Entry裝飾的自定義組件,其build()函數下的根節點唯一且必要,且必須為容器組件,其中ForEach禁止作為根節點
- @Component裝飾的自定義組件,其build()函數下的根節點唯一且必要,可以為非容器組件,其中ForEach禁止作為根節點。
@Entry @Component struct MyComponent {build() {// 根節點唯一且必要,必須為容器組件Row() {ChildComponent() }} }@Component struct ChildComponent {build() {// 根節點唯一且必要,可為非容器組件Image('test.jpg')} }
- 不允許聲明本地變量,反例如下。
build() {// 反例:不允許聲明本地變量let num: number = 1; }
- 不允許在UI描述里直接使用console.info,但允許在方法或者函數里使用,反例如下。
build() {// 反例:不允許console.infoconsole.info('print debug log'); }
- 不允許創建本地的作用域,反例如下。
build() {// 反例:不允許本地作用域{// ...} }
- ?不允許調用沒有用@Builder裝飾的方法,允許系統組件的參數是TS方法的返回值。
@Component struct ParentComponent {doSomeCalculations() {}calcTextValue(): string {return 'Hello World';}@Builder doSomeRender() {Text(`Hello World`)}build() {Column() {// 反例:不能調用沒有用@Builder裝飾的方法this.doSomeCalculations();// 正例:可以調用this.doSomeRender();// 正例:參數可以為調用TS方法的返回值Text(this.calcTextValue())}} }
- 不允許使用switch語法,當需要使用條件判斷時,請使用if。示例如下。
build() {Column() {// 反例:不允許使用switch語法switch (expression) {case 1:Text('...')break;case 2:Image('...')break;default:Text('...')break;}// 正例:使用ifif(expression == 1) {Text('...')} else if(expression == 2) {Image('...')} else {Text('...')}} }
- 不允許使用表達式,請使用if組件,示例如下。
build() {Column() {// 反例:不允許使用表達式(this.aVar > 10) ? Text('...') : Image('...')// 正例:使用if判斷if(this.aVar > 10) {Text('...')} else {Image('...')}} }
- 不允許直接改變狀態變量,反例如下。
@Component struct MyComponent {@State textColor: Color = Color.Yellow;@State columnColor: Color = Color.Green;@State count: number = 1;build() {Column() {// 應避免直接在Text組件內改變count的值Text(`${this.count++}`).width(50).height(50).fontColor(this.textColor).onClick(() => {this.columnColor = Color.Red;})Button("change textColor").onClick(() =>{this.textColor = Color.Pink;})}.backgroundColor(this.columnColor)} }
在ArkUI狀態管理中,狀態驅動UI更新。
自定義組件生命周期
自定義組件生命周期,即用@Component或@ComponentV2裝飾的自定義組件的生命周期,提供以下生命周期接口:
- aboutToAppear:組件即將出現時回調該接口,具體時機為在創建自定義組件的新實例后,在執行其build函數之前執行。
- onDidBuild:在組件首次渲染觸發的build函數執行完成之后回調該接口,后續組件重新渲染將不回調該接口。開發者可以在這個階段進行埋點數據上報等不影響實際UI的功能。不建議在onDidBuild函數中更改狀態變量、使用animateTo等功能,這可能會導致不穩定的UI表現。
- aboutToDisappear:aboutToDisappear函數在自定義組件析構銷毀之前執行。不允許在aboutToDisappear函數中改變狀態變量,特別是@Link變量的修改可能會導致應用程序行為不穩定。
自定義組件生命周期流程如下圖所示。
根據上面的流程圖,接下來從自定義組件的初始創建、重新渲染和刪除來詳細說明。
自定義組件的創建和渲染流程
- 自定義組件的創建:自定義組件的實例由ArkUI框架創建。
- 初始化自定義組件的成員變量:通過本地默認值或者構造方法傳遞參數來初始化自定義組件的成員變量,初始化順序為成員變量的定義順序。
- 如果開發者定義了aboutToAppear,則執行該方法。
- 在首次渲染的時候,執行build方法渲染系統組件,如果子組件為自定義組件,則創建自定義組件的實例。在首次渲染的過程中,框架會記錄狀態變量和組件的映射關系,當狀態變量改變時,驅動其相關的組件刷新。
- 如果開發者定義了onDidBuild,則執行該方法。
自定義組件重新渲染
當觸發事件(比如點擊)改變狀態變量時,或者LocalStorage / AppStorage中的屬性更改,并導致綁定的狀態變量更改其值時:
- 框架觀察到變化,啟動重新渲染。
- 根據框架記錄的狀態變量和組件的映射關系,僅刷新發生變化的狀態變量所關聯的組件,實現最小化更新。
自定義組件的刪除
例如if組件的分支改變或ForEach循環渲染中數組的個數改變,組件將被移除:
- 在刪除組件之前,將調用其aboutToDisappear生命周期函數,標記著該節點將要被銷毀。ArkUI的節點刪除機制是:后端節點直接從組件樹上摘下,后端節點被銷毀,對前端節點解引用,前端節點已經沒有引用時,將被Ark虛擬機垃圾回收。
- 自定義組件和它的變量將被刪除,如果組件有同步的變量(如@Link、@Prop、@StorageLink),將從同步源上取消注冊。
不建議在生命周期aboutToDisappear中使用async await。如果在此生命周期中使用異步操作(如 Promise 或回調方法),自定義組件將被保留在Promise的閉包中,直到回調方法執行完畢,這會阻止自定義組件的垃圾回收。
自定義組件嵌套使用與示例
通過以下示例,來詳細說明自定義組件在嵌套使用時,自定義組件生命周期的調用時序:
// Index.ets
@Entry
@Component
struct Parent {@State showChild: boolean = true;@State btnColor: string = "#FF007DFF";// 組件生命周期aboutToAppear() {console.info('Parent aboutToAppear');}// 組件生命周期onDidBuild() {console.info('Parent onDidBuild');}// 組件生命周期aboutToDisappear() {console.info('Parent aboutToDisappear');}build() {Column() {// this.showChild為true,創建Child子組件,執行Child aboutToAppearif (this.showChild) {Child()}Button('delete Child').margin(20).backgroundColor(this.btnColor).onClick(() => {// 更改this.showChild為false,刪除Child子組件,執行Child aboutToDisappear// 更改this.showChild為true,添加Child子組件,執行Child aboutToAppearthis.showChild = !this.showChild;})}}
}@Component
struct Child {@State title: string = 'Hello World';// 組件生命周期aboutToDisappear() {console.info('Child aboutToDisappear');}// 組件生命周期onDidBuild() {console.info('Child onDidBuild');}// 組件生命周期aboutToAppear() {console.info('Child aboutToAppear');}build() {Text(this.title).fontSize(50).margin(20).onClick(() => {this.title = 'Hello ArkUI';})}
}
以上示例中,Index頁面包含兩個自定義組件,一個是Parent,一個是Child,Parent及其子組件Child分別聲明了各自的自定義組件生命周期函數(aboutToAppear / onDidBuild / aboutToDisappear)。
- 應用冷啟動的初始化流程為:Parent aboutToAppear --> Parent build --> Parent onDidBuild --> Child aboutToAppear --> Child build --> Child onDidBuild。此處體現了自定義組件懶展開特性,即Parent執行完onDidBuild之后才會執行Child組件的aboutToAppear。日志輸出信息如下:
Parent aboutToAppear
Parent onDidBuild
Child aboutToAppear
Child onDidBuild
- 點擊Button按鈕,更改showChild為false,刪除Child組件,執行Child aboutToDisappear方法。
- 如果直接退出應用,則會觸發以下生命周期:Parent aboutToDisappear --> Child aboutToDisappear,此處體現了自定義組件刪除順序也是從父到子。日志輸出信息如下:
Parent aboutToDisappear
Child aboutToDisappear
- 最小化應用或者應用進入后臺,當前Index頁面未被銷毀,所以并不會執行組件的aboutToDisappear。
- 如果showChild的默認值為false,則應用冷啟動的初始化流程為:Parent aboutToAppear --> Parent build --> Parent onDidBuild。日志輸出信息如下:
Parent aboutToAppear
Parent onDidBuild
- 如果showChild的默認值為false,直接退出應用,則只執行Parent aboutToDisappear方法。
- 如果showChild的默認值為false,此時點擊Button按鈕,更改showChild為true,添加Child組件,添加流程為:Child aboutToAppear --> Child build --> Child onDidBuild。日志輸出信息如下:
Child aboutToAppear
Child onDidBuild
當showchild為默認值true時,該示例的生命周期流程圖如下所示: