一.ArkTs語言介紹
1.ArkTs語言概述
ArkTs是目前鴻蒙開發(fā)的主要語言,目前鴻蒙的主推模型Stage的新版本(3.1.0(API 9))開發(fā)不支持Java和JavaScript了稼病,所以開發(fā)鴻蒙應(yīng)用學(xué)習(xí)ArkTs是很有必要的卓起。ArkTs是Ts(TypeScript)的擴(kuò)展赢织,后面的介紹就是介紹ArkTs的特性朝捆,Ts部分可以參考:https://www.tslang.cn/docs/handbook/typescript-in-5-minutes.html
2.ArkTs語言特性
當(dāng)前的ArkTs語言是在原先的Ts基礎(chǔ)上擴(kuò)展了聲明式UI特性憋他,目的就是簡化構(gòu)建和更新UI释牺,主要有以下特性:
基本UI描述:ArkTs定義了各種裝飾器、自定義組件微服、UI描述以及UI框架的內(nèi)置組件的事件方法和屬性方法等構(gòu)建成UI主體架構(gòu)趾疚。
狀態(tài)管理:ArkTs提供了多維度的狀態(tài)管理機(jī)制,在UI開發(fā)框架中以蕴,與界面綁定的數(shù)據(jù)不僅可以在同一組件中使用糙麦,還可以在不同組件中使用。比如:父子組件丛肮,爺孫組件赡磅,也可以在應(yīng)用全局范圍內(nèi)傳遞,甚至可以跨設(shè)備傳遞數(shù)據(jù)宝与。從數(shù)據(jù)流向來區(qū)分是可讀的單項數(shù)據(jù)傳遞和雙向變更數(shù)據(jù)傳遞焚廊。開發(fā)者可以靈活的利用現(xiàn)有的能力進(jìn)行數(shù)據(jù)與UI聯(lián)動。
動態(tài)UI構(gòu)建:ArkTs提供了動態(tài)UI構(gòu)建能力习劫,不僅可以自定義組件的內(nèi)部結(jié)構(gòu)咆瘟、還可以復(fù)用組件樣式、擴(kuò)展原生組件
渲染控制:ArkTs主要提供了條件渲染控制和循環(huán)渲染控制诽里,條件渲染主要就是根據(jù)條件渲染不同的UI內(nèi)容搞疗,循環(huán)渲染可以從數(shù)據(jù)源開始迭代獲取數(shù)據(jù),并在迭代過程創(chuàng)建UI組件
使用限制與擴(kuò)展:ArkTS在使用過程中存在限制與約束,同時也擴(kuò)展了雙向綁定等能力匿乃。
下面我們以一個具體的示例來說明ArkTS的基本組成桩皿。如下圖所示的代碼示例,UI界面包含一段文本和一個按鈕幢炸,當(dāng)開發(fā)者點擊按鈕時泄隔,文本內(nèi)容會從'Hello World'變?yōu)?'Hello ArkUI'。
這個示例中所包含的ArkTS聲明式開發(fā)范式的基本組成說明如下:
裝飾器: 用于裝飾類宛徊、結(jié)構(gòu)佛嬉、方法以及變量,賦予其特殊的含義闸天,如上述示例中@Entry暖呕、@Component和@State都是裝飾器。 具體而言苞氮,@Component表示這是個自定義組件湾揽;@Entry則表示這是個入口組件;@State表示這是組件中的狀態(tài)變量笼吟,這個變量變化會觸發(fā)UI刷新库物。
自定義組件:可復(fù)用的UI單元,可組合其他組件贷帮,如上述被@Component裝飾的struct Hello戚揭。
UI描述:聲明式的方法來描述UI的結(jié)構(gòu),例如build()方法中的代碼塊撵枢。
內(nèi)置組件:ArkTS中默認(rèn)內(nèi)置的基礎(chǔ)組件民晒、容器組件、媒體組件锄禽、繪制組件潜必、畫布組件等各種組件,開發(fā)者可以直接調(diào)用沟绪,如示例中的Column刮便、Text空猜、Divider绽慈、Button等。
屬性方法:用于組件屬性的配置辈毯,如fontSize()坝疼、width()、height()谆沃、color()等钝凶,可通過鏈?zhǔn)秸{(diào)用的方式設(shè)置多項屬性。
事件方法:用于添加組件對事件的響應(yīng)邏輯唁影,如跟隨在Button后面的onClick()耕陷,同樣可以通過鏈?zhǔn)秸{(diào)用的方式設(shè)置多個事件響應(yīng)邏輯掂名。
狀態(tài)修改:用于修改與UI綁定的數(shù)據(jù),通過@state修飾符修飾后當(dāng)狀態(tài)發(fā)生改變后對應(yīng)的UI界面也會發(fā)生改變哟沫。
二.基本UI描述
ArkTs通過裝飾器@Component與@Entry裝飾struct關(guān)鍵字生命的數(shù)據(jù)結(jié)構(gòu)饺蔑,構(gòu)造一個自定義組件。自定義組件中提供一個build函數(shù)嗜诀,開發(fā)者需在該函數(shù)內(nèi)以鏈?zhǔn)秸{(diào)用的方式進(jìn)行基本的UI描述猾警。
1.基本概念
struct:自定義組件可以基于struct實現(xiàn),不能用于繼承隆敢。
裝飾器:裝飾器給當(dāng)前被裝飾的對象賦予一定的能力发皿,其不僅可以裝飾結(jié)構(gòu)體或者類,還可以裝飾類的屬性拂蝎,多個裝飾器推薦多行方式編寫更加規(guī)范穴墅。
build函數(shù):自定義組件必須定義build函數(shù),并且禁止自定義構(gòu)造函數(shù)匣屡。build函數(shù)滿足Builder構(gòu)造器接口定義封救,用于定義組件的聲明式UI描述。
@Component:裝飾struct捣作,結(jié)構(gòu)體在裝飾后具有基于組件的能力誉结,需要實現(xiàn)build方法來創(chuàng)建UI。
@Entry: 裝飾struct券躁,組件被裝飾后作為頁面的入口惩坑,頁面加載時將被渲染顯示。
鏈?zhǔn)秸{(diào)用:以 "." 鏈?zhǔn)秸{(diào)用的方式配置UI組件的屬性方法也拜、事件方法等以舒。
2.UI描述規(guī)范
無參數(shù)構(gòu)造配置
如果組件的接口定義中不包含必選構(gòu)造參數(shù),組件后面的“()”中不需要配置任何內(nèi)容慢哈。例如蔓钟,Column和Divider組件不包含構(gòu)造參數(shù):
Column() {
??Text('Hello World')
????.fontSize(50)
????.fontWeight(FontWeight.Bold)
??Divider().width(200).height(10).color($r('app.color.button_next_background'))
??Text('Hello ArkTs')
????.fontSize(50)
????.fontWeight(FontWeight.Bold)
}
有參數(shù)構(gòu)造配置
如果組件的接口定義中包含構(gòu)造參數(shù),則在組件后面的“()”中可配置相應(yīng)參數(shù)卵贱,參數(shù)可以使用常量進(jìn)行賦值滥沫。
例如:
Image組件的必選參數(shù)src:
Image($r('app.media.icon'))
Text組件的參數(shù)content,該參數(shù)非必選键俱,即配置或不配置均可:
Text('test')
變量或表達(dá)式也可以用于參數(shù)賦值兰绣,其中表達(dá)式返回的結(jié)果類型必須滿足參數(shù)類型要求,變量的定義詳見頁面級變量的狀態(tài)管理與應(yīng)用級變量的狀態(tài)管理编振。例如缀辩,設(shè)置變量或表達(dá)式來構(gòu)造Image和Text組件的參數(shù):
Image(this.imagePath)
Image('https://'?+?this.imageUrl)
Text(`count: ${this.count}`)
屬性配置
使用屬性方法配置組件的屬性,屬性方法緊隨組件,并用"."運算符連接臀玄。
配置Text組件的字體大小屬性:
Text('test')
??.fontSize(12)
使用"."運算符進(jìn)行鏈?zhǔn)秸{(diào)用并同時配置組件的多個屬性瓢阴,如下所示:
Text("你好").fontSize(20).fontColor($r('app.color.button_next_background')).textAlign(TextAlign.End).width(200)
除了直接傳遞常量參數(shù)外,還可以傳遞變量或表達(dá)式健无,如下所示:
Text('hello')
??.fontSize(this.fontSize)
Image('test.jpg')
??.width(this.count %?2?===?0???100?:?200)???
??.height(this.offsetTest +?100)
對于系統(tǒng)內(nèi)置組件炫掐,框架還為其屬性預(yù)定義了一些枚舉類型供開發(fā)人員調(diào)用,枚舉類型可以作為參數(shù)傳遞睬涧,且必須滿足參數(shù)類型要求募胃。例如,可以按以下方式配置Text組件的顏色和字體屬性
Text('hello')
??.fontSize(20)
??.fontColor(Color.Red)
??.fontWeight(FontWeight.Bold)
事件配置
通過事件方法可以配置組件支持的事件畦浓,事件方法緊隨組件痹束,并用"."運算符連接。
使用lambda表達(dá)式配置組件的事件方法:
Button() {
??????????Text('Add')
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.onClick(() => {
??????????this.messageNum++
????????})
使用匿名函數(shù)表達(dá)式配置組件的事件方法讶请,要求使用bind(不使用bind點擊事件是無效的)祷嘶,以確保函數(shù)體中的this引用包含的組件:
Button() {
??????????Text('Add'+this.messageNum)
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.onClick(function (){
??????????this.messageNum++
????????}.bind(this))
使用組件的成員函數(shù)配置組件的事件方法:
Button() {
??????????Text('Add'?+?this.messageNum)
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.onClick(this.myClick.bind(this))
子組件配置
對于支持子組件配置的組件,例如容器組件夺溢,在"{ ... }"里為組件添加子組件的UI描述论巍。Column、Row风响、Stack嘉汰、Grid、List等組件都是容器組件状勤。
以下是簡單的Column示例:
Column() {
????????Button() {
??????????Text('Add'?+?this.messageNum)
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.onClick(this.myClick.bind(this))
????????Text("你好").fontSize(20).fontColor($r('app.color.button_next_background')).textAlign(TextAlign.End).width(200)
??????}
容器組件之間也可以互相嵌套鞋怀,實現(xiàn)相對復(fù)雜的多級嵌套效果:
Row() {
??Column() {
????this.TextBuild()
????Child({ count:?this.num })
????Child2({ count: $num })
????Button() {
??????Text('Back')
????????.fontSize(45)
????????.fontColor($r('app.color.start_window_background'))
????}
????.type(ButtonType.Capsule)
????.width('60%')
????.height('10%')
????.backgroundColor($r('app.color.button_next_background'))
????.onClick(() => {
??????router.back()
????})
????Button() {
??????Text(`Num++ ${this.num}`)
????????.fontSize(45)
????????.fontColor($r('app.color.start_window_background'))
????}
????.type(ButtonType.Capsule)
????.width('60%')
????.height('10%')
????.backgroundColor($r('app.color.button_next_background'))
????.margin({ top:?20?})
????.onClick(() => {
??????this.num++
????})
??}
??.width('100%')
}
.height('100%')
三.狀態(tài)管理
1.基本概念
ArkTS提供了多維度的狀態(tài)管理機(jī)制,在ArkUI開發(fā)框架中持搜,和UI相關(guān)聯(lián)的數(shù)據(jù)密似,不僅可以在組件內(nèi)使用,還可以在不同組件層級間傳遞葫盼,比如父子組件之間残腌、爺孫組件之間,也可以是應(yīng)用全局范圍內(nèi)的傳遞贫导。另外抛猫,從數(shù)據(jù)的傳遞形式來看,可分為只讀的單向傳遞和可變更的雙向傳遞脱盲。開發(fā)者可以靈活地利用這些能力來實現(xiàn)數(shù)據(jù)和UI的聯(lián)動邑滨。
2.頁面級狀態(tài)管理
頁面級狀態(tài)管理對比介紹
裝飾器裝飾內(nèi)容說明
@state基本數(shù)據(jù)類型日缨,類钱反,數(shù)組修改后的狀態(tài)數(shù)據(jù)被執(zhí)行時會執(zhí)行自定義組件中與此狀態(tài)變量相關(guān)的UI元素,更新UI界面
@Prop基本數(shù)據(jù)類型,類面哥,數(shù)組修改后的狀態(tài)數(shù)據(jù)用于父組件與子組件之間建立單項數(shù)據(jù)流的綁定關(guān)系哎壳,當(dāng)父組件中狀態(tài)數(shù)據(jù)發(fā)生變化時子組件與之相關(guān)的UI元素也會進(jìn)行頁面更新
@Link基本數(shù)據(jù)類型,類尚卫,數(shù)組父組件與子組件建立雙向的數(shù)據(jù)流綁定關(guān)系归榕,當(dāng)任意一方數(shù)據(jù)發(fā)生改變另一方與之相關(guān)的UI元素都會進(jìn)行頁面更新,將最新的數(shù)據(jù)狀態(tài)展示出來
@Observed類@Observed應(yīng)用于類吱涉,表示該類中的數(shù)據(jù)變更被UI頁面管理
@ObjectLink被@Observed所裝飾類的對象@ObjectLink裝飾的狀態(tài)數(shù)據(jù)被修改時刹泄,在父組件或者其他兄弟組件內(nèi)與它關(guān)聯(lián)的狀態(tài)數(shù)據(jù)所在的組件都會重新渲染。
@Provide基本數(shù)據(jù)類型怎爵,類特石,數(shù)組@Provide作為數(shù)據(jù)的提供方,可以更新其子孫節(jié)點的數(shù)據(jù)鳖链,并觸發(fā)頁面重新渲染姆蘸。
@Consume基本數(shù)據(jù)類型,類芙委,數(shù)組@Consume裝飾的變量在感知到@Provide裝飾的變量更新后逞敷,會觸發(fā)當(dāng)前自定義組件的重新渲染。
@State
@State裝飾的變量是組件內(nèi)部的狀態(tài)數(shù)據(jù)灌侣,當(dāng)這些狀態(tài)數(shù)據(jù)被修改時推捐,將會調(diào)用所在組件的build方法中的部分UI描述(使用該狀態(tài)變量的UI組件相關(guān)描述)進(jìn)行UI刷新。
@State狀態(tài)數(shù)據(jù)具有以下特征:
支持多種類型數(shù)據(jù):支持class侧啼、number玖姑、boolean、string強(qiáng)類型數(shù)據(jù)的值類型和引用類型慨菱,以及這些強(qiáng)類型構(gòu)成的數(shù)組焰络,即Array<class>、Array<string>符喝、Array<boolean>闪彼、Array<number>。不支持any协饲。
支持多實例:組件不同實例的內(nèi)部狀態(tài)數(shù)據(jù)獨立畏腕。
內(nèi)部私有:標(biāo)記為@State的屬性是私有變量,只能在組件內(nèi)訪問茉稠。
需要本地初始化:必須為所有@State變量分配初始值描馅,變量未初始化可能導(dǎo)致未定義的框架異常行為。
創(chuàng)建自定義組件時支持通過狀態(tài)變量名設(shè)置初始值:在創(chuàng)建組件實例時而线,可以通過變量名顯式指定@State狀態(tài)變量的初始值铭污。
現(xiàn)在我們來做一個示例恋日,主要邏輯是在自定義組件MyComponent設(shè)置兩個狀態(tài)值為state的值count與title,如下:
// xxx.ets
@Entry
@Component
struct EntryComponent {
??build() {
????Column() {
??????MyComponent({ count:?1, increaseBy:?2?})?// 第1個MyComponent實例
??????MyComponent({ title:?'Hello World 2', count:?7?})?// 第2個MyComponent實例
????}
??}
}
@Component
struct MyComponent {
??@State?count: number =?0
??@State?title: string =?'Hello World'
??private?increaseBy: number =?1
??build() {
????Column() {
??????Text(this.title)
??????Button('Click to change title')
????????.margin(20)
????????.onClick(() => {
??????????// 修改內(nèi)部狀態(tài)變量title
??????????this.title = (this.title !=?'Hello World') ??'Hello World'?:?'Hello ArkUI'
????????})
??????Button(`Click to increase count=${this.count}`)
????????.margin(20)
????????.onClick(() => {
??????????// 修改內(nèi)部狀態(tài)變量count
??????????this.count +=?this.increaseBy
????????})
????}
??}
}
上面的代碼放在編輯器中嘗試是可以看出點擊Click to change title事件MyComponent是進(jìn)行界面更新的并且兩個MyComponent是相互不干擾的嘹狞,同理點擊Click to increase count=也是如此
@Prop
@Prop與@State有相同的語義岂膳,但初始化方式不同。@Prop裝飾的變量必須使用其父組件提供的@State變量進(jìn)行初始化磅网,允許組件內(nèi)部修改@Prop變量谈截,但變量的更改不會通知給父組件,父組件變量的更改會同步到@prop裝飾的變量涧偷,即@Prop屬于單向數(shù)據(jù)綁定簸喂。
@Prop狀態(tài)數(shù)據(jù)具有以下特征:
支持多種類型數(shù)據(jù):支持class、number燎潮、boolean娘赴、string強(qiáng)類型數(shù)據(jù)的值類型和引用類型,以及這些強(qiáng)類型構(gòu)成的數(shù)組跟啤,即Array<class>诽表、Array<string>、Array<boolean>隅肥、Array<number>竿奏。不支持any。
私有:僅支持組件內(nèi)訪問腥放;
單個數(shù)據(jù)源:父組件中用于初始化子組件@Prop變量的必須是父組件定義的狀態(tài)變量泛啸;
單向通信:子組件對@Prop變量的更改將不會同步修改父組件中的@State變量,父組件中@State的變量修改將會同步給子組件中的@Prop變量秃症;
創(chuàng)建自定義組件時將按值傳遞方式給@Prop變量進(jìn)行初始化:在創(chuàng)建組件的新實例時候址,必須初始化所有@Prop變量,不支持在組件內(nèi)部進(jìn)行初始化种柑。
此處示例與下一個@Link裝飾符一起演示岗仑,可以看出明顯區(qū)別
@Link
@Link裝飾的變量可以和父組件的@State變量建立雙向數(shù)據(jù)綁定:
支持多種類型:@Link支持的數(shù)據(jù)類型與@State相同,即class聚请、number荠雕、string、boolean或這些類型的數(shù)組驶赏;
私有:僅支持組件內(nèi)訪問炸卑;
單個數(shù)據(jù)源:父組件中用于初始化子組件@Link變量的必須是父組件定義的狀態(tài)變量;
雙向通信:子組件對@Link變量的更改將同步修改父組件中的@State變量煤傍;
創(chuàng)建自定義組件時需要將變量的引用傳遞給@Link變量盖文,在創(chuàng)建組件的新實例時,必須使用命名參數(shù)初始化所有@Link變量蚯姆。@Link變量可以使用@State變量或@Link變量的引用進(jìn)行初始化五续,@State變量可以通過'$'操作符創(chuàng)建引用洒敏。
@Link變量不能在組件內(nèi)部進(jìn)行初始化。
下面的示例是演示Child組件中的count變量是@Prop修飾的返帕,Child2組件中的count變量是@Link修飾的,當(dāng)點擊Child組件的按鈕時會發(fā)現(xiàn)只有Child組件count數(shù)據(jù)更新并更新了界面篙挽,當(dāng)點擊Child2組件的按鈕時會發(fā)現(xiàn)所有的count數(shù)據(jù)都更新并且界面更新了荆萤,當(dāng)點擊父組件的按鈕也會發(fā)現(xiàn)所有的組件中count數(shù)據(jù)發(fā)生了更新并且界面發(fā)生更新了
import?router from?'@ohos.router';
@Entry
@Component
struct Second {
??@State?message: string =?'Hello World Second'
??@State?num: number =?1
??build() {
????Row() {
??????Column() {
????????this.TextBuild()
????????Child({ count:?this.num })
????????Child2({ count: $num })
????????Button() {
??????????Text('Back')
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.type(ButtonType.Capsule)
????????.width('60%')
????????.height('10%')
????????.backgroundColor($r('app.color.button_next_background'))
????????.onClick(() => {
??????????router.back()
????????})
????????Button() {
??????????Text(`Num++ ${this.num}`)
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.type(ButtonType.Capsule)
????????.width('60%')
????????.height('10%')
????????.backgroundColor($r('app.color.button_next_background'))
????????.margin({ top:?20?})
????????.onClick(() => {
??????????this.num++
????????})
??????}
??????.width('100%')
????}
????.height('100%')
??}
??@Builder?TextBuild(){
????Text(this.message)
??????.align(Alignment.Center)
??????.fontSize(40)
??????.fontWeight(FontWeight.Bold)
??}
}
@Component
struct Child {
??//父組件可以改變子組件,子組件不能改變父組件
??@Prop?count: number
??build() {
????Column() {
??????Text(`You have ${this.count} Nuggets left`).fontSize(18)
??????Button('count - costOfOneAttempt')
????????.margin(15)
????????.onClick(() => {
??????????this.count--
????????})
????}
??}
}
@Component
struct Child2 {
??//父組件與子組件雙向綁定
??@Link?count: number
??build() {
????Column() {
??????Text(`You have ${this.count} Nuggets left`).fontSize(18)
??????Button('count - costOfOneAttempt')
????????.margin(15)
????????.onClick(() => {
??????????this.count--
????????})
????}
??}
}
@Observed和ObjectLink數(shù)據(jù)管理
當(dāng)開發(fā)者需要在子組件中針對父組件的一個變量(parent_a)設(shè)置雙向同步時铣卡,開發(fā)者可以在父組件中使用@State裝飾變量(parent_a)链韭,并在子組件中使用@Link裝飾對應(yīng)的變量(child_a)。這樣不僅可以實現(xiàn)父組件與單個子組件之間的數(shù)據(jù)同步煮落,也可以實現(xiàn)父組件與多個子組件之間的數(shù)據(jù)同步敞峭。如下圖所示,可以看到蝉仇,父子組件針對ClassA類型的變量設(shè)置了雙向同步旋讹,那么當(dāng)子組件1中變量對應(yīng)的屬性c的值變化時,會通知父組件同步變化轿衔,而當(dāng)父組件中屬性c的值變化時沉迹,會通知所有子組件同步變化。而當(dāng)需要傳遞數(shù)組其中一個實例時害驹,使用@Link就不能滿足要求鞭呕。如果這些部分信息是一個類對象,就可以使用@ObjectLink配合@Observed來實現(xiàn)
設(shè)置要求:
@Observed用于類宛官,@ObjectLink用于變量葫松。
@ObjectLink裝飾的變量類型必須為類(class type)。
類要被@Observed裝飾器所裝飾底洗。
不支持簡單類型參數(shù)腋么,可以使用@Prop進(jìn)行單向同步。
@ObjectLink裝飾的變量是不可變的亥揖。
屬性的改動是被允許的党晋,當(dāng)改動發(fā)生時,如果同一個對象被多個@ObjectLink變量所引用徐块,那么所有擁有這些變量的自定義組件都會被通知進(jìn)行重新渲染未玻。
@ObjectLink裝飾的變量不可設(shè)置默認(rèn)值。
必須讓父組件中有一個由@State胡控、@Link扳剿、@StorageLink、@Provide或@Consume裝飾的變量所參與的TS表達(dá)式進(jìn)行初始化昼激。
@ObjectLink裝飾的變量是私有變量庇绽,只能在組件內(nèi)訪問锡搜。
示例與下面的修飾符一起演示
@Provide和@Consume
@Provide作為數(shù)據(jù)的提供方,可以更新其子孫節(jié)點的數(shù)據(jù)瞧掺,并觸發(fā)頁面渲染耕餐。@Consume在感知到@Provide數(shù)據(jù)的更新后,會觸發(fā)當(dāng)前自定義組件的重新渲染辟狈。
@Provide
裝飾器參數(shù)是一個string類型的常量肠缔,用于給裝飾的變量起別名。如果規(guī)定別名哼转,則提供對應(yīng)別名的數(shù)據(jù)更新明未。如果沒有,則使用變量名作為別名壹蔓。推薦使用@Provide('alias')這種形式趟妥。
同步機(jī)制:@Provide的變量類似@State,可以修改對應(yīng)變量進(jìn)行頁面重新渲染佣蓉。也可以修改@Consume裝飾的變量披摄,反向修改@State變量。
必須設(shè)置初始值勇凭。
觸發(fā)頁面渲染的修改:基礎(chǔ)類型(boolean行疏,string,number)變量的改變套像;@Observed class類型變量及其屬性的修改酿联;添加,刪除夺巩,更新數(shù)組中的元素贞让。
@Consume
不可設(shè)置默認(rèn)初始值。
示例如下:
import?router from?'@ohos.router';
@Entry
@Component
struct Third {
??//因為是數(shù)組所以需要用到@ObjectLink和@Observed
??@State?arrA: ClassA[] = [new?ClassA(2),?new?ClassA(0)]
??//此修飾符與@Consume組合可以讓其與孫子節(jié)點數(shù)據(jù)雙向綁定
??@Provide("reviewVote") reviewVotes: number =?0;
??build() {
????Row() {
??????Column() {
????????ForEach(this.arrA, (item) => {
??????????TextChild({ a: item })
????????}, (item) => item.id.toString())
????????ForEach(this.arrA, (item) => {
??????????Child({ a: item })
????????}, (item) => item.id.toString())
????????Button() {
??????????Text('Back')
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.type(ButtonType.Capsule)
????????.width('60%')
????????.height('10%')
????????.backgroundColor($r('app.color.button_next_background'))
????????.onClick(() => {
??????????router.back()
????????})
????????Button() {
??????????Text('Add')
????????????.fontSize(45)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.type(ButtonType.Capsule)
????????.width('60%')
????????.height('5%')
????????.backgroundColor($r('app.color.button_next_background'))
????????.margin({ top:?20?})
????????.onClick(() => {
??????????this.arrA[0].c++
????????})
????????Button() {
??????????Text('AddChildChild'+this.reviewVotes)
????????????.fontSize(25)
????????????.fontColor($r('app.color.start_window_background'))
????????}
????????.type(ButtonType.Capsule)
????????.width('60%')
????????.height('10%')
????????.backgroundColor($r('app.color.button_next_background'))
????????.margin({ top:?20?})
????????.onClick(() => {
??????????this.reviewVotes++
????????})
??????}
??????.width('100%')
????}
????.height('100%')
??}
}
@Component
struct TextChild {
??@ObjectLink?a: ClassA
??build() {
????Column() {
??????Text(this.a.c +?"TextChild")
????????.align(Alignment.Center)
????????.fontSize(40)
????????.fontWeight(FontWeight.Bold)
??????TextChildChild()
????}
??}
}
@Component
struct TextChildChild {
??//此修飾符與爺爺組件的@Provide組合可以與爺爺組件雙向綁定
??@Consume("reviewVote") reviewVotes: number
??build() {
????Column() {
??????Button() {
????????Text('RemoveChildChild'+this.reviewVotes)
??????????.fontSize(20)
??????????.fontColor($r('app.color.start_window_background'))
??????}
??????.type(ButtonType.Capsule)
??????.width('60%')
??????.height('5%')
??????.backgroundColor($r('app.color.button_next_background'))
??????.margin({ top:?20?})
??????.onClick(() => {
????????this.reviewVotes--
??????})
??????Text(this.reviewVotes +?"TextChildChild")
????????.align(Alignment.Center)
????????.fontSize(40)
????????.fontWeight(FontWeight.Bold)
????}
??}
}
@Component
struct Child {
??@ObjectLink?a: ClassA
??build() {
????Column() {
??????Text(this.a.c +?"Child")
????????.align(Alignment.Center)
????????.fontSize(40)
????????.fontWeight(FontWeight.Bold)
??????Button('count - costOfOneAttempt')
????????.margin(15)
????????.onClick(() => {
??????????this.a.c--
????????})
????}
??}
}
var nextID: number =?0
@Observed
class?ClassA {
??public?name: string
??public?c: number
??public?id: number
??constructor(c: number, name: string =?'OK') {
????this.name = name
????this.c = c
????this.id = nextID++
??}
}
@Watch
@Watch用于監(jiān)聽狀態(tài)變量的變化柳譬,語法結(jié)構(gòu)為:
@State?@Watch("onChanged") count :?number?=?0
如上所示喳张,給狀態(tài)變量增加一個@Watch裝飾器,通過@Watch注冊一個回調(diào)方法onChanged美澳, 當(dāng)狀態(tài)變量count被改變時销部, 觸發(fā)onChanged回調(diào)。
裝飾器@State制跟、@Prop舅桩、@Link、@ObjectLink雨膨、@Provide擂涛、@Consume、@StorageProp以及@StorageLink所裝飾的變量均可以通過@Watch監(jiān)聽其變化聊记。
import?router from?'@ohos.router';
// xxx.ets
@Entry
@Component
struct CompA {
??@State?@Watch('onBasketUpdated') shopBasket: Array<number> = [7,?12,?47,?3]
??@State?totalPurchase: number =?0
??@State?addPurchase: number =?0
??aboutToAppear() {
????this.updateTotal()
??}
??updateTotal():?void?{
????let sum =?0;
????this.shopBasket.forEach((i) => {
??????sum += i
????})
????// 計算新的購物籃總價值撒妈,如果超過100恢暖,則適用折扣
????this.totalPurchase = (sum <?100) ? sum :?0.9?* sum
??}
??// shopBasket更改時觸發(fā)該方法
??onBasketUpdated(propName: string):?void?{
????this.updateTotal()
??}
??build() {
????Column() {
??????Button('add to basket '?+?this.addPurchase)
????????.margin(15)
????????.onClick(() => {
??????????this.addPurchase = Math.round(100?* Math.random())
??????????this.shopBasket.push(this.addPurchase)
????????})
??????Text(`${this.totalPurchase}`)
????????.fontSize(30)
??????Button() {
????????Text('Back')
??????????.fontSize(45)
??????????.fontColor($r('app.color.start_window_background'))
??????}
??????.type(ButtonType.Capsule)
??????.width('60%')
??????.height('10%')
??????.backgroundColor($r('app.color.button_next_background'))
??????.onClick(() => {
????????router.back()
??????})
????}
??}
}
3.應(yīng)用級變量的狀態(tài)管理
AppStorage
AppStorage是應(yīng)用程序中的單例對象,由UI框架在應(yīng)用程序啟動時創(chuàng)建狰右,在應(yīng)用程序退出時銷毀杰捂,為應(yīng)用程序范圍內(nèi)的可變狀態(tài)屬性提供中央存儲。
AppStorage包含整個應(yīng)用程序中需要訪問的所有狀態(tài)屬性棋蚌,只要應(yīng)用程序保持運行嫁佳,AppStorage就會保存所有屬性及屬性值,屬性值可以通過唯一的鍵值進(jìn)行訪問附鸽。
組件可以通過裝飾器將應(yīng)用程序狀態(tài)數(shù)據(jù)與AppStorage進(jìn)行同步脱拼,應(yīng)用業(yè)務(wù)邏輯的實現(xiàn)也可以通過接口訪問AppStorage瞒瘸。
AppStorage的選擇狀態(tài)屬性可以與不同的數(shù)據(jù)源或數(shù)據(jù)接收器同步坷备,這些數(shù)據(jù)源和接收器可以是設(shè)備上的本地或遠(yuǎn)程,并具有不同的功能情臭,如數(shù)據(jù)持久性省撑。這樣的數(shù)據(jù)源和接收器可以獨立于UI在業(yè)務(wù)邏輯中實現(xiàn)。
默認(rèn)情況下俯在,AppStorage中的屬性是可變的竟秫,AppStorage還可使用不可變(只讀)屬性。
@StorageLink裝飾器
組件通過使用@StorageLink(key)裝飾的狀態(tài)變量跷乐,與AppStorage建立雙向數(shù)據(jù)綁定肥败,key為AppStorage中的屬性鍵值。當(dāng)創(chuàng)建包含@StorageLink的狀態(tài)變量的組件時愕提,該狀態(tài)變量的值將使用AppStorage中的值進(jìn)行初始化馒稍。在UI組件中對@StorageLink的狀態(tài)變量所做的更改將同步到AppStorage,并從AppStorage同步到任何其他綁定實例中,如PersistentStorage或其他綁定的UI組件。
@StorageProp裝飾器
組件通過使用@StorageProp(key)裝飾的狀態(tài)變量含滴,與AppStorage建立單向數(shù)據(jù)綁定乞而,key標(biāo)識AppStorage中的屬性鍵值。當(dāng)創(chuàng)建包含@StorageProp的狀態(tài)變量的組件時键耕,該狀態(tài)變量的值將使用AppStorage中的值進(jìn)行初始化。AppStorage中屬性值的更改會導(dǎo)致綁定該狀態(tài)變量的UI組件進(jìn)行狀態(tài)更新。
示例
每次用戶單擊Count按鈕時澳化,this.varA變量值都會增加1,此變量與AppStorageA中的varA同步稳吮。每次用戶單擊language按鈕時肆捕,修改AppStorageA中的languageCode,此修改會同步給this.languageCode變量盖高。點擊跳轉(zhuǎn)到AppStorageB慎陵,會發(fā)現(xiàn)varA的值和languageCode的值是傳遞過去了眼虱。
import?router from?'@ohos.router';
// xxx.ets
@Entry
@Component
struct AppStorageA {
??@StorageLink('varA') varA: number =?2
??@StorageProp('languageCode') languageCode: string =?'en'
??@State?private?label: string =?'count'
??build() {
????Column() {
??????Row({ space:?20?}) {
????????Button(`${this.label}: ${this.varA}`)
??????????.onClick(() => {
????????????AppStorage.Set<number>('varA', AppStorage.Get<number>('varA') +?1)
??????????})
????????Button(`language: ${this.languageCode}`)
??????????.onClick(() => {
????????????if?(AppStorage.Get<string>('languageCode') ===?'zh') {
??????????????AppStorage.Set<string>('languageCode',?'en')
????????????}?else?{
??????????????AppStorage.Set<string>('languageCode',?'zh')
????????????}
????????????this.label = (this.languageCode ===?'zh') ??'數(shù)量'?:?'Count'
??????????})
??????}
??????.margin({ bottom:?50?})
??????Row() {
????????Button(`更改@StorageLink修飾的變量:${this.varA}`).height(40).fontSize(14)
??????????.onClick(() => {
????????????this.varA++
??????????})
??????}.margin({ bottom:?50?})
??????Row() {
????????Button(`更改@StorageProp修飾的變量:${this.languageCode}`).height(40).fontSize(14)
??????????.onClick(() => {
????????????if?(this.languageCode ===?'zh') {
??????????????this.languageCode =?'en'
????????????}?else?{
??????????????this.languageCode =?'zh'
????????????}
??????????})
??????}.margin({bottom:100})
??????Row() {
????????Button(`跳轉(zhuǎn)到AppStorageB`).height(40).fontSize(14)
??????????.onClick(() => {
????????????router.pushUrl({url:'pages/AppStorageB'})
??????????})
??????}
????}
??}
}
@Entry
@Component
struct AppStorageB {
??@StorageLink('varA') varA: number =?2
??@StorageProp('languageCode') languageCode: string =?'en'
??@State?private?label: string =?'count'
??build() {
????Column() {
??????Text(`language: ${this.languageCode}`).margin({top:100})
??????Text(`varA: ${this.varA}`).margin({top:100})
????}.height('100%').width('100%')
??}
}
LocalStorage
LocalStorage是應(yīng)用程序中的存儲單元,生命周期跟隨其關(guān)聯(lián)的Ability席纽。在Stage模型下捏悬,LocalStorage解決AppStorage共享范圍過大的問題,提供Ability之間全局?jǐn)?shù)據(jù)的隔離润梯。同時过牙,LocalStorage為應(yīng)用程序范圍內(nèi)的可變狀態(tài)屬性和非可變狀態(tài)屬性提供存儲,可變狀態(tài)屬性和非可變狀態(tài)屬性是構(gòu)建應(yīng)用程序UI的一部分纺铭,如一個Ability的UI寇钉。解決App與Ability之間數(shù)據(jù)互相干擾問題,多實例場景下同一個Ability類的不同Ability實例之間的數(shù)據(jù)互相干擾問題舶赔。在分布式遷移的場景下扫倡,Ability是系統(tǒng)調(diào)度的最小單元,配合LocalStorage更方便實現(xiàn)組件的數(shù)據(jù)遷移竟纳。
應(yīng)用層:一個應(yīng)用程序可以創(chuàng)建多個LocalStorage實例撵溃,應(yīng)用程序的每一個Ability對應(yīng)一個LocalStorage實例。
Ability:一個應(yīng)用程序可以擁有多個Ability锥累,一個Ability中的所有子組件最多可以分配一個LocalStorage實例缘挑。并且,Ability中的所有子組件都將繼承對此LocalStorage實例存儲對象的訪問權(quán)桶略。
一個組件最多可以訪問一個LocalStorage實例语淘,一個LocalStorage對象可以分配給多個組件。
@LocalStorageLink裝飾器
組件通過使用@LocalStorageLink(key)裝飾的狀態(tài)變量际歼,key值為LocalStorage中的屬性鍵值惶翻,與LocalStorage建立雙向數(shù)據(jù)綁定。當(dāng)創(chuàng)建包含@LocalStorageLink的狀態(tài)變量的組件時蹬挺,該狀態(tài)變量的值將會使用LocalStorage中的值進(jìn)行初始化维贺。如果LocalStorage中未定義初始值,將使用@LocalStorageLink定義的初始值巴帮。在UI組件中對@LocalStorageLink的狀態(tài)變量所做的更改將同步到LocalStorage中溯泣,并從LocalStorage同步到Ability下的組件中。
@LocalStorageProp裝飾器
組件通過使用LocalStorageProp(key)裝飾的狀態(tài)變量榕茧,key值為LocalStorage中的屬性鍵值垃沦,與LocalStorage建立單向數(shù)據(jù)綁定。當(dāng)創(chuàng)建包含@LocalStorageProp的狀態(tài)變量的組件時用押,該狀態(tài)變量的值將使用LocalStorage中的值進(jìn)行初始化肢簿。LocalStorage中的屬性值的更改會導(dǎo)致當(dāng)前Ability下的所有UI組件進(jìn)行狀態(tài)更新。
具體用法參考:
import?UIAbility from?'@ohos.app.ability.UIAbility';
import?hilog from?'@ohos.hilog';
import?window from?'@ohos.window';
export?default?class?EntryAbility?extends?UIAbility {
????storage: LocalStorage
????onCreate(want, launchParam) {
????????this.storage =?new?LocalStorage()
????????this.storage.setOrCreate('storageSimpleProp',?8)
????????hilog.info(0x0000,?'testTag',?'%{public}s',?'Ability onCreate');
????}
????onDestroy() {
????????hilog.info(0x0000,?'testTag',?'%{public}s',?'Ability onDestroy');
????}
????onWindowStageCreate(windowStage: window.WindowStage) {
????????// Main window is created, set main page for this ability
????????hilog.info(0x0000,?'testTag',?'%{public}s',?'Ability onWindowStageCreate');
????????windowStage.loadContent('pages/LocalStorageComponent',?this.storage)
????}
}
import?router from?'@ohos.router';
// xxx.ets
// Index.ets
let storage = LocalStorage.GetShared()
@Entry(storage)
@Component
struct LocalStorageComponent {
??@LocalStorageLink('storageSimpleProp') simpleVarName: number =?0
??build() {
????Column() {
??????Button(`LocalStorageLink: ${this.simpleVarName.toString()}`)
????????.margin(20)
????????.onClick(() => {
??????????this.simpleVarName +=?1
????????})
??????Text(JSON.stringify(this.simpleVarName))
????????.fontSize(50)
??????LocalStorageComponentProp()
????}.width('100%')
??}
}
@Component
struct LocalStorageComponentProp {
??@LocalStorageProp('storageSimpleProp') simpleVarName: number =?0
??build() {
????Column() {
??????Button(`LocalStorageProp: ${this.simpleVarName.toString()}`)
????????.margin(20)
????????.onClick(() => {
??????????this.simpleVarName +=?1
????????})
??????Text(JSON.stringify(this.simpleVarName))
????????.fontSize(50)
????}.width('100%')
??}
}
PersistentStorage
PersistentStorage提供了一些靜態(tài)方法用來管理應(yīng)用持久化數(shù)據(jù),可以將特定標(biāo)記的持久化數(shù)據(jù)鏈接到AppStorage中池充,并由AppStorage接口訪問對應(yīng)持久化數(shù)據(jù)桩引,或者通過@StorageLink裝飾器來訪問對應(yīng)key的變量。
// xxx.ets
PersistentStorage.PersistProp('highScore',?10)
@Entry
@Component
struct PersistentComponent {
??@StorageLink('highScore') highScore: number =?0
??@State?currentScore: number =?0
??build() {
????Column() {
??????if?(this.currentScore === Number(this.highScore)) {
????????Text(`new?highScore : ${this.highScore}`).fontSize(18)
??????}
??????Button(`goal!, currentScore : ${this.currentScore}`)
????????.margin(20)
????????.onClick(() => {
??????????this.currentScore++
??????????if?(this.currentScore > Number(this.highScore)) {
????????????this.highScore =?this.currentScore
??????????}
????????})
????}.width('100%')
??}
}
Environment
Environment是框架在應(yīng)用程序啟動時創(chuàng)建的單例對象收夸,它為AppStorage提供了一系列應(yīng)用程序需要的環(huán)境狀態(tài)數(shù)據(jù)坑匠,這些數(shù)據(jù)描述了應(yīng)用程序運行的設(shè)備環(huán)境,包括系統(tǒng)語言卧惜、深淺色模式等等厘灼。Environment及其屬性是不可變的,所有數(shù)據(jù)類型均為簡單類型咽瓷。如下示例展示了從Environment獲取系統(tǒng)是否開啟無障礙屏幕朗讀:
Environment.EnvProp('accessibilityEnabled',?'default')
var enable = AppStorage.Get('accessibilityEnabled')
accessibilityEnabled是Environment提供的系統(tǒng)默認(rèn)變量識別符设凹。首先需要將對應(yīng)系統(tǒng)屬性綁定到AppStorage上,再通過AppStorage中的方法或者裝飾器訪問對應(yīng)的系統(tǒng)屬性數(shù)據(jù)茅姜。
四.動態(tài)構(gòu)建UI元素
基本UI描述介紹的是如何創(chuàng)建一個內(nèi)部UI結(jié)構(gòu)固定的自定義組件闪朱,為了滿足開發(fā)者自定義組件內(nèi)部UI結(jié)構(gòu)的需求,ArkTS同時提供了動態(tài)構(gòu)建UI元素的能力匈睁。
@Builder
可通過@Builder裝飾器進(jìn)行描述监透,該裝飾器可以修飾一個函數(shù)桶错,此函數(shù)可以在build函數(shù)之外聲明航唆,并在build函數(shù)中或其他@Builder修飾的函數(shù)中使用,從而實現(xiàn)在一個自定義組件內(nèi)快速生成多個布局內(nèi)容院刁。使用方式如下面示例所示糯钙。
@Entry
@Component
struct Second {
??@State?message: string =?'Hello World Second'
??@State?num: number =?1
??build() {
????Row() {
??????Column() {
????????this.TextBuild()
??????}
??????.width('100%')
????}
????.height('100%')
??}
??@Builder?TextBuild(){
????Text(this.message)
??????.align(Alignment.Center)
??????.fontSize(40)
??????.fontWeight(FontWeight.Bold)
??}
}
@BuilderParam8+
@BuilderParam裝飾器用于修飾自定義組件內(nèi)函數(shù)類型的屬性(例如:@BuilderParam noParam: () => void),并且在初始化自定義組件時被@BuilderParam修飾的屬性必須賦值退腥。
引入動機(jī)
當(dāng)開發(fā)者創(chuàng)建自定義組件任岸,并想對該組件添加特定功能時(例如在自定義組件中添加一個點擊跳轉(zhuǎn)操作)。若直接在組件內(nèi)嵌入事件方法狡刘,將會導(dǎo)致所有引入該自定義組件的地方均增加了該功能享潜。為解決此問題,引入了@BuilderParam裝飾器嗅蔬,此裝飾器修飾的屬性值可為@Builder裝飾的函數(shù)剑按,開發(fā)者可在初始化自定義組件時對此屬性進(jìn)行賦值,為自定義組件增加特定的功能澜术。
參數(shù)初始化組件
通過參數(shù)初始化組件時艺蝴,將@Builder裝飾的函數(shù)賦值給@BuilderParam修飾的屬性,并在自定義組件內(nèi)調(diào)用該屬性值鸟废。若@BuilderParam修飾的屬性在進(jìn)行賦值時不帶參數(shù)(如:noParam: this.specificNoParam)猜敢,則此屬性的類型需定義為無返回值的函數(shù)(如:@BuilderParam noParam: () => void);若帶參數(shù)(如:withParam: this.SpecificWithParam('WithParamA')),則此屬性的類型需定義成any(如:@BuilderParam withParam: any)缩擂。
// xxx.ets
@Component
struct CustomContainer {
??header: string =?''
??@BuilderParam?noParam: () =>?void
??@BuilderParam?withParam: any
??footer: string =?''
??build() {
????Column() {
??????Text(this.header)
????????.fontSize(30)
??????this.noParam()
??????this.withParam()
??????Text(this.footer)
????????.fontSize(30)
????}
??}
}
@Entry
@Component
struct CustomContainerUser {
??@Builder?specificNoParam() {
????Column() {
??????Text('noParam').fontSize(30)
????}
??}
??@Builder?SpecificWithParam(label: string) {
????Column() {
??????Text(label).fontSize(30)
????}
??}
??build() {
????Column() {
??????CustomContainer({
????????header:?'HeaderA',
????????noParam:?this.specificNoParam,
????????withParam:?this.SpecificWithParam('WithParamA'),
????????footer:?'FooterA'
??????})
??????Divider().color(Color.Black)
????????.strokeWidth(3)
????????.margin(10)
??????CustomContainer({
????????header:?'HeaderB',
????????noParam:?this.specificNoParam,
????????withParam:?this.SpecificWithParam('WithParamB'),
????????footer:?'FooterB'
??????})
????}
??}
}
@Styles
ArkTS為了避免開發(fā)者對重復(fù)樣式的設(shè)置鼠冕,通過@Styles裝飾器可以將多個樣式設(shè)置提煉成一個方法,直接在組件聲明時調(diào)用胯盯,通過@Styles裝飾器可以快速定義并復(fù)用自定義樣式供鸠。當(dāng)前@Styles僅支持通用屬性。
@Styles可以定義在組件內(nèi)或組件外陨闹,在組件外定義時需在方法名前面添加function關(guān)鍵字楞捂,組件內(nèi)定義時則不需要添加function關(guān)鍵字。
// xxx.ets
@Styles?function globalFancy () {
??.width(150)
??.height(100)
??.backgroundColor(Color.Pink)
}
@Entry
@Component
struct FancyUse {
??@Styles?componentFancy() {
????.width(100)
????.height(200)
????.backgroundColor(Color.Yellow)
??}
??build() {
????Column({ space:?10?}) {
??????Text('FancyA')
????????.globalFancy()
????????.fontSize(30)
??????Text('FancyB')
????????.globalFancy()
????????.fontSize(20)
??????Text('FancyC')
????????.componentFancy()
????????.fontSize(30)
??????Text('FancyD')
????????.componentFancy()
????????.fontSize(20)
????}
??}
}
@Styles還可以在StateStyles屬性內(nèi)部使用趋厉,在組件處于不同的狀態(tài)時賦予相應(yīng)的屬性寨闹。
在StateStyles內(nèi)可以直接調(diào)用組件外定義的@Styles方法,但需要通過this關(guān)鍵字調(diào)用組件內(nèi)定義的@Styles方法君账。
// xxx.ets
@Styles?function globalFancy () {
??.width(120)
??.height(120)
??.backgroundColor(Color.Green)
}
@Entry
@Component
struct FancyUse {
??@Styles?componentFancy() {
????.width(80)
????.height(80)
????.backgroundColor(Color.Red)
??}
??build() {
????Row({ space:?10?}) {
??????Button('Fancy')
????????.stateStyles({
??????????normal: {
????????????.width(100)
????????????.height(100)
????????????.backgroundColor(Color.Blue)
??????????},
??????????disabled:?this.componentFancy,
??????????pressed: globalFancy
????????})
????}
??}
}
@Extend
@Extend裝飾器將新的屬性方法添加到Text繁堡、Column、Button等內(nèi)置組件上乡数,通過@Extend裝飾器可以快速地擴(kuò)展原生組件椭蹄。@Extend不能定義在自定義組件struct內(nèi)。
// xxx.ets
@Extend(Text) function fancy (fontSize: number) {
??.fontColor(Color.Red)
??.fontSize(fontSize)
??.fontStyle(FontStyle.Italic)
??.fontWeight(600)
}
@Entry
@Component
struct FancyUse {
??build() {
????Row({ space:?10?}) {
??????Text("Fancy")
????????.fancy(16)
??????Text("Fancy")
????????.fancy(24)
??????Text("Fancy")
????????.fancy(32)
????}
??}
}
@CustomDialog
@CustomDialog裝飾器用于裝飾自定義彈窗組件净赴,使得彈窗可以動態(tài)設(shè)置內(nèi)容及樣式绳矩。
import?router from?'@ohos.router';
// xxx.ets
@CustomDialog
struct DialogExample {
??controller: CustomDialogController
??action: () =>?void
??build() {
????Row() {
??????Button('Close CustomDialog')
????????.onClick(() => {
??????????this.controller.close()
??????????this.action()
????????})
????}.padding(20)
??}
}
@Entry
@Component
struct CustomDialogUser {
??dialogController: CustomDialogController =?new?CustomDialogController({
????builder: DialogExample({ action:?this.onAccept }),
????cancel:?this.existApp,
????autoCancel:?true
??});
??onAccept() {
????console.info('onAccept');
??}
??existApp() {
????console.info('Cancel dialog!');
??}
??build() {
????Column() {
??????Button('Click to open Dialog')
????????.onClick(() => {
??????????this.dialogController.open()
????????})
????}
??}
}
五.渲染控制
ArkTS也提供了渲染控制的能力。條件渲染可根據(jù)應(yīng)用的不同狀態(tài)玖翅,渲染對應(yīng)狀態(tài)下的UI內(nèi)容翼馆。循環(huán)渲染可從數(shù)據(jù)源中迭代獲取數(shù)據(jù),并在每次迭代過程中創(chuàng)建相應(yīng)的組件金度。
條件渲染
使用if/else進(jìn)行條件渲染应媚。
f/else語句的每個分支都包含一個構(gòu)建UI的描述,此類描述中必須創(chuàng)建一個或多個子組件猜极。在初始渲染時中姜,if語句會執(zhí)行其中的一個分支并將生成的子組件添加到if/else的父組件中。
每當(dāng)if或else if條件語句中使用的狀態(tài)變量發(fā)生變化時跟伏,條件渲染都會更新并重新進(jìn)行條件評估丢胚,如果條件分支發(fā)生了變化,這意味著需要構(gòu)建另一個條件分支酬姆,此時框架將:
刪除所有以前渲染的(早期分支的)組件嗜桌。
執(zhí)行分支的UI描述,將生成的子組件添加到其父組件中辞色。
在以下示例中骨宠,如果count從0增加到1浮定,那么條件渲染會進(jìn)行更新并計算各個條件分支值:
條件(count > 0)計算結(jié)果為false,未發(fā)生變化层亿,不執(zhí)行額外操作桦卒;
條件(this.count % 2 === 0)從true更改為false,該分支內(nèi)的Text組件將從Column組件中移除并銷毀匿又;
else分支為true方灾,執(zhí)行該條件下的UI描述,創(chuàng)建一個Text組件碌更,并將它放在Column組件內(nèi)裕偿。
@Entry
@Component
struct MyComponent {
??@State?count: number =?300
??build() {
????Column() {
??????if?(this.count <?0) {
????????Text('count is negative').fontSize(14)
??????}?else?if?(this.count %?2?===?0) {
????????Text('count is even').fontSize(14)
??????}?else?{
????????Text('count is odd').fontSize(14)
??????}
????}
??}
}
循環(huán)渲染
通過循環(huán)渲染(ForEach)從數(shù)組中獲取數(shù)據(jù),并為每個數(shù)據(jù)項創(chuàng)建相應(yīng)的組件痛单,可減少代碼復(fù)雜度嘿棘。
ForEach(
??arr: any[],
??itemGenerator: (item: any, index?: number) =>?void,
??keyGenerator?: (item: any, index?: number) => string
)
參數(shù):
參數(shù)名參數(shù)類型必填參數(shù)描述
arrany[]是必須是數(shù)組,允許設(shè)置為空數(shù)組旭绒,空數(shù)組場景下將不會創(chuàng)建子組件鸟妙。同時允許設(shè)置返回值為數(shù)組類型的函數(shù),例如arr.slice(1, 3)挥吵,設(shè)置的函數(shù)不得改變包括數(shù)組本身在內(nèi)的任何狀態(tài)變量重父,如Array.splice、Array.sort或Array.reverse這些改變原數(shù)組的函數(shù)忽匈。
itemGenerator(item: any, index?: number) => void是生成子組件的lambda函數(shù)房午,為數(shù)組中的每一個數(shù)據(jù)項創(chuàng)建一個或多個子組件,單個子組件或子組件列表必須包括在大括號“{...}”中脉幢。
keyGenerator(item: any, index?: number) => string否匿名函數(shù)歪沃,用于給數(shù)組中的每一個數(shù)據(jù)項生成唯一且固定的鍵值嗦锐。當(dāng)數(shù)據(jù)項在數(shù)組中的位置更改時嫌松,其鍵值不得更改,當(dāng)數(shù)組中的數(shù)據(jù)項被新項替換時奕污,被替換項的鍵值和新項的鍵值必須不同萎羔。鍵值生成器的功能是可選的,不提供時框架默認(rèn)使用index+JSON.stringify(item)的方式生成碳默。
為了使開發(fā)框架能夠更好地識別數(shù)組更改贾陷,提高性能,建議開發(fā)者提供自定義的鍵值生成器嘱根。如將數(shù)組反向時髓废,如果沒有提供鍵值生成器,則ForEach中的所有節(jié)點都將重建该抒。
示例:
@Entry
@Component
struct MyComponent {
??@State?arr: number[] = [10,?20,?30]
??build() {
????Column({ space:?5?}) {
??????Button('Reverse Array')
????????.onClick(() => {
??????????this.arr.reverse()
????????})
??????ForEach(this.arr, (item: number) => {
????????Text(`item value: ${item}`).fontSize(18)
????????Divider().strokeWidth(2).color(Color.Black)
??????}, (item: number) => item.toString())
????}
??}
}
數(shù)據(jù)懶加載
通過數(shù)據(jù)懶加載(LazyForEach)從提供的數(shù)據(jù)源中按需迭代數(shù)據(jù)慌洪,并在每次迭代過程中創(chuàng)建相應(yīng)的組件。
LazyForEach(
??dataSource: IDataSource,????????????
??itemGenerator: (item: any) =>?void,?
??keyGenerator?: (item: any) => string
):?void
interface?IDataSource {
??totalCount(): number;??????????????????????????????????????????
??getData(index: number): any;??????????????????????????????????
??registerDataChangeListener(listener: DataChangeListener):?void;??
??unregisterDataChangeListener(listener: DataChangeListener):?void;
}
interface?DataChangeListener {
??onDataReloaded():?void;?????????????????????
??onDataAdd(index: number):?void;???????????
??onDataMove(from: number, to: number):?void;
??onDataDelete(index: number):?void;????????
??onDataChange(index: number):?void;?????????
}
參數(shù):
參數(shù)名參數(shù)類型必填參數(shù)描述
dataSourceIDataSource是實現(xiàn)IDataSource接口的對象,需要開發(fā)者實現(xiàn)相關(guān)接口冈爹。
itemGenerator(item: any, index?: number) => void是生成子組件的lambda函數(shù)涌攻,為數(shù)組中的每一個數(shù)據(jù)項創(chuàng)建一個或多個子組件,單個子組件或子組件列表必須包括在大括號“{...}”中频伤。
keyGenerator(item: any, index?: number) => string否匿名函數(shù)恳谎,用于給數(shù)組中的每一個數(shù)據(jù)項生成唯一且固定的鍵值。當(dāng)數(shù)據(jù)項在數(shù)組中的位置更改時憋肖,其鍵值不得更改因痛,當(dāng)數(shù)組中的數(shù)據(jù)項被新項替換時,被替換項的鍵值和新項的鍵值必須不同岸更。鍵值生成器的功能是可選的婚肆,不提供時框架默認(rèn)使用index+JSON.stringify(item)的方式生成。
為了使開發(fā)框架能夠更好地識別數(shù)組更改坐慰,提高性能较性,建議提供自定義的鍵值生成器。如將數(shù)組反向時结胀,如果沒有提供鍵值生成器赞咙,則LazyForEach中的所有節(jié)點都將重建。
IDataSource類型說明
名稱描述
totalCount(): number獲取數(shù)據(jù)總數(shù)糟港。
getData(index: number): any獲取索引值index對應(yīng)的數(shù)據(jù)攀操。
registerDataChangeListener(listener:DataChangeListener): void注冊數(shù)據(jù)改變的監(jiān)聽器。
unregisterDataChangeListener(listener:DataChangeListener): void注銷數(shù)據(jù)改變的監(jiān)聽器秸抚。
DataChangeListener類型說明
名稱描述
onDataReloaded(): void重新加載所有數(shù)據(jù)速和。
onDataAdded(index: number): voiddeprecated通知組件index的位置有數(shù)據(jù)添加。從API Version 8開始廢棄剥汤,建議使用onDataAdd颠放。
onDataMoved(from: number, to: number): voiddeprecated通知組件數(shù)據(jù)從from的位置移到to的位置。從API Version 8開始廢棄吭敢,建議使用onDataMove碰凶。
onDataDeleted(index: number): voiddeprecated通知組件index的位置有數(shù)據(jù)刪除。從API Version 8開始廢棄鹿驼,建議使用onDataDelete欲低。
onDataChanged(index: number): voiddeprecated通知組件index的位置有數(shù)據(jù)變化。 從API Version 8開始廢棄畜晰,建議使用onDataChange砾莱。
onDataAdd(index: number): void8+通知組件index的位置有數(shù)據(jù)添加。
onDataMove(from: number, to: number): void8+通知組件數(shù)據(jù)從from的位置移到to的位置凄鼻。
onDataDelete(index: number): void8+通知組件index的位置有數(shù)據(jù)刪除腊瑟。
onDataChange(index: number): void8+通知組件index的位置有數(shù)據(jù)變化面哼。
示例:
// xxx.ets
class?BasicDataSource?implements?IDataSource {
??private?listeners: DataChangeListener[] = []
??public?totalCount(): number {
????return?0
??}
??public?getData(index: number): any {
????return?undefined
??}
??registerDataChangeListener(listener: DataChangeListener):?void?{
????if?(this.listeners.indexOf(listener) <?0) {
??????console.info('add listener')
??????this.listeners.push(listener)
????}
??}
??unregisterDataChangeListener(listener: DataChangeListener):?void?{
????const?pos =?this.listeners.indexOf(listener);
????if?(pos >=?0) {
??????console.info('remove listener')
??????this.listeners.splice(pos,?1)
????}
??}
??notifyDataReload():?void?{
????this.listeners.forEach(listener => {
??????listener.onDataReloaded()
????})
??}
??notifyDataAdd(index: number):?void?{
????this.listeners.forEach(listener => {
??????listener.onDataAdd(index)
????})
??}
??notifyDataChange(index: number):?void?{
????this.listeners.forEach(listener => {
??????listener.onDataChange(index)
????})
??}
??notifyDataDelete(index: number):?void?{
????this.listeners.forEach(listener => {
??????listener.onDataDelete(index)
????})
??}
??notifyDataMove(from: number, to: number):?void?{
????this.listeners.forEach(listener => {
??????listener.onDataMove(from, to)
????})
??}
}
class?MyDataSource?extends?BasicDataSource {
??// 初始化數(shù)據(jù)列表
??private?dataArray: string[] = ['/path/image0.png',?'/path/image1.png',?'/path/image2.png',?'/path/image3.png']
??public?totalCount(): number {
????return?this.dataArray.length
??}
??public?getData(index: number): any {
????return?this.dataArray[index]
??}
??public?addData(index: number, data: string):?void?{
????this.dataArray.splice(index,?0, data)
????this.notifyDataAdd(index)
??}
??public?pushData(data: string):?void?{
????this.dataArray.push(data)
????this.notifyDataAdd(this.dataArray.length -?1)
??}
}
@Entry
@Component
struct MyComponent {
??private?data: MyDataSource =?new?MyDataSource()
??build() {
????List({ space:?3?}) {
??????LazyForEach(this.data, (item: string) => {
????????ListItem() {
??????????Row() {
????????????Image(item).width(50).height(50)
????????????Text(item).fontSize(20).margin({ left:?10?})
??????????}.margin({ left:?10, right:?10?})
????????}
????????.onClick(() => {
??????????// 每點擊一次列表項,數(shù)據(jù)增加一項
??????????this.data.pushData('/path/image'?+?this.data.totalCount() +?'.png')
????????})
??????}, item => item)
????}
??}
}