ArkUI框架 - UI范式
ArkTS的基本組成
-
裝飾器:
用于裝飾類、結構、方法以及變量,并賦予其特殊的含義。如上述示例中@Entry、@Component和@State都是裝飾器,@Component表示自定義組件,@Entry表示該自定義組件為入口組件,@State表示組件中的狀態變量,狀態變量變化會觸發UI刷新。 -
UI描述:以聲明式的方式來描述UI的結構,例如build()方法中的代碼塊。
-
自定義組件:可復用的UI單元,可組合其他組件,如上述被@Component裝飾的struct Hello。
-
系統組件:ArkUI框架中默認內置的基礎和容器組件,可直接被開發者調用,比如示例中的Column、Text、Divider、Button。
-
屬性方法:組件可以通過鏈式調用配置多項屬性,如fontSize()、width()、height()、backgroundColor()等。
-
事件方法:組件可以通過鏈式調用設置多個事件的響應邏輯,如跟隨在Button后面的onClick()。
-
系統組件、屬性方法、事件方法具體使用可參考基于ArkTS的聲明式開發范式。
除此之外,ArkTS擴展了多種語法范式來使開發更加便捷:
-
@Builder/@BuilderParam:特殊的封裝UI描述的方法,細粒度的封裝和復用UI描述。
-
@Extend/@Styles:擴展內置組件和封裝屬性樣式,更靈活地組合內置組件。
-
stateStyles:多態樣式,可以依據組件的內部狀態的不同,設置不同樣式。
創建自定義組件
自定義組件具有以下特點:
- 可組合:允許開發者組合使用系統組件、及其屬性和方法。
- 可重用:自定義組件 可以被其他組件重用,并作為不同的實例在不同的父組件或容器中使用。
- 數據驅動UI更新:通過狀態變量的改變,來驅動UI的刷新。
自定義組件的基本用法
{ message: string = 'Hello, World!';build() {// HelloComponent自定義組件組合系統組件Row和TextRow() {Text(this.message).onClick(() => {// 狀態變量message的改變驅動UI刷新,UI從'Hello, World!'刷新為'Hello, ArkUI!'this.message = 'Hello, ArkUI!';})}}
}
struct HelloComponent
注意
如果在另外的文件中引用該自定義組件,需要使用export關鍵字導出,并在使用的頁面import該自定義組件。
HelloComponent可以在其他自定義組件中的build()函數中多次創建,實現自定義組件的重用。
{build() {Column() {Text('ArkUI message')HelloComponent({ message: 'Hello World!' });Divider()HelloComponent({ message: '你好,世界!' });}}
}
struct ParentComponent
頁面和自定義組件生命周期
自定義組件和頁面的關系:
- 自定義組件:@Component裝飾的UI單元,可以組合多個系統組件實現UI的復用,可以調用組件的生命周期。
- 頁面:即應用的UI頁面。可以由一個或者多個自定義組件組成,@Entry裝飾的自定義組件為頁面的入口組件,即頁面的根節點,一個頁面有且僅能有一個@Entry。只有被@Entry裝飾的組件才可以調用頁面的生命周期。
頁面生命周期,即被@Entry裝飾的組件生命周期,提供以下生命周期接口:
- onPageShow:頁面每次顯示時觸發一次,包括路由過程、應用進入前臺等場景。
- onPageHide:頁面每次隱藏時觸發一次,包括路由過程、應用進入后臺等場景。
- onBackPress:當用戶點擊返回按鈕時觸發。
組件生命周期,即一般用@Component裝飾的自定義組件的生命周期,提供以下生命周期接口:
-
aboutToAppear:組件即將出現時回調該接口,具體時機為在創建自定義組件的新實例后,在執行其build()函數之前執行。
-
onDidBuild:組件build()函數執行完成之后回調該接口,開發者可以在這個階段進行埋點數據上報等不影響實際UI的功能。不建議在onDidBuild函數中更改狀態變量、使用animateTo等功能,這可能會導致不穩定的UI表現。
-
aboutToDisappear:aboutToDisappear函數在自定義組件析構銷毀之前執行。不允許在aboutToDisappear函數中改變狀態變量,特別是@Link變量的修改可能會導致應用程序行為不穩定。
生命周期流程如下圖所示,下圖展示的是被@Entry裝飾的組件(頁面)生命周期。
循環渲染
ForEach接口基于數組類型數據來進行循環渲染,需要與容器組件配合使用
鍵值生成規則
在ForEach循環渲染過程中,系統會為每個數組元素生成一個唯一且持久的鍵值,用于標識對應的組件。當這個鍵值變化時,ArkUI框架將視為該數組元素已被替換或修改,并會基于新的鍵值創建一個新的組件。
ForEach提供了一個名為keyGenerator的參數,這是一個函數,開發者可以通過它自定義鍵值的生成規則。如果開發者沒有定義keyGenerator函數,則ArkUI框架會使用默認的鍵值生成函數,即(item: Object, index: number) => { return index + ‘__’ + JSON.stringify(item); }。
ArkUI框架對于ForEach的鍵值生成有一套特定的判斷規則,這主要與itemGenerator函數的第二個參數index以及keyGenerator函數的第二個參數index有關,具體的鍵值生成規則判斷邏輯如下圖所示。
正確渲染并保證效率的ForEach寫法是:
ForEach(this.simpleList, (item: string) => {ChildItem({ item: item })
}, (item: string) => item.id) // 需要保證key唯一
示例:在文章列表卡片上點擊“點贊”按鈕,從而修改文章的點贊數量。
Article類被@Observed裝飾器修飾。父組件ArticleListView傳入Article對象實例給子組件ArticleCard,子組件使用@ObjectLink裝飾器接收該實例。
當點擊第1個文章卡片上的點贊圖標時,會觸發ArticleCard組件的handleLiked函數。
該函數修改第1個卡片對應組件里article實例的isLiked和likesCount屬性值。
article實例是@ObjectLink裝飾的狀態變量,它的屬性值變化,觸發對應的ArticleCard組件渲染,讀取到的isLiked和likesCount為修改后的新值。
class Article {id: string;title: string;brief: string;isLiked: boolean;likesCount: number;constructor(id: string, title: string, brief: string, isLiked: boolean, likesCount: number) {this.id = id;this.title = title;this.brief = brief;this.isLiked = isLiked;this.likesCount = likesCount;}
}
struct ArticleListView { articleList: Array<Article> = [new Article('001', '第0篇文章', '文章簡介內容', false, 100),new Article('002', '第1篇文章', '文章簡介內容', false, 100),new Article('003', '第2篇文章', '文章簡介內容', false, 100),new Article('004', '第4篇文章', '文章簡介內容', false, 100),new Article('005', '第5篇文章', '文章簡介內容', false, 100),new Article('006', '第6篇文章', '文章簡介內容', false, 100),];build() {List() {ForEach(this.articleList, (item: Article) => {ListItem() {ArticleCard({article: item}).margin({ top: 20 })}}, (item: Article) => item.id)}.padding(20).scrollBar(BarState.Off).backgroundColor(0xF1F3F5)}
}
struct ArticleCard { article: Article;handleLiked() {this.article.isLiked = !this.article.isLiked;this.article.likesCount = this.article.isLiked ? this.article.likesCount + 1 : this.article.likesCount - 1;}build() {Row() {// 此處'app.media.icon'僅作示例,請開發者自行替換,否則imageSource創建失敗會導致后續無法正常執行。Image($r('app.media.icon')).width(80).height(80).margin({ right: 20 })Column() {Text(this.article.title).fontSize(20).margin({ bottom: 8 })Text(this.article.brief).fontSize(16).fontColor(Color.Gray).margin({ bottom: 8 })Row() {// 此處app.media.iconLiked','app.media.iconUnLiked'僅作示例,請開發者自行替換,否則imageSource創建失敗會導致后續無法正常執行。Image(this.article.isLiked ? $r('app.media.iconLiked') : $r('app.media.iconUnLiked')).width(24).height(24).margin({ right: 8 })Text(this.article.likesCount.toString()).fontSize(16)}.onClick(() => this.handleLiked()).justifyContent(FlexAlign.Center)}.alignItems(HorizontalAlign.Start).width('80%').height('100%')}.padding(20).borderRadius(12).backgroundColor('#FFECECEC').height(120).width('100%').justifyContent(FlexAlign.SpaceBetween)}
}
上述代碼的初始運行效果(左圖)和點擊第1個文章卡片上的點贊圖標后的運行效果(右圖)如下圖所示。