女生java網(wǎng)站開發(fā)培訓(xùn)后好找工作西安百度推廣客服電話多少
??????? 從今天開始,博主將開設(shè)一門新的專欄用來講解市面上比較熱門的技術(shù) “鴻蒙開發(fā)”,對于剛接觸這項技術(shù)的小伙伴在學(xué)習(xí)鴻蒙開發(fā)之前,有必要先了解一下鴻蒙,從你的角度來講,你認(rèn)為什么是鴻蒙呢?它出現(xiàn)的意義又是什么?鴻蒙僅僅是一個手機(jī)操作系統(tǒng)嗎?它的出現(xiàn)能夠和Android和IOS三分天下嗎?它未來的潛力能否制霸整個手機(jī)市場呢?
抱著這樣的疑問和對鴻蒙開發(fā)的好奇,讓我們開始今天對ArkUI狀態(tài)管理的掌握吧!
目錄
ArkUI狀態(tài)管理
@State裝飾器
@Prop和@Link
@Provide和@Consume
@Observed和@ObjectLink
頁面路由
ArkUI狀態(tài)管理
在聲明式UI中是以狀態(tài)來驅(qū)動視圖進(jìn)行更新的,其中的核心概念就是狀態(tài)和視圖。所謂狀態(tài)就是驅(qū)動視圖更新這個數(shù)據(jù),或者說是我們自定義組件當(dāng)中定義好的那些被裝飾器標(biāo)記好的變量;所謂視圖就是指GUI描述渲染得到的用戶界面;視圖渲染好了之后用戶就可以對視圖中的頁面元素產(chǎn)生交互,通過點擊、觸摸、拖拽等互動事件來改變狀態(tài)變量的值,在arkui的內(nèi)部就有一種機(jī)制去監(jiān)控狀態(tài)變量的值,一旦發(fā)現(xiàn)它發(fā)生了變更就會去觸發(fā)視圖的重新渲染。所以像這種狀態(tài)和視圖之間的相互作用的機(jī)制,我們就稱之為狀態(tài)管理機(jī)制。
狀態(tài)管理需要用到多個不同的裝飾器,接下來我們開始學(xué)習(xí)狀態(tài)管理的基本概念以及以下幾個裝飾器的基本用法和注意事項。
@State裝飾器
使用@State裝飾器有以下注意事項:
1)@State裝飾器標(biāo)記的變量必須初始化,不能為空值
2)@State支持Object、class、string、number、boolean、enum類型以及這些類型的數(shù)組
3)嵌套類型(Object里面的某個屬性又是一個Object)以及數(shù)組中的對象屬性無法觸發(fā)視圖更新,以下是演示代碼:
class Person {name: stringage: numberconstructor(name: string, age: number) {this.name = namethis.age = age}
}
@Entry
@Component
struct StatePage {idx: number = 1@State p: Person[] = [new Person('張三', 20)]build(){Column(){Button('添加').onClick(()=>{this.p.push(new Person('張三'+this.idx++, 20 ))})ForEach(this.p,(p, index) => {Row(){Text(`${p.name}: ${p.age}`).fontSize(30).onClick(() => {//數(shù)組內(nèi)的元素變更不會觸發(fā)數(shù)組的重新渲染// p.age++//數(shù)組重新添加、刪除或者賦值的時候才會觸發(fā)數(shù)組的重新渲染this.p[index] = new Person(p.name, p.age+1)})Button('刪除').onClick(()=>{this.p.splice(index, 1)})}.width('100%').justifyContent(FlexAlign.SpaceAround)})}.width('100%').height('100%')}
}
@Prop和@Link
Prop和Link這兩個裝飾器是在父子組件之間數(shù)據(jù)同步的時候去使用的,以下是兩者的使用情況:
裝飾器 | @Prop | @Link |
---|---|---|
同步類型 | 單向同步 | 雙向同步 |
允許裝飾的變量類型 | 1)@Prop只支持string、number、boolean、enum類型 2)父組件是對象類型,子組件是對象屬性 3)不可以是數(shù)組、any | 1)父子類型一致:string、number、boolean、enum、object、class,以及他們的數(shù)組 2)數(shù)組中的元素增、刪、替換會引起刷新 3)嵌套類型以及數(shù)組中的對象屬性無法觸發(fā)視圖更新 |
接下來借助Prop和Link完成一個小案例:
我們在父組件中通過prop向子組件傳值,子組件通過@Prop裝飾器接受到值之后進(jìn)行頁面渲染,這里我們采用了ArkUI提供的堆疊容器和進(jìn)度條組件實現(xiàn)頁面的配置:
// 統(tǒng)一卡片樣式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}@Component
export struct TaskStatistics {@Prop totalTask: number // 總?cè)蝿?wù)數(shù)量@Prop finishTask: number // 已完成任務(wù)數(shù)量build() {// 任務(wù)進(jìn)度卡片Row(){Text('任務(wù)進(jìn)度').fontSize(30).fontWeight(FontWeight.Bold)// 堆疊容器,組件之間可以相互疊加顯示Stack(){// 環(huán)形進(jìn)度條Progress({value: this.finishTask,total: this.totalTask,type: ProgressType.Ring // 選擇環(huán)形進(jìn)度條}).width(100)Row(){Text(this.finishTask.toString()).fontColor('#36D').fontSize(24)Text(' / ' + this.totalTask.toString()).fontSize(24)}}}.margin({top: 20, bottom: 10}).justifyContent(FlexAlign.SpaceEvenly).card()}
}
子組件如果修改父組件的值的話,需要通過裝飾器@Link來實現(xiàn),父組件需要通過$來拿值:
// 任務(wù)類
class Task {static id: number = 1 // 靜態(tài)變量,內(nèi)部共享name: string = `任務(wù)${Task.id++}` // 任務(wù)名稱finished: boolean = false // 任務(wù)狀態(tài),是否已完成
}// 統(tǒng)一卡片樣式
@Styles function card(){.width('95%').padding(20).backgroundColor(Color.White).borderRadius(15).shadow({radius: 6, color: '#1F000000', offsetX: 2, offsetY: 4})
}@Component
export struct TaskList {@Link totalTask: number // 總?cè)蝿?wù)數(shù)量@Link finishTask: number // 已完成任務(wù)數(shù)量@State tasks: Task[] = [] // 任務(wù)數(shù)組// 任務(wù)更新觸發(fā)函數(shù)handleTaskChange(){this.totalTask = this.tasks.length // 更新任務(wù)總數(shù)量this.finishTask = this.tasks.filter(item => item.finished).length // 更新任務(wù)數(shù)量}build() {Column(){// 任務(wù)新增按鈕Button('新增任務(wù)').width(200).onClick(()=>{this.tasks.push(new Task()) // 新增任務(wù)數(shù)組this.handleTaskChange()})// 任務(wù)列表List({space: 10}){ForEach(this.tasks,(item: Task, index)=>{ListItem(){Row(){Text(item.name).fontSize(20)Checkbox().select(item.finished).onChange(val => {item.finished = val // 更新當(dāng)前的任務(wù)狀態(tài)this.handleTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}.swipeAction({end: this.DeleteButton(index)})})}.width('100%').layoutWeight(1).alignListItem(ListItemAlign.Center)}}@Builder DeleteButton(index: number){Button(){Image($r('app.media.delete')).fillColor(Color.Red).width(20)}.width(40).height(40).type(ButtonType.Circle).backgroundColor(Color.Red).margin(5).onClick(()=>{this.tasks.splice(index, 1)this.handleTaskChange()})}
}
接下來就需要在父組件引用這兩個子組件了,然后傳參來獲取和傳遞相關(guān)數(shù)值:
// 任務(wù)類
class Task {static id: number = 1 // 靜態(tài)變量,內(nèi)部共享name: string = `任務(wù)${Task.id++}` // 任務(wù)名稱finished: boolean = false // 任務(wù)狀態(tài),是否已完成
}import { TaskStatistics } from '../components/TaskStatistics'
import { TaskList } from '../components/TaskList'
@Entry
@Component
struct PropPage {@State totalTask: number = 0 // 總?cè)蝿?wù)數(shù)量@State finishTask: number = 0 // 已完成任務(wù)數(shù)量@State tasks: Task[] = [] // 任務(wù)數(shù)組build(){Column({space: 10}){// 任務(wù)進(jìn)度卡片TaskStatistics({ totalTask: this.totalTask, finishTask: this.finishTask })// 任務(wù)列表TaskList({ totalTask: $totalTask, finishTask: $finishTask })}.width('100%').height('100%').backgroundColor('#F1F2F3')}
}
最終呈現(xiàn)的結(jié)果如下:
@Provide和@Consume
這兩個裝飾器可以跨組件提供類似@State和@Link的雙向同步,操作方式很簡單,父組件之間使用Provide裝飾器,子組件全部使用Consume裝飾器,父組件都不需要傳遞參數(shù)了,直接調(diào)用子組件函數(shù)即可:
最終呈現(xiàn)的結(jié)果如下:
雖然相對來說比Prop和Link簡便許多,但是使用Provide和Consume還是有代價的,本來需要傳遞參數(shù)的,但是使用Provide不需要傳遞參數(shù),其內(nèi)部自動幫助我們?nèi)ゾS護(hù),肯定是有一些資源上的浪費,所以說我們能用Prop還是盡量用Prop,實在用不了的可以去考慮Provide。
@Observed和@ObjectLink
這兩個裝飾器用于在涉及嵌套對象或數(shù)組元素為對象的場景中進(jìn)行雙向數(shù)據(jù)同步:
我們給任務(wù)列表中的文本添加一個樣式屬性,當(dāng)我們點擊勾選的話,文本就會變灰并且加上一個中劃線樣式:
但是當(dāng)我們勾選之后,視圖并沒有發(fā)生變化,原因是我們的Task是一個對象類型,數(shù)組的元素是對象,對象的屬性發(fā)生修改是不會觸發(fā)視圖的重新渲染的,所以這里我們需要使用本次講解的裝飾器來進(jìn)行解決:
我們給class對象設(shè)置@Observed裝飾器:
然后在要修改對象屬性值的位置進(jìn)行設(shè)置@ObjectLink裝飾器,因為這里一個任務(wù)列表通過ForEach遍歷出來的,所以我們需要將這個位置單獨抽離出來形成一個函數(shù),然后將要使用的item設(shè)置@ObjectLink裝飾器,因為還需要調(diào)用函數(shù),但是任務(wù)列表的函數(shù)不能動,所以我們也將調(diào)用的函數(shù)作為參數(shù)傳遞過去:
@Component
struct TaskItem {@ObjectLink item: TaskonTaskChange: () => voidbuild(){Row(){if (this.item.finished){Text(this.item.name).finishedTask()}else{Text(this.item.name)}Checkbox().select(this.item.finished).onChange(val => {this.item.finished = val // 更新當(dāng)前的任務(wù)狀態(tài)this.onTaskChange()})}.card().justifyContent(FlexAlign.SpaceBetween)}
}
傳遞過程中為了確保this指向沒有發(fā)生改變,我們在傳遞函數(shù)的時候,還需要通過bind函數(shù)指定this指向:
最終呈現(xiàn)的結(jié)果如下:
頁面路由
頁面路由是指在應(yīng)用程序中實現(xiàn)不同頁面之間的跳轉(zhuǎn)和數(shù)據(jù)傳遞,如果學(xué)習(xí)過前端vue或react框架的人,可以非常簡單的理解頁面路由跳轉(zhuǎn)的概念,以下是在鴻蒙開發(fā)中進(jìn)行頁面路由跳轉(zhuǎn)所調(diào)用的API函數(shù)以及相應(yīng)函數(shù)的作用,與前端的vue框架十分類似:
Router有兩種頁面跳轉(zhuǎn)模式,分別是:
router.pushUrl():目標(biāo)頁不會替換當(dāng)前頁,而是壓入頁面棧,因此可以用router.back()返回當(dāng)前頁
router.replaceUrl():目標(biāo)頁替換當(dāng)前頁,當(dāng)前頁會被銷毀并釋放資源,無法返回當(dāng)前頁
Router有兩種頁面實例模式,分別是:
Standard:標(biāo)準(zhǔn)實例模式,每次跳轉(zhuǎn)都會新建一個目標(biāo)頁并壓入棧頂,默認(rèn)就是這種模式
Single:單實例模式,如果目標(biāo)頁已經(jīng)在棧中,則離棧頂最近的同url頁面會被移動到棧頂并重新加載
了解完頁面路由基本概念之后,接下來在案例中開始介紹如何使用頁面路由:
首先我們在index首頁定義路由信息:
// 定義路由信息
class RouterInfo {url: string // 頁面路徑title: string // 頁面標(biāo)題constructor(url: string, title: string) {this.url = urlthis.title = title}
}
接下在struct結(jié)構(gòu)體里面定義路由相關(guān)信息以及頁面的靜態(tài)樣式:
@State message: string = '頁面列表'private routers: RouterInfo[] = [new RouterInfo('pages/router/test1', '頁面1'),new RouterInfo('pages/router/test2', '頁面2'),new RouterInfo('pages/router/test3', '頁面3'),new RouterInfo('pages/router/test4', '頁面4')]build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).fontColor('#008c8c').height(80)List({space: 15}){ForEach(this.routers,(router, index) => {ListItem(){this.RouterItem(router, index + 1)}})}.layoutWeight(1).alignListItem(ListItemAlign.Center).width('100%')}.width('100%').height('100%')}}
定義RouterItem函數(shù),設(shè)置點擊函數(shù)進(jìn)行路由跳轉(zhuǎn):
@Builder RouterItem(r: RouterInfo, i: number){Row(){Text(i+'.').fontSize(20).fontColor(Color.White)Blank()Text(r.title).fontSize(20).fontColor(Color.White)}.width('90%').padding(12).backgroundColor('#38f').shadow({radius: 6, color: '#4f0000', offsetX: 2, offsetY: 4}).onClick(()=>{// router跳轉(zhuǎn),傳遞3個參數(shù)router.pushUrl(// 跳轉(zhuǎn)路徑及參數(shù){url: r.url,params: {id: i}},// 頁面實例router.RouterMode.Single,// 跳轉(zhuǎn)失敗的一個回調(diào)err => {if (err) {console.log(`跳轉(zhuǎn)失敗,errCode:${err.code} errMsg: ${err.message}`)}})})}
定義3個路由跳轉(zhuǎn)頁,設(shè)置第四個路由沒有跳轉(zhuǎn)頁面,作對照:
注意,如果是僅僅是新建一個ArkTS頁面的話需要在以下的文件中進(jìn)行路由配置:
如果覺得每次創(chuàng)建一個頁面都要進(jìn)行一次路由路徑的創(chuàng)建比較煩的話,可以采用以下創(chuàng)建方式,會自動幫我們配置好路由路徑,而不需在去手動設(shè)置:
在子組件中,如果我們想拿到傳遞過來的參數(shù)可以調(diào)用getParams函數(shù),返回調(diào)用back函數(shù):
想要加個返回的警告可以采用如下的方式:
最終呈現(xiàn)的結(jié)果為: