一、基本概念
我們構(gòu)建的頁面多為靜態(tài)界面。如果希望構(gòu)建一個動態(tài)的、有交互的界面,就需要引入“狀態(tài)”的概念。
在聲明式UI編程框架中,UI是程序狀態(tài)的運行結(jié)果,用戶構(gòu)建了一個UI模型,其中應(yīng)用的運行時的狀態(tài)是參數(shù)。當參數(shù)改變時,UI作為返回結(jié)果,也將進行對應(yīng)的改變。這些運行時的狀態(tài)變化所帶來的UI的重新渲染,在ArkUI中統(tǒng)稱為狀態(tài)管理機制。
二、@State 裝飾器

狀態(tài)管理
class Person {
name: string
age: number
girlfriend: Person
// ?可選參數(shù)
constructor(name: string, age: number, girlfriend?: Person) {
this.name = name
this.age = age
this.girlfriend = girlfriend
}
}
@Entry
@Component
struct StatePage {
// 注意1:狀態(tài)變量(必須初始化,不能為空值)
@State name: string = 'Jack'
@State age: number = 30
// 改變對象屬性的值,會觸發(fā)頁面重新渲染
@State person1: Person = new Person('Tom', 20)
// 注意2:對象嵌套對象,改變嵌套對象屬性的值,不會觸發(fā)頁面重新渲染
@State person2: Person = new Person('David', 26, new Person('Lily', 22))
// 注意3:添加/刪除/修改數(shù)組元素會觸發(fā)頁面重新渲染;修改數(shù)組中對象屬性的值,不會觸發(fā)頁面重新渲染
@State personList: Person[] = [
new Person('張三', 18),
new Person('李四', 19)
]
build() {
Row() {
Column() {
// 字符串模版語法
Text(`${this.name} : ${this.age}`)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.age++
})
Text(`${this.person1.name} , ${this.person1.age}`)
.fontSize(30)
.fontColor(Color.Blue)
.onClick(value => {
this.person1.age += 1
})
}
.width('100%')
}
.height('100%')
}
}
三、@Prop、@Link 裝飾器
用途:用于父子組件數(shù)據(jù)同步傳值
-
@Prop裝飾屬性(單向數(shù)據(jù)同步):父組件向子組件傳值,傳的是變量的值 -
@Link裝飾屬性(雙向數(shù)據(jù)同步),父組件向子組件傳值,必須帶上$,表示傳的是變量的引用
@Prop、@Link 裝飾器的用法
四、@Provide、@Consume 裝飾器
用途:一般用于跨組件雙向數(shù)據(jù)同步傳值。相比
@State、@Link裝飾器,使用更加方便簡潔,如果使用@Link,會傳遞多次,使用@Provide、@Consume,不需要我們做任何事(即不需要我們調(diào)用子組件手動傳參),由系統(tǒng)內(nèi)部自動幫我們做數(shù)據(jù)同步和傳值。系統(tǒng)幫我們維護,會消耗系統(tǒng)一些資源,一般是在@Link裝飾器不能滿足使用需求時,會使用這個。-
使用:父組件使用
@Provide裝飾屬性,子組件/其它后代組件使用@Consume裝飾屬性,注意屬性名及類型和父組件保持一致,系統(tǒng)內(nèi)部會自動幫我們做數(shù)據(jù)同步和傳值。
五、@Observed、@ObjectLink 裝飾器


@Observed、@ObjectLink的使用
六、任務(wù)統(tǒng)計案例
-
效果圖
任務(wù)統(tǒng)計效果圖 - 實現(xiàn)代碼
// 任務(wù)模型
@Observed // @Observed裝飾器,用于監(jiān)聽嵌套對象的屬性,或數(shù)組元素對象的屬性變化;與@ObjectLink配套使用(即子組件Task類型屬性前加@ObjectLink)
class Task {
// static 靜態(tài)變量:這個類的所有對象共享(通過類訪問)
static id: number = 1
// 任務(wù)名稱(每次創(chuàng)建對象會加1)
name: string = `任務(wù)${Task.id++}`
// 任務(wù)狀態(tài):是否完成
finished: boolean = false
// 屬性在定義的時候初始化,就不需要再寫構(gòu)造方法,new對象時候會自動初始化,也就不需要調(diào)用構(gòu)造方法去創(chuàng)建對象
}
// 任務(wù)統(tǒng)計信息
class StatInfo {
// 總?cè)蝿?wù)數(shù)量
totalTask: number = 0
// 已完成任務(wù)數(shù)量
finishTask: number = 0
}
// 統(tǒng)一的卡片樣式
@Styles function cardStyle() {
.width('90%')
.padding(20)
.backgroundColor(Color.White)
.borderRadius(15)
.shadow({ radius: 6, color: '#ccc', offsetX: 2, offsetY: 4 })
}
// 任務(wù)完成樣式
@Extend(Text) function finishedTaskStyle() {
.fontColor('#b1b2b1')
// 中劃線
.decoration({ type: TextDecorationType.LineThrough })
}
@Entry
@Component
struct TaskPage {
// 統(tǒng)計信息
@State statInfo: StatInfo = new StatInfo()
// 跨組件雙向數(shù)據(jù)同步傳值
//@Provide statInfo: StatInfo = new StatInfo()
build() {
Column({ space: 20 }) {
// 1.任務(wù)進度卡片
// @Prop裝飾屬性(單向數(shù)據(jù)同步):父組件向子組件傳值,傳的是變量的值
TaskStatView({finishTask: this.statInfo.finishTask, totalTask: this.statInfo.totalTask}) // 不能傳對象
// 2.任務(wù)列表組件
// @Link裝飾屬性(雙向數(shù)據(jù)同步),父組件向子組件傳值,必須帶上$,表示傳的是變量的引用
TaskListView({statInfo: $statInfo}) // 可以傳對象
// 布局權(quán)重調(diào)高,剩下高度都給我(彈性布局),解決滾動時高度自適應(yīng)屏幕邊緣
.layoutWeight(1)
// 3.如果使用 @Provide、@Consume 裝飾屬性,如:
// 父組件屬性:@Provide statInfo: StatInfo = new StatInfo()
// 子組件屬性:@Consume statInfo: StatInfo
//TaskStatView() // 調(diào)用組件時不需要傳參,由系統(tǒng)內(nèi)部自動幫我們做數(shù)組同步。注意:屬性類型和屬性名要保持一致
}
.width('100%')
.height('100%')
.backgroundColor('#f1f2f3')
}
}
// ======== 1.任務(wù)統(tǒng)計組件 ========
@Component
struct TaskStatView {
// @Prop:單向數(shù)據(jù)同步(相當于傳的拷貝,深拷貝),接受父組件傳來的值,注意不能初始化
@Prop finishTask: number
@Prop totalTask: number
build() {
Row() {
Text('任務(wù)進度:')
.fontSize(30)
.fontWeight(FontWeight.Bold)
// 堆疊容器
Stack() {
// 進度條組件(圓形)
Progress({
value: this.finishTask,
total: this.totalTask,
type: ProgressType.Ring
})
.width(100)
Row() {
Text(this.finishTask.toString())
.fontColor('#36d')
.fontSize(24)
Text(' / ' + this.totalTask.toString())
.fontSize(24)
}
}
}
.cardStyle()
.margin({ top: 20 })
.justifyContent(FlexAlign.SpaceEvenly)
}
}
// ======== 2.任務(wù)列表組件 ========
@Component
struct TaskListView {
// @Link:雙向數(shù)據(jù)同步(相當于傳的引用,淺拷貝),接受父組件傳來的值,注意不能初始化
@Link statInfo: StatInfo
// 任務(wù)數(shù)組
@State tasks: Task[] = []
// 處理任務(wù)變更時,更新數(shù)據(jù)
handleTaskChange() {
this.statInfo.totalTask = this.tasks.length
// filter:遍歷數(shù)組,篩選出滿足指定條件的元素
this.statInfo.finishTask = this.tasks.filter(task => task.finished).length
}
build() {
// 注意:build下只能有一個根組件
Column({ space: 20 }) {
// 新增任務(wù)按鈕
Button('新增任務(wù)')
.width(200)
.fontSize(20)
.onClick(() => {
// 向數(shù)組中添加元素
this.tasks.push(new Task())
// 處理任務(wù)變更
this.handleTaskChange()
})
// 任務(wù)列表
List() {
ForEach(this.tasks, (item: Task, index) => {
ListItem() {
// 傳遞方法,相當于iOS中的block
// 注意:方法傳遞時,要綁定當前this,不然會導(dǎo)致this變化,方法內(nèi)部調(diào)用報錯!
TaskItemView({item: item, onTaskChange: this.handleTaskChange.bind(this)})
}
// 通過構(gòu)建函數(shù)傳入自定義刪除按鈕
.swipeAction({ end: this.DeleteButton(index) })
})
}
.width('100%')
.height('100%')
// List中元素居中對齊
.alignListItem(ListItemAlign.Center)
// 布局權(quán)重調(diào)高,剩下高度都給我(彈性布局),解決滾動時高度自適應(yīng)屏幕邊緣
.layoutWeight(1)
}
}
// 構(gòu)建函數(shù):自定義刪除按鈕
@Builder DeleteButton(index: number) {
Button() {
Image($r('app.media.app_icon'))
.fillColor(Color.White)
.width(20)
}
.width(40)
.height(40)
.type(ButtonType.Circle)
.backgroundColor(Color.Red)
.margin({left: 10})
.onClick(() => {
// 刪除數(shù)組元素(從index開始,刪除1個)
this.tasks.splice(index, 1)
// 處理任務(wù)變更
this.handleTaskChange()
})
}
}
// ======== 3.任務(wù)卡片組件 ========
@Component
struct TaskItemView {
// Task類也需要加 @Observed 進行裝飾
// 使用 @Observed 和 @ObjectLink,item對象屬性發(fā)生變化,觸發(fā)視圖重新渲染
@ObjectLink item: Task
// 接收父組件傳過來的方法(變量是函數(shù)類型,相當于iOS中的block回調(diào))
onTaskChange: () => void
build() {
Row() {
// 這里
if (this.item.finished) {
Text(this.item.name)
.fontSize(20)
.finishedTaskStyle()
} else {
Text(this.item.name)
.fontSize(20)
}
// 多選框
Checkbox()
.select(this.item.finished)
.onChange(value => {
this.item.finished = value
// 注意:這里相當于是調(diào)用了 handleTaskChange 方法
this.onTaskChange()
})
}
.cardStyle()
.justifyContent(FlexAlign.SpaceBetween)
.margin({bottom: 10})
}
}


