參考文檔
目錄
- 基本語法概述
- 聲明式UI描述
- 創(chuàng)建自定義組件
- 頁面和自定義組件生命周期
- 裝飾器
基本語法概述
我們用一個具體的示例來說明ArkTS的基本組成。如下圖所示,當開發(fā)者點擊按鈕時,文本內(nèi)容從“Hello World”變?yōu)椤癏ello ArkUI”:

本示例中,ArkTS的基本組成如下所示:

裝飾器: 用于裝飾類、結(jié)構(gòu)、方法以及變量,并賦予其特殊的含義。如上述示例中@Entry、@Component和@State都是裝飾器,@Component表示自定義組件,@Entry表示該自定義組件為入口組件,@State表示組件中的狀態(tài)變量,狀態(tài)變量變化會觸發(fā)UI刷新。
UI描述:以聲明式的方式來描述UI的結(jié)構(gòu),例如build()方法中的代碼塊。
自定義組件:可復(fù)用的UI單元,可組合其他組件,如上述被@Component裝飾的struct Hello。
系統(tǒng)組件:ArkUI框架中默認內(nèi)置的基礎(chǔ)和容器組件,可直接被開發(fā)者調(diào)用,比如示例中的Column、Text、Divider、Button。
屬性方法:組件可以通過鏈式調(diào)用配置多項屬性,如fontSize()、width()、height()、backgroundColor()等。
事件方法:組件可以通過鏈式調(diào)用設(shè)置多個事件的響應(yīng)邏輯,如跟隨在Button后面的onClick()。
系統(tǒng)組件、屬性方法、事件方法具體使用可參考基于ArkTS的聲明式開發(fā)范式。
聲明式UI描述
ArkTS以聲明方式組合和擴展組件來描述應(yīng)用程序的UI,同時還提供了基本的屬性、事件和子組件配置方法,幫助開發(fā)者實現(xiàn)應(yīng)用交互邏輯。
創(chuàng)建組件
根據(jù)組件構(gòu)造方法的不同,創(chuàng)建組件包含有參數(shù)和無參數(shù)兩種方式。
Column() {
// string類型的參數(shù)
Text('item 1')
// 無參數(shù)
Divider()
// $r形式引入應(yīng)用資源,可應(yīng)用于多語言場景
Text($r('app.string.title_value'))
// 使用變量作為參數(shù)
Image(this.imagePath)
// 使用表達式作為參數(shù)
Image('https://' + this.imageUrl)
}
配置屬性
屬性方法以“.”鏈式調(diào)用的方式配置系統(tǒng)組件的樣式和其他屬性,建議每個屬性方法單獨寫一行。
Text('test')
// 配置字體大小
.fontSize(12)
// 使用預(yù)定義的枚舉作為參數(shù)
.fontColor(Color.Red)
Image('test.jpg')
// 使用變量或表達式作為參數(shù)
.width(this.count % 2 === 0 ? 100 : 200)
.height(this.offset)
配置事件
使用匿名函數(shù)表達式配置組件的事件方法,要求使用“() => {...}”,以確保函數(shù)與組件綁定,同時符合ArtTS語法規(guī)范。
Button('Click me')
.onClick(() => {
this.myText = 'ArkUI';
})
使用組件的成員函數(shù)配置組件的事件方法。
myClickHandler(): void {
this.myText = 'ArkUI';
}
...
Button('add counter')
.onClick(this.myClickHandler)
配置子組件
容器組件均支持子組件配置,可以實現(xiàn)相對復(fù)雜的多級嵌套。
Column、Row、Stack、Grid、List等組件都是容器組件。
Column() {
Text('Hello')
.fontSize(100)
Row() {
Image('test1.jpg')
.width(100)
.height(100)
Button('click +1')
.onClick(() => {
console.info('+1 clicked!');
})
}
}
創(chuàng)建自定義組件
在ArkUI中,UI顯示的內(nèi)容均為組件,由框架直接提供的稱為系統(tǒng)組件,由開發(fā)者定義的稱為自定義組件。在進行 UI 界面開發(fā)時,通常不是簡單的將系統(tǒng)組件進行組合使用,而是需要考慮代碼可復(fù)用性、業(yè)務(wù)邏輯與UI分離,后續(xù)版本演進等因素。因此,將UI和部分業(yè)務(wù)邏輯封裝成自定義組件是不可或缺的能力。
自定義組件具有以下特點:
- 可組合:允許開發(fā)者組合使用系統(tǒng)組件、及其屬性和方法。
- 可重用:自定義組件可以被其他組件重用,并作為不同的實例在不同的父組件或容器中使用。
- 數(shù)據(jù)驅(qū)動UI更新:通過狀態(tài)變量的改變,來驅(qū)動UI的刷新。
以下示例展示了自定義組件的基本用法。
@Component
struct HelloComponent {
@State message: string = 'Hello, World!';
build() {
// HelloComponent自定義組件組合系統(tǒng)組件Row和Text
Row() {
Text(this.message)
.onClick(() => {
// 狀態(tài)變量message的改變驅(qū)動UI刷新,UI從'Hello, World!'刷新為'Hello, ArkUI!'
this.message = 'Hello, ArkUI!';
})
}
}
}
HelloComponent可以在其他自定義組件中的build()函數(shù)中多次創(chuàng)建,實現(xiàn)自定義組件的重用。
自定義組件的基本結(jié)構(gòu)
- struct:自定義組件基于struct實現(xiàn),struct + 自定義組件名 + {...}的組合構(gòu)成自定義組件,不能有繼承關(guān)系。對于struct的實例化,可以省略new。
- @Component:@Component裝飾器僅能裝飾struct關(guān)鍵字聲明的數(shù)據(jù)結(jié)構(gòu)。struct被@Component裝飾后具備組件化的能力,需要實現(xiàn)build方法描述UI,一個struct只能被一個@Component裝飾。
- @Entry:@Entry裝飾的自定義組件將作為UI頁面的入口。在單個UI頁面中,最多可以使用@Entry裝飾一個自定義組件。@Entry可以接受一個可選的LocalStorage的參數(shù)。
自定義組件的參數(shù)規(guī)定
我們可以在build方法或者@Builder裝飾的函數(shù)里創(chuàng)建自定義組件,并傳入?yún)?shù)。
@Component
struct MyComponent {
private countDownFrom: number = 0;
private color: Color = Color.Blue;
build() {
}
}
@Entry
@Component
struct ParentComponent {
private someColor: Color = Color.Pink;
build() {
Column() {
// 創(chuàng)建MyComponent實例,并將創(chuàng)建MyComponent成員變量countDownFrom初始化為10,將成員變量color初始化為this.someColor
MyComponent({ countDownFrom: 10, color: this.someColor })
}
}
}
build()函數(shù)
所有聲明在build()函數(shù)的語言,我們統(tǒng)稱為UI描述語言,UI描述語言需要遵循以下規(guī)則:
- @Entry裝飾的自定義組件,其build()函數(shù)下的根節(jié)點唯一且必要,且必須為容器組件,其中ForEach禁止作為根節(jié)點。
@Component裝飾的自定義組件,其build()函數(shù)下的根節(jié)點唯一且必要,可以為非容器組件,其中ForEach禁止作為根節(jié)點。
@Entry
@Component
struct MyComponent {
build() {
// 根節(jié)點唯一且必要,必須為容器組件
Row() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
build() {
// 根節(jié)點唯一且必要,可為非容器組件
Image('test.jpg')
}
}
- 不允許聲明本地變量,反例如下。
build() {
// 反例:不允許聲明本地變量
let a: number = 1;
}
- 不允許在UI描述里直接使用console.info,但允許在方法或者函數(shù)里使用,反例如下。
build() {
// 反例:不允許console.info
console.info('print debug log');
}
- 不允許創(chuàng)建本地的作用域,反例如下。
build() {
// 反例:不允許本地作用域
{
...
}
}
- 不允許調(diào)用除了被@Builder裝飾以外的方法,允許系統(tǒng)組件的參數(shù)是TS方法的返回值。
@Component
struct ParentComponent {
doSomeCalculations() {
}
calcTextValue(): string {
return 'Hello World';
}
@Builder doSomeRender() {
Text(`Hello World`)
}
build() {
Column() {
// 反例:不能調(diào)用沒有用@Builder裝飾的方法
this.doSomeCalculations();
// 正例:可以調(diào)用
this.doSomeRender();
// 正例:參數(shù)可以為調(diào)用TS方法的返回值
Text(this.calcTextValue())
}
}
}
- 不允許switch語法,如果需要使用條件判斷,請使用if。反例如下。
build() {
Column() {
// 反例:不允許使用switch語法
switch (expression) {
case 1:
Text('...')
break;
case 2:
Image('...')
break;
default:
Text('...')
break;
}
}
}
- 不允許使用表達式,反例如下。
build() {
Column() {
// 反例:不允許使用表達式
(this.aVar > 10) ? Text('...') : Image('...')
}
}
自定義組件通用樣式
自定義組件通過“.”鏈式調(diào)用的形式設(shè)置通用樣式。
@Component
struct MyComponent2 {
build() {
Button(`Hello World`)
}
}
@Entry
@Component
struct MyComponent {
build() {
Row() {
MyComponent2()
.width(200)
.height(300)
.backgroundColor(Color.Red)
}
}
}
頁面和自定義組件生命周期
在開始之前,我們先明確自定義組件和頁面的關(guān)系:
- 自定義組件:@Component裝飾的UI單元,可以組合多個系統(tǒng)組件實現(xiàn)UI的復(fù)用,可以調(diào)用組件的生命周期。
- 頁面:即應(yīng)用的UI頁面??梢杂梢粋€或者多個自定義組件組成,@Entry裝飾的自定義組件為頁面的入口組件,即頁面的根節(jié)點,一個頁面有且僅能有一個@Entry。只有被@Entry裝飾的組件才可以調(diào)用頁面的生命周期。
頁面生命周期,即被@Entry裝飾的組件生命周期,提供以下生命周期接口:
onPageShow:頁面每次顯示時觸發(fā)一次,包括路由過程、應(yīng)用進入前臺等場景,僅@Entry裝飾的自定義組件生效。(相當于iOS中viewDidAppear)
onPageHide:頁面每次隱藏時觸發(fā)一次,包括路由過程、應(yīng)用進入后臺等場景,僅@Entry裝飾的自定義組件生效。(相當于iOS中viewDidDisappear)
onBackPress:當用戶點擊返回按鈕時觸發(fā),僅@Entry裝飾的自定義組件生效。
組件生命周期,即一般用@Component裝飾的自定義組件的生命周期,提供以下生命周期接口:
aboutToAppear:組件即將出現(xiàn)時回調(diào)該接口,具體時機為在創(chuàng)建自定義組件的新實例后,在執(zhí)行其build()函數(shù)之前執(zhí)行。(在iOS中類似super.init()之后)
aboutToDisappear:aboutToDisappear函數(shù)在自定義組件析構(gòu)銷毀之前執(zhí)行。不允許在aboutToDisappear函數(shù)中改變狀態(tài)變量,特別是@Link變量的修改可能會導(dǎo)致應(yīng)用程序行為不穩(wěn)定。(在iOS中類似deinit())
生命周期流程如下圖所示,下圖展示的是被@Entry裝飾的組件(首頁)生命周期。

根據(jù)上面的流程圖,我們從自定義組件的初始創(chuàng)建、重新渲染和刪除來詳細解釋。
自定義組件的創(chuàng)建和渲染流程
自定義組件的創(chuàng)建:自定義組件的實例由ArkUI框架創(chuàng)建。
初始化自定義組件的成員變量:通過本地默認值或者構(gòu)造方法傳遞參數(shù)來初始化自定義組件的成員變量,初始化順序為成員變量的定義順序。
如果開發(fā)者定義了aboutToAppear,則執(zhí)行aboutToAppear方法。
-
在首次渲染的時候,執(zhí)行build方法渲染系統(tǒng)組件,如果子組件為自定義組件,則創(chuàng)建自定義組件的實例。在執(zhí)行build()函數(shù)的過程中,框架會觀察每個狀態(tài)變量的讀取狀態(tài),將保存兩個map:
a. 狀態(tài)變量 -> UI組件(包括ForEach和if)。
b. UI組件 -> 此組件的更新函數(shù),即一個lambda方法,作為build()函數(shù)的子集,創(chuàng)建對應(yīng)的UI組件并執(zhí)行其屬性方法,示意如下。
build() {
...
this.observeComponentCreation(() => {
Button.create();
})
this.observeComponentCreation(() => {
Text.create();
})
...
}
當應(yīng)用在后臺啟動時,此時應(yīng)用進程并沒有銷毀,所以僅需要執(zhí)行onPageShow。
自定義組件的刪除
如果if組件的分支改變,或者ForEach循環(huán)渲染中數(shù)組的個數(shù)改變,組件將被刪除:
在刪除組件之前,將調(diào)用其aboutToDisappear生命周期函數(shù),標記著該節(jié)點將要被銷毀。ArkUI的節(jié)點刪除機制是:后端節(jié)點直接從組件樹上摘下,后端節(jié)點被銷毀,對前端節(jié)點解引用,前端節(jié)點已經(jīng)沒有引用時,將被JS虛擬機垃圾回收。
自定義組件和它的變量將被刪除,如果其有同步的變量,比如@Link、@Prop、@StorageLink,將從同步源上取消注冊。
不建議在生命周期aboutToDisappear內(nèi)使用async await,如果在生命周期的aboutToDisappear使用異步操作(Promise或者回調(diào)方法),自定義組件將被保留在Promise的閉包中,直到回調(diào)方法被執(zhí)行完,這個行為阻止了自定義組件的垃圾回收。
以下示例展示了生命周期的調(diào)用時機:
// Index.ets
import router from '@ohos.router';
@Entry
@Component
struct MyComponent {
@State showChild: boolean = true;
@State btnColor:string = "#FF007DFF"
// 只有被@Entry裝飾的組件才可以調(diào)用頁面的生命周期
onPageShow() {
console.info('Index onPageShow');
}
// 只有被@Entry裝飾的組件才可以調(diào)用頁面的生命周期
onPageHide() {
console.info('Index onPageHide');
}
// 只有被@Entry裝飾的組件才可以調(diào)用頁面的生命周期
onBackPress() {
console.info('Index onBackPress');
this.btnColor ="#FFEE0606"
return true // 返回true表示頁面自己處理返回邏輯,不進行頁面路由;返回false表示使用默認的路由返回邏輯,不設(shè)置返回值按照false處理
}
// 組件生命周期
aboutToAppear() {
console.info('MyComponent aboutToAppear');
}
// 組件生命周期
aboutToDisappear() {
console.info('MyComponent aboutToDisappear');
}
build() {
Column() {
// this.showChild為true,創(chuàng)建Child子組件,執(zhí)行Child aboutToAppear
if (this.showChild) {
Child()
}
// this.showChild為false,刪除Child子組件,執(zhí)行Child aboutToDisappear
Button('delete Child')
.margin(20)
.backgroundColor(this.btnColor)
.onClick(() => {
this.showChild = false;
})
// push到page頁面,執(zhí)行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')
}
// 組件生命周期
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() { // 不設(shè)置返回值按照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,也是頁面的入口組件,即頁面的根節(jié)點;一個是Child,是MyComponent的子組件。只有@Entry裝飾的節(jié)點才可以使頁面級別的生命周期方法生效,所以MyComponent中聲明了當前Index頁面的頁面生命周期函數(shù)。MyComponent和其子組件Child也同時聲明了組件的生命周期函數(shù)。
- 應(yīng)用冷啟動的初始化流程為:MyComponent aboutToAppear --> MyComponent build --> Child aboutToAppear --> Child build --> Child build執(zhí)行完畢 --> MyComponent build執(zhí)行完畢 --> Index onPageShow。
- 點擊“delete Child”,if綁定的this.showChild變成false,刪除Child組件,會執(zhí)行Child aboutToDisappear方法。
- 點擊“push to next page”,調(diào)用router.pushUrl接口,跳轉(zhuǎn)到另外一個頁面,當前Index頁面隱藏,執(zhí)行頁面生命周期Index onPageHide。此處調(diào)用的是router.pushUrl接口,Index頁面被隱藏,并沒有銷毀,所以只調(diào)用onPageHide。跳轉(zhuǎn)到新頁面后,執(zhí)行初始化新頁面的生命周期的流程。
- 如果調(diào)用的是router.replaceUrl,則當前Index頁面被銷毀,執(zhí)行的生命周期流程將變?yōu)椋篒ndex onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。上文已經(jīng)提到,組件的銷毀是從組件樹上直接摘下子樹,所以先調(diào)用父組件的aboutToDisappear,再調(diào)用子組件的aboutToDisappear,然后執(zhí)行初始化新頁面的生命周期流程。
- 點擊返回按鈕,觸發(fā)頁面生命周期Index onBackPress,且觸發(fā)返回一個頁面后會導(dǎo)致當前Index頁面被銷毀。
- 最小化應(yīng)用或者應(yīng)用進入后臺,觸發(fā)Index onPageHide。當前Index頁面沒有被銷毀,所以并不會執(zhí)行組件的aboutToDisappear。應(yīng)用回到前臺,執(zhí)行Index onPageShow。
- 退出應(yīng)用,執(zhí)行Index onPageHide --> MyComponent aboutToDisappear --> Child aboutToDisappear。
裝飾器
@Builder裝飾器:自定義構(gòu)建函數(shù)
自定義組件內(nèi)部UI結(jié)構(gòu)固定,僅與使用方進行數(shù)據(jù)傳遞。ArkUI還提供了一種更輕量的UI元素復(fù)用機制@Builder,@Builder所裝飾的函數(shù)遵循build()函數(shù)語法規(guī)則,開發(fā)者可以將重復(fù)使用的UI元素抽象成一個方法,在build方法里調(diào)用。
為了簡化語言,我們將@Builder裝飾的函數(shù)也稱為“自定義構(gòu)建函數(shù)”。
裝飾器使用說明
自定義組件內(nèi)自定義構(gòu)建函數(shù)
定義的語法:
@Builder MyBuilderFunction() { ... }
使用方法:
this.MyBuilderFunction()
- 允許在自定義組件內(nèi)定義一個或多個@Builder方法,該方法被認為是該組件的私有、特殊類型的成員函數(shù)。
- 自定義構(gòu)建函數(shù)可以在所屬組件的build方法和其他自定義構(gòu)建函數(shù)中調(diào)用,但不允許在組件外調(diào)用。
- 在自定義函數(shù)體中,this指代當前所屬組件,組件的狀態(tài)變量可以在自定義構(gòu)建函數(shù)內(nèi)訪問。建議通過this訪問自定義組件的狀態(tài)變量而不是參數(shù)傳遞。
全局自定義構(gòu)建函數(shù)
定義的語法:
@Builder function MyGlobalBuilderFunction() { ... }
使用方法:
MyGlobalBuilderFunction()
全局的自定義構(gòu)建函數(shù)可以被整個應(yīng)用獲取,不允許使用this和bind方法。
如果不涉及組件狀態(tài)變化,建議使用全局的自定義構(gòu)建方法。
參數(shù)傳遞規(guī)則
自定義構(gòu)建函數(shù)的參數(shù)傳遞有按值傳遞和按引用傳遞兩種,均需遵守以下規(guī)則:
參數(shù)的類型必須與參數(shù)聲明的類型一致,不允許undefined、null和返回undefined、null的表達式。
在@Builder修飾的函數(shù)內(nèi)部,不允許改變參數(shù)值。
@Builder內(nèi)UI語法遵循UI語法規(guī)則。
只有傳入一個參數(shù),且參數(shù)需要直接傳入對象字面量才會按引用傳遞該參數(shù),其余傳遞方式均為按值傳遞。
按引用傳遞參數(shù)
按引用傳遞參數(shù)時,傳遞的參數(shù)可為狀態(tài)變量,且狀態(tài)變量的改變會引起@Builder方法內(nèi)的UI刷新。ArkUI提供$$作為按引用傳遞參數(shù)的范式。
class ABuilderParam {
paramA1: string = ''
paramB1: string = ''
}
@Builder function ABuilder($$ : ABuilderParam) {...}
按值傳遞參數(shù)
調(diào)用@Builder裝飾的函數(shù)默認按值傳遞。當傳遞的參數(shù)為狀態(tài)變量時,狀態(tài)變量的改變不會引起@Builder方法內(nèi)的UI刷新。所以當使用狀態(tài)變量的時候,推薦使用按引用傳遞。
不同參數(shù)傳遞方式的示例
class ABuilderParam {
paramA1: string = ''
}
// 使用$$作為按引用傳遞參數(shù)的范式。當傳遞的參數(shù)為狀態(tài)變量,且狀態(tài)變量的改變會引起@Builder方法內(nèi)的UI刷新
@Builder function BuilderByReference($$: ABuilderParam) {
Row() {
Text(`UseStateVarByReference: ${$$.paramA1} `)
}
.backgroundColor(Color.Red)
}
// 默認按值傳遞。當傳遞的參數(shù)為狀態(tài)變量時,狀態(tài)變量的改變不會引起@Builder方法內(nèi)的UI刷新
@Builder function BuilderByValue(paramA1: string) {
Row() {
Text(`UseStateVarByValue: ${paramA1} `)
}
.backgroundColor(Color.Green)
}
@Entry
@Component
struct Parent {
@State message: string = 'Hello World';
build() {
Row() {
Column({ space: 10 }) {
// 將this.message引用傳遞給BuilderByReference,message的改變會引起UI變化
BuilderByReference({ paramA1: this.message })
// 將this.message按值傳遞給BuilderByValue,message的改變不會引起UI變化
BuilderByValue(this.message)
Button('Click me').onClick(() => {
// 點擊“Click me”后,UI從“Hello World”刷新為“ArkUI”
this.message = 'ArkUI';
})
}
.width('100%')
}
.height('100%')
}
}

@BuilderParam裝飾器:引用@Builder函數(shù)
當開發(fā)者創(chuàng)建了自定義組件,并想對該組件添加特定功能時,例如在自定義組件中添加一個點擊跳轉(zhuǎn)操作。若直接在組件內(nèi)嵌入事件方法,將會導(dǎo)致所有引入該自定義組件的地方均增加了該功能。為解決此問題,ArkUI引入了@BuilderParam裝飾器,@BuilderParam用來裝飾指向@Builder方法的變量,開發(fā)者可在初始化自定義組件時對此屬性進行賦值,為自定義組件增加特定的功能。該裝飾器用于聲明任意UI描述的一個元素,類似slot占位符。
初始化@BuilderParam裝飾的方法
@BuilderParam裝飾的方法只能被自定義構(gòu)建函數(shù)(@Builder裝飾的方法)初始化
- 使用所屬自定義組件的自定義構(gòu)建函數(shù)或者全局的自定義構(gòu)建函數(shù),在本地初始化@BuilderParam。
@Builder function GlobalBuilder0() {}
@Component
struct Child {
@Builder doNothingBuilder() {};
@BuilderParam aBuilder0: () => void = this.doNothingBuilder;
@BuilderParam aBuilder1: () => void = GlobalBuilder0;
build(){}
}
- 用父組件自定義構(gòu)建函數(shù)初始化子組件@BuilderParam裝飾的方法。
@Component
struct Child {
@Builder FunABuilder0() {}
@BuilderParam aBuilder0: () => void = this.FunABuilder0;
build() {
Column() {
this.aBuilder0()
}
}
}
@Entry
@Component
struct Parent {
@Builder componentBuilder() {
Text(`Parent builder `)
}
build() {
Column() {
Child({ aBuilder0: this.componentBuilder })
}
}
}

- 需注意this指向正確。
以下示例中,Parent組件在調(diào)用this.componentBuilder()時,this指向其所屬組件,即“Parent”。@Builder componentBuilder()傳給子組件@BuilderParam aBuilder0,在Child組件中調(diào)用this.aBuilder0()時,this指向在Child的label,即“Child”。對于@BuilderParam aBuilder1,在將this.componentBuilder傳給aBuilder1時,調(diào)用bind綁定了this,因此其this.label指向Parent的label。
說明
開發(fā)者謹慎使用bind改變函數(shù)調(diào)用的上下文,可能會使this指向混亂。
@Component
struct Child {
label: string = `Child`
@Builder FunABuilder0() {}
@Builder FunABuilder1() {}
@BuilderParam aBuilder0: () => void = this.FunABuilder0;
@BuilderParam aBuilder1: () => void = this.FunABuilder1;
build() {
Column() {
this.aBuilder0()
this.aBuilder1()
}
}
}
@Entry
@Component
struct Parent {
label: string = `Parent`
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder, aBuilder1: ():void=>{this.componentBuilder()} })
}
}
}

使用場景
@BuilderParam裝飾的方法可以是有參數(shù)和無參數(shù)的兩種形式,需與指向的@Builder方法類型匹配。@BuilderParam裝飾的方法類型需要和@Builder方法類型一致。
class Tmp{
label:string = ''
}
@Builder function GlobalBuilder1($$ : Tmp) {
Text($$.label)
.width(400)
.height(50)
.backgroundColor(Color.Green)
}
@Component
struct Child {
label: string = 'Child'
@Builder FunABuilder0() {}
// 無參數(shù)類,指向的componentBuilder也是無參數(shù)類型
@BuilderParam aBuilder0: () => void = this.FunABuilder0;
// 有參數(shù)類型,指向的GlobalBuilder1也是有參數(shù)類型的方法
@BuilderParam aBuilder1: ($$ : Tmp) => void = GlobalBuilder1;
build() {
Column() {
this.aBuilder0()
this.aBuilder1({label: 'global Builder label' } )
}
}
}
@Entry
@Component
struct Parent {
label: string = 'Parent'
@Builder componentBuilder() {
Text(`${this.label}`)
}
build() {
Column() {
this.componentBuilder()
Child({ aBuilder0: this.componentBuilder, aBuilder1: GlobalBuilder1 })
}
}
}

尾隨閉包初始化組件
在自定義組件中使用@BuilderParam裝飾的屬性時也可通過尾隨閉包進行初始化。在初始化自定義組件時,組件后緊跟一個大括號“{}”形成尾隨閉包場景。
說明:
- 此場景下自定義組件內(nèi)有且僅有一個使用@BuilderParam裝飾的屬性。
此場景下自定義組件不支持使用通用屬性。
多個@BuilderParam裝飾的屬性,編譯報錯
開發(fā)者可以將尾隨閉包內(nèi)的內(nèi)容看做@Builder裝飾的函數(shù)傳給@BuilderParam。示例如下:
// xxx.ets
@Component
struct CustomContainer {
@Prop header: string = '';
@Builder CloserFun(){}
@BuilderParam closer: () => void = this.CloserFun
build() {
Column() {
Text(this.header)
.fontSize(30)
this.closer()
}
}
}
@Builder function specificParam(label1: string, label2: string) {
Column() {
Text(label1)
.fontSize(30)
Text(label2)
.fontSize(30)
}
}
@Entry
@Component
struct CustomContainerUser {
@State text: string = 'header';
build() {
Column() {
// 創(chuàng)建CustomContainer,在創(chuàng)建CustomContainer時,通過其后緊跟一個大括號“{}”形成尾隨閉包
// 作為傳遞給子組件CustomContainer @BuilderParam closer: () => void的參數(shù)
CustomContainer({ header: this.text }) {
Column() {
specificParam('testA', 'testB')
}.backgroundColor(Color.Yellow)
.onClick(() => {
this.text = 'changeHeader';
})
}
}
}
}

@Styles裝飾器:定義組件重用樣式
如果每個組件的樣式都需要單獨設(shè)置,在開發(fā)過程中會出現(xiàn)大量代碼在進行重復(fù)樣式設(shè)置,雖然可以復(fù)制粘貼,但為了代碼簡潔性和后續(xù)方便維護,我們推出了可以提煉公共樣式進行復(fù)用的裝飾器@Styles。
@Styles裝飾器可以將多條樣式設(shè)置提煉成一個方法,直接在組件聲明的位置調(diào)用。通過@Styles裝飾器可以快速定義并復(fù)用自定義樣式。用于快速定義并復(fù)用自定義樣式。
裝飾器使用說明
@Styles方法不支持參數(shù),反例如下。
// 反例: @Styles不支持參數(shù)
@Styles function globalFancy (value: number) {
.width(value)
}
- @Styles可以定義在組件內(nèi)或全局,在全局定義時需在方法名前面添加function關(guān)鍵字,組件內(nèi)定義時則不需要添加function關(guān)鍵字。
// 全局
@Styles function functionName() { ... }
// 在組件內(nèi)
@Component
struct FancyUse {
@Styles fancy() {
.height(100)
}
}
- 定義在組件內(nèi)的@Styles可以通過this訪問組件的常量和狀態(tài)變量,并可以在@Styles里通過事件來改變狀態(tài)變量的值,示例如下:
@Component
struct FancyUse {
@State heightValue: number = 100
@Styles fancy() {
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
}
- 組件內(nèi)@Styles的優(yōu)先級高于全局@Styles。
框架優(yōu)先找當前組件內(nèi)的@Styles,如果找不到,則會全局查找。
使用場景
以下示例中演示了組件內(nèi)@Styles和全局@Styles的用法。
// 定義在全局的@Styles封裝的樣式
@Styles function globalFancy () {
.width(150)
.height(100)
.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
@State heightValue: number = 100
// 定義在組件內(nèi)的@Styles封裝的樣式
@Styles fancy() {
.width(200)
.height(this.heightValue)
.backgroundColor(Color.Yellow)
.onClick(() => {
this.heightValue = 200
})
}
build() {
Column({ space: 10 }) {
// 使用全局的@Styles封裝的樣式
Text('FancyA')
.globalFancy ()
.fontSize(30)
// 使用組件內(nèi)的@Styles封裝的樣式
Text('FancyB')
.fancy()
.fontSize(30)
}
}
}
@Extend裝飾器:定義擴展組件樣式
在前文的示例中,可以使用@Styles用于樣式的擴展,在@Styles的基礎(chǔ)上,我們提供了@Extend,用于擴展原生組件樣式。
裝飾器使用說明
- 語法
@Extend(UIComponentName) function functionName { ... }
- 和@Styles不同,@Extend僅支持在全局定義,不支持在組件內(nèi)部定義。
- 和@Styles不同,@Extend支持封裝指定的組件的私有屬性和私有事件,以及預(yù)定義相同組件的@Extend的方法。
// @Extend(Text)可以支持Text的私有屬性fontColor
@Extend(Text) function fancy () {
.fontColor(Color.Red)
}
// superFancyText可以調(diào)用預(yù)定義的fancy
@Extend(Text) function superFancyText(size:number) {
.fontSize(size)
.fancy()
}
- 和@Styles不同,@Extend裝飾的方法支持參數(shù),開發(fā)者可以在調(diào)用時傳遞參數(shù),調(diào)用遵循TS方法傳值調(diào)用。
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(16)
Text('Fancy')
.fancy(24)
}
}
}
- @Extend裝飾的方法的參數(shù)可以為function,作為Event事件的句柄。
@Extend(Text) function makeMeClick(onClick: () => void) {
.backgroundColor(Color.Blue)
.onClick(onClick)
}
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World';
onClickHandler() {
this.label = 'Hello ArkUI';
}
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.makeMeClick(() => {this.onClickHandler()})
}
}
}
- @Extend的參數(shù)可以為狀態(tài)變量,當狀態(tài)變量改變時,UI可以正常的被刷新渲染。
@Extend(Text) function fancy (fontSize: number) {
.fontColor(Color.Red)
.fontSize(fontSize)
}
@Entry
@Component
struct FancyUse {
@State fontSizeValue: number = 20
build() {
Row({ space: 10 }) {
Text('Fancy')
.fancy(this.fontSizeValue)
.onClick(() => {
this.fontSizeValue = 30
})
}
}
}
使用場景
以下示例聲明了3個Text組件,每個Text組件均設(shè)置了fontStyle、fontWeight和backgroundColor樣式。
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(100)
.backgroundColor(Color.Blue)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(200)
.backgroundColor(Color.Pink)
Text(`${this.label}`)
.fontStyle(FontStyle.Italic)
.fontWeight(300)
.backgroundColor(Color.Orange)
}.margin('20%')
}
}
@Extend將樣式組合復(fù)用,示例如下。
@Extend(Text) function fancyText(weightValue: number, color: Color) {
.fontStyle(FontStyle.Italic)
.fontWeight(weightValue)
.backgroundColor(color)
}
通過@Extend組合樣式后,使得代碼更加簡潔,增強可讀性。
@Entry
@Component
struct FancyUse {
@State label: string = 'Hello World'
build() {
Row({ space: 10 }) {
Text(`${this.label}`)
.fancyText(100, Color.Blue)
Text(`${this.label}`)
.fancyText(200, Color.Pink)
Text(`${this.label}`)
.fancyText(300, Color.Orange)
}.margin('20%')
}
}
stateStyles:多態(tài)樣式
@Styles和@Extend僅僅應(yīng)用于靜態(tài)頁面的樣式復(fù)用,stateStyles可以依據(jù)組件的內(nèi)部狀態(tài)的不同,快速設(shè)置不同樣式。這就是我們本章要介紹的內(nèi)容stateStyles(又稱為:多態(tài)樣式)。
概述
stateStyles是屬性方法,可以根據(jù)UI內(nèi)部狀態(tài)來設(shè)置樣式,類似于css偽類,但語法不同。ArkUI提供以下五種狀態(tài):
focused:獲焦態(tài)。
normal:正常態(tài)。
pressed:按壓態(tài)。
disabled:不可用態(tài)。
selected10+:選中態(tài)。
基礎(chǔ)場景
下面的示例展示了stateStyles最基本的使用場景。Button1處于第一個組件,Button2處于第二個組件。按壓時顯示為pressed態(tài)指定的黑色。使用Tab鍵走焦,先是Button1獲焦并顯示為focus態(tài)指定的粉色。當Button2獲焦的時候,Button2顯示為focus態(tài)指定的粉色,Button1失焦顯示normal態(tài)指定的紅色。
@Entry
@Component
struct StateStylesSample {
build() {
Column() {
Button('Button1')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
.margin(20)
Button('Button2')
.stateStyles({
focused: {
.backgroundColor(Color.Pink)
},
pressed: {
.backgroundColor(Color.Black)
},
normal: {
.backgroundColor(Color.Red)
}
})
}.margin('30%')
}
}

@Styles和stateStyles聯(lián)合使用
以下示例通過@Styles指定stateStyles的不同狀態(tài)。
@Entry
@Component
struct MyComponent {
@Styles normalStyle() {
.backgroundColor(Color.Gray)
}
@Styles pressedStyle() {
.backgroundColor(Color.Red)
}
build() {
Column() {
Text('Text1')
.fontSize(50)
.fontColor(Color.White)
.stateStyles({
normal: this.normalStyle,
pressed: this.pressedStyle,
})
}
}
}

在stateStyles里使用常規(guī)變量和狀態(tài)變量
stateStyles可以通過this綁定組件內(nèi)的常規(guī)變量和狀態(tài)變量。
@Entry
@Component
struct CompWithInlineStateStyles {
@State focusedColor: Color = Color.Red;
normalColor: Color = Color.Green
build() {
Column() {
Button('clickMe').height(100).width(100)
.stateStyles({
normal: {
.backgroundColor(this.normalColor)
},
focused: {
.backgroundColor(this.focusedColor)
}
})
.onClick(() => {
this.focusedColor = Color.Pink
})
.margin('30%')
}
}
}
Button默認normal態(tài)顯示綠色,第一次按下Tab鍵讓Button獲焦顯示為focus態(tài)的紅色,點擊事件觸發(fā)后,再次按下Tab鍵讓Button獲焦,focus態(tài)變?yōu)榉凵?/p>

@AnimatableExtend裝飾器:定義可動畫屬性
@AnimatableExtend裝飾器用于自定義可動畫的屬性方法,在這個屬性方法中修改組件不可動畫的屬性。在動畫執(zhí)行過程時,通過逐幀回調(diào)函數(shù)修改不可動畫屬性值,讓不可動畫屬性也能實現(xiàn)動畫效果。
可動畫屬性:如果一個屬性方法在animation屬性前調(diào)用,改變這個屬性的值可以生效animation屬性的動畫效果,這個屬性稱為可動畫屬性。比如height、width、backgroundColor、translate屬性,Text組件的fontSize屬性等。
不可動畫屬性:如果一個屬性方法在animation屬性前調(diào)用,改變這個屬性的值不能生效animation屬性的動畫效果,這個屬性稱為不可動畫屬性。比如Ployline組件的points屬性等。
語法
@AnimatableExtend(UIComponentName) function functionName(value: typeName) {
.propertyName(value)
}
- @AnimatableExtend僅支持定義在全局,不支持在組件內(nèi)部定義。
- @AnimatableExtend定義的函數(shù)參數(shù)類型必須為number類型或者實現(xiàn) AnimtableArithmetic<T>接口的自定義類型。
- @AnimatableExtend定義的函數(shù)體內(nèi)只能調(diào)用@AnimatableExtend括號內(nèi)組件的屬性方法。
AnimtableArithmetic<T>接口說明
對復(fù)雜數(shù)據(jù)類型做動畫,需要實現(xiàn)AnimtableArithmetic<T>接口中加法、減法、乘法和判斷相等函數(shù)。
| 名稱 | 入?yún)㈩愋?/th> | 返回值類型 | 說明 |
|---|---|---|---|
| plus | AnimtableArithmetic<T> | AnimtableArithmetic<T> | 加法函數(shù) |
| subtract | AnimtableArithmetic<T> | AnimtableArithmetic<T> | 減法函數(shù) |
| multiply | number | AnimtableArithmetic<T> | 乘法函數(shù) |
| equals | AnimtableArithmetic<T> | boolean | 相等判斷函數(shù) |
使用場景
以下示例實現(xiàn)字體大小的動畫效果。
@AnimatableExtend(Text) function animatableFontSize(size: number) {
.fontSize(size)
}
@Entry
@Component
struct AnimatablePropertyExample {
@State fontSize: number = 20
build() {
Column() {
Text("AnimatableProperty")
.animatableFontSize(this.fontSize)
.animation({duration: 1000, curve: "ease"})
Button("Play")
.onClick(() => {
this.fontSize = this.fontSize == 20 ? 36 : 20
})
}.width("100%")
.padding(10)
}
}

以下示例實現(xiàn)折線的動畫效果。
class Point {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
plus(rhs: Point): Point {
return new Point(this.x + rhs.x, this.y + rhs.y)
}
subtract(rhs: Point): Point {
return new Point(this.x - rhs.x, this.y - rhs.y)
}
multiply(scale: number): Point {
return new Point(this.x * scale, this.y * scale)
}
equals(rhs: Point): boolean {
return this.x === rhs.x && this.y === rhs.y
}
}
class PointVector extends Array<Point> implements AnimatableArithmetic<PointVector> {
constructor(value: Array<Point>) {
super();
value.forEach(p => this.push(p))
}
plus(rhs: PointVector): PointVector {
let result = new PointVector([])
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push((this as Array<Point>)[i].plus((rhs as Array<Point>)[I]))
}
return result
}
subtract(rhs: PointVector): PointVector {
let result = new PointVector([])
const len = Math.min(this.length, rhs.length)
for (let i = 0; i < len; i++) {
result.push((this as Array<Point>)[i].subtract((rhs as Array<Point>)[I]))
}
return result
}
multiply(scale: number): PointVector {
let result = new PointVector([])
for (let i = 0; i < this.length; i++) {
result.push((this as Array<Point>)[i].multiply(scale))
}
return result
}
equals(rhs: PointVector): boolean {
if (this.length != rhs.length) {
return false
}
for (let i = 0; i < this.length; i++) {
if (!(this as Array<Point>)[i].equals((rhs as Array<Point>)[i])) {
return false
}
}
return true
}
get(): Array<Object[]> {
let result: Array<Object[]> = []
this.forEach(p => result.push([p.x, p.y]))
return result
}
}
@AnimatableExtend(Polyline) function animatablePoints(points: PointVector) {
.points(points.get())
}
@Entry
@Component
struct AnimatablePropertyExample {
@State points: PointVector = new PointVector([
new Point(50, Math.random() * 200),
new Point(100, Math.random() * 200),
new Point(150, Math.random() * 200),
new Point(200, Math.random() * 200),
new Point(250, Math.random() * 200),
])
build() {
Column() {
Polyline()
.animatablePoints(this.points)
.animation({duration: 1000, curve: "ease"})
.size({height:220, width:300})
.fill(Color.Green)
.stroke(Color.Red)
.backgroundColor('#eeaacc')
Button("Play")
.onClick(() => {
this.points = new PointVector([
new Point(50, Math.random() * 200),
new Point(100, Math.random() * 200),
new Point(150, Math.random() * 200),
new Point(200, Math.random() * 200),
new Point(250, Math.random() * 200),
])
})
}.width("100%")
.padding(10)
}
}

