在 ArkUI 中,UI 顯示的內容均為組件,由框架直接提供的稱為系統組件,由開發者定義的稱為自定義組件。在進行 UI 界面開發時,通常不是簡單的將系統組件進行組合使用,而是需要考慮代碼可重用性、業務邏輯與 UI 分離,後續版本演進等因素。因此,將 UI 和部分業務邏輯封裝成自定義組件是不可或缺的能力。
自定義組件#
自定義組件具有以下特點:
- 可組合:允許開發者組合使用系統組件、及其屬性和方法。
- 可重用:自定義組件可以被其他組件重用,並作為不同的實例在不同的父組件或容器中使用。
- 數據驅動 UI 更新:通過狀態變量的改變,來驅動 UI 的刷新。
自定義組件的基本用法#
@Component
struct HelloComponent {
@State message: string = 'Hello, World!';
build() {
// HelloComponent自定義組件組合系統組件Row和Text
Row() {
Text(this.message)
.onClick(() => {
// 狀態變量message的改變驅動UI刷新,UI從'Hello, World!'刷新為'Hello, ArkUI!'
this.message = 'Hello, ArkUI!';
})
}
}
}
::: warning 注意
如果在另外的文件中引用該自定義組件,需要使用 export 關鍵字導出,並在使用的頁面 import 該自定義組件。
:::
HelloComponent 可以在其他自定義組件中的 build () 函數中多次創建,實現自定義組件的重用。
class HelloComponentParam {
message: string = ""
}
@Entry
@Component
struct ParentComponent {
param: HelloComponentParam = {
message: 'Hello, World!'
}
build() {
Column() {
Text('ArkUI message')
HelloComponent(this.param);
Divider()
HelloComponent(this.param);
}
}
}
自定義組件的基本結構#
- struct:自定義組件基於 struct 實現,struct + 自定義組件名 + {...} 的組合構成自定義組件,不能有繼承關係。對於 struct 的實例化,可以省略 new。
::: tip 說明
自定義組件名、類名、函數名不能和系統組件名相同。
:::
- @Component:@Component 裝飾器僅能裝飾 struct 關鍵字聲明的數據結構。struct 被 @Component 裝飾後具備組件化的能力,需要實現 build 方法描述 UI,一個 struct 只能被一個 @Component 裝飾。@Component 可以接受一個可選的 bool 類型參數。
::: tip 說明
從 API version 9 開始,該裝飾器支持在 ArkTS 卡片中使用。
從 API version 11 開始,@Component 可以接受一個可選的 bool 類型參數。
:::
@Component
struct MyComponent {
}
freezeWhenInactive#
組件凍結選項。
@Component({ freezeWhenInactive: true })
struct MyComponent {
}
build () 函數#
build () 函數用於定義自定義組件的聲明式 UI 描述,自定義組件必須定義 build () 函數。
@Component
struct MyComponent {
build() {
}
}
@Entry#
@Entry 裝飾的自定義組件將作為 UI 頁面的入口。在單個 UI 頁面中,最多可以使用 @Entry 裝飾一個自定義組件。@Entry 可以接受一個可選的 LocalStorage 的參數
::: tip 說明
從 API version 9 開始,該裝飾器支持在 ArkTS 卡片中使用。
從 API version 10 開始,@Entry 可以接受一個可選的 LocalStorage 的參數或者一個可選的 EntryOptions 參數。
從 API version 11 開始,該裝飾器支持在元服務中使用。
:::
@Entry
@Component
struct MyComponent {
}```
EntryOptions10#
命名路由跳轉選項。
@Entry({ routeName : 'myPage' })
@Component
struct MyComponent {
}
@Reusable:@Reusable 裝飾的自定義組件具備可重用能力
成員函數 / 變量#
- 自定義組件除了必須要實現 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.someColor
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
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 a: number = 1;
}
- 不允許在 UI 描述裡直接使用 console.info,但允許在方法或者函數裡使用,反例如下。
build() {
// 反例:不允許console.info
console.info('print debug log');
}
- 不允許調用沒有用 @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;
}
// 正例:使用if
if(expression == 1) {
Text('...')
} else if(expression == 2) {
Image('...')
} else {
Text('...')
}
}
}
- 不允許直接改變狀態變量,反例如下。詳細分析見 @State 常見問題:不允許在 build 裡改狀態變量
@Component
struct CompA {
@State col1: Color = Color.Yellow;
@State col2: Color = Color.Green;
@State count: number = 1;
build() {
Column() {
// 應避免直接在Text組件內改變count的值
Text(`${this.count++}`)
.width(50)
.height(50)
.fontColor(this.col1)
.onClick(() => {
this.col2 = Color.Red;
})
Button("change col1").onClick(() =>{
this.col1 = Color.Pink;
})
}
.backgroundColor(this.col2)
}
}
在 ArkUI 狀態管理中,狀態驅動 UI 更新。
所以,不能在自定義組件的 build () 或 @Builder 方法裡直接改變狀態變量,這可能會造成循環渲染的風險。Text ('${this.count++}') 在全量更新或最小化更新會產生不同的影響:
-
全量更新(API8 及以前版本): ArkUI 可能會陷入一個無限的重渲染的循環裡,因為 Text 組件的每一次渲染都會改變應用的狀態,就會再引起下一輪渲染的開啟。 當 this.col2 更改時,都会執行整個 build 構建函數,因此
,Text(${this.count++})
綁定的文本也會更改,每次重新渲染Text(${this.count++})
,又會使 this.count 狀態變量更新,導致新一輪的 build 執行,從而陷入無限循環。 -
最小化更新(API9 - 至今版本): 當 this.col2 更改時,只有 Column 組件會更新,Text 組件不會更改。 只當 this.col1 更改時,會去更新整個 Text 組件,其所有屬性函數都會執行,所以會看到
Text(${this.count++})
自增。因為目前 UI 以組件為單位進行更新,如果組件上某一個屬性發生改變,會更新整體的組件。所以整體的更新鏈路是:this.col1 = Color.Pink -> Text 組件整體更新 ->this.count++ ->Text 組件整體更新。值得注意的是,這種寫法在初次渲染時會導致 Text 組件渲染兩次,從而對性能產生影響。
build 函數中更改應用狀態的行為可能會比上面的示例更加隱蔽,比如:
- 在 @Builder,@Extend 或 @Styles 方法內改變狀態變量 。
- 計算參數時調用函數中改變應用狀態變量,例如
Text('${this.calcLabel()}')
。 - 對當前數組做出修改,sort () 改變了數組 this.arr,隨後的 filter 方法會返回一個新的數組。
自定義組件通用樣式#
自定義組件通過 “.” 鏈式調用的形式設置通用樣式。
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
::: tip 說明
ArkUI 給自定義組件設置樣式時,相當於給 MyComponent2 套了一個不可見的容器組件,而這些樣式是設置在容器組件上的,而非直接設置給 MyComponent2 的 Button 組件。通過渲染結果我們可以很清楚的看到,背景顏色紅色並沒有直接生效在 Button 上,而是生效在 Button 所處的開發者不可見的容器組件上。
:::
此文由 Mix Space 同步更新至 xLog
原始鏈接為 http://www.sroxck.top/posts/harmony/arkts-components