前言
本文代碼案例基于Api13。
在實際的開發中,我們經常會遇到自定義組件的情況,比如通用的列表組件,選項卡組件等等,由于使用方的樣式不一,子組件是動態變化的,針對這一情況,就不得不讓使用方把子組件視圖傳遞過來,如何來接收這個UI視圖,這就是@BuilderParam裝飾器的作用。
簡單案例
簡單封裝一個通用的List組件,由于每個列表的數據和UI布局都是不一樣的,那么這兩塊就需要暴露給使用方,代碼如下:
/*** AUTHOR:AbnerMing* DATE:2025/2/13* INTRODUCE:通用的列表組件* */
@Component
export struct ListView {dataList?: Object[] //數據源@BuilderParam itemLayout: (item: Object, index: number) => void //子視圖build() {List() {ForEach(this.dataList, (item: Object, index: number) => {ListItem() {this.itemLayout(item, index)}})}}
}
以上的自定義List組件,我們就可以用在任何頁面,只需要傳遞數據和子視圖即可。
@Entry
@Component
struct Index {@BuilderitemLayout(item: Object, index: number) {Text(item.toString())}build() {Column() {ListView({dataList: [0, 1, 2, 3, 4, 5, 6],itemLayout: this.itemLayout})}.height('100%').width('100%')}
}
@BuilderParam裝飾器讓UI組件賦予了動態的變化,可以根據自身需要實現不同的效果,避免了實例增加了相同的功能,從而實現更高程度的組件復用和代碼解耦。
使用方式
@BuilderParam裝飾器,常見于自定義組件,暴露給使用方進行調用,用來承接@Builder裝飾器修飾的函數,使用方式很簡單,格式如下:
@BuilderParam test: () => void
如果接收參數,直接在括號中添加即可。
除了正常的由調用者傳遞UI組件之外,我們也可以初始化一個默認的視圖,直接在后面等于即可,這樣在未傳遞的話就會加載默認的視圖。
@Builder
testView() {}@BuilderParam test: () => void = this.testView()
this指向問題
如下所示,我們自定義了一個組件,定義了兩個@BuilderParam,用于接收傳遞的UI視圖:
@Component
export struct TestView {testContent: string = "TestView"@BuilderParam layout: () => void@BuilderParam layout2: () => voidbuild() {Column() {if (this.layout != undefined) {this.layout()}if (this.layout2 != undefined) {this.layout2()}}}
}
我們通過三種方式,分別調用@Builder修飾的函數。
@Entry
@Component
struct Index {testContent: string = "Index"@BuildertestView() {Text(`${this.testContent}`).fontSize(20).margin({ top: 20 }).fontWeight(FontWeight.Bold)}build() {Column() {this.testView()TestView({ layout: this.testView })TestView({layout2: () => {this.testView()}})}.height('100%').width('100%')}
}
運行之后,效果如下:
我們可以看到,直接調用@Builder修飾的函數,也就是this.testView()這行代碼,this指向的是當前的Index類,其值也是取的Index中值。
當我們以參數的形式,傳遞給@BuilderParam時,也就是TestView({ layout: this.testView })這行代碼,可以發現,其this并不是指的是Index類,而是自定義組件TestView,取的是TestView中所定義的值。
當我們針對自定義組件,換種方式使用時,也就是如下方式使用:
TestView({layout2: () => {this.testView()}})
可以發現,this又切換為了當前類Index,這是因為箭頭函數的this指向的是宿主對象,所以其值取的是Index類中的。
所以在有@BuilderParam傳遞UI視圖時,一定要注意this的指向問題,這也是為什么很多同學遇到在@Builder修飾的函數中為什么不刷新數據的問題,其原因就是this指向不對。
數據更新
更新@BuilderParam裝飾器,本意就是更新對應的@Builder修飾的函數,這個在《鴻蒙開發:了解@Builder裝飾器》一篇中已經重點做了講解,這里再重新概述一下。
簡單自定義一個組件,使用 @BuilderParam裝飾器,對外暴露一個UI視圖。
@Component
export struct TestView {@BuilderParam layout: () => voidbuild() {Column() {if (this.layout != undefined) {this.layout()}}}
}
上面已經講述過this指向問題了,如果數據在本頁面內,那么一定要使用箭頭函數來調用@Builder修飾的函數,才能實現數據的更新。
@Entry
@Component
struct Index {@State testContent: string = "測試數據一"@BuildertextView() {Text(this.testContent).fontSize(20).fontWeight(FontWeight.Bold)}build() {Column() {TestView({layout: () => {this.textView()}})Button("點擊").onClick(() => {this.testContent = "測試數據二"})}.height('100%').width('100%')}
}
相關總結
在聲明@BuilderParam的時候,如果未有默認值,那么在不傳遞的情況下,會發生異常崩潰,為了解決這一問題,有兩種方案,方案一,主動設置默認值:
@Builder
testView() {}@BuilderParam test: () => void = this.testView()
方案二,在調用的地方進行非空校驗:
@BuilderParam test: () => voidbuild() {if (this.test != undefined) {this.test()}}
@BuilderParam用于接收@Builder定義的函數,私有和全局都可以。
定義全局的@Builder。
@Builder
export function TextView() {Text("測試數據一").fontSize(20).fontWeight(FontWeight.Bold)
}
調用
@Entry
@Component
struct Index {build() {Column() {TestView({ layout: TextView })}.height('100%').width('100%')}
}