頁面級變量的狀態(tài)管理
@State、@Prop、@Link、@Provide、@Consume、@ObjectLink、@Observed和@Watch用于管理頁面級變量的狀態(tài)。
@State
@State裝飾的變量是組件內(nèi)部的狀態(tài)數(shù)據(jù),當(dāng)這些狀態(tài)數(shù)據(jù)被修改時,將會調(diào)用所在組件的build方法進(jìn)行UI刷新。
@State狀態(tài)數(shù)據(jù)具有以下特征:
- 支持多種類型數(shù)據(jù):支持class、number、boolean、string強(qiáng)類型數(shù)據(jù)的值類型和引用類型,以及這些強(qiáng)類型構(gòu)成的數(shù)組,即Array、Array、Array、Array。不支持object和any。
- 支持多實例:組件不同實例的內(nèi)部狀態(tài)數(shù)據(jù)獨(dú)立。
- 內(nèi)部私有:標(biāo)記為@State的屬性是私有變量,只能在組件內(nèi)訪問。
- 需要本地初始化:必須為所有@State變量分配初始值,變量未初始化可能導(dǎo)致未定義的框架異常行為。
- 創(chuàng)建自定義組件時支持通過狀態(tài)變量名設(shè)置初始值:在創(chuàng)建組件實例時,可以通過變量名顯式指定@State狀態(tài)變量的初始值。
示例:
在下面的示例中:
- 用戶定義的組件MyComponent定義了@State狀態(tài)變量count和title。如果count或title的值發(fā)生變化,則執(zhí)行MyComponent的build方法來重新渲染組件;
- EntryComponent中有多個MyComponent組件實例,第一個MyComponent內(nèi)部狀態(tài)的更改不會影響第二個MyComponent;
- 創(chuàng)建MyComponent實例時通過變量名給組件內(nèi)的變量進(jìn)行初始化,如:
MyComponent({ title: { value: 'Hello World 2' }, count: 7 })
// xxx.ets
class Model {
value: string
constructor(value: string) {
this.value = value
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
MyComponent({ count: 1, increaseBy: 2 }) // 第1個MyComponent實例
MyComponent({ title: { value: 'Hello World 2' }, count: 7 }) // 第2個MyComponent實例
}
}
}
@Component
struct MyComponent {
@State title: Model = { value: 'Hello World' }
@State count: number = 0
private toggle: string = 'Hello World'
private increaseBy: number = 1
build() {
Column() {
Text(`${this.title.value}`).fontSize(30)
Button('Click to change title')
.margin(20)
.onClick(() = > {
// 修改內(nèi)部狀態(tài)變量title
this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello ArkUI'
})
Button(`Click to increase count=${this.count}`)
.margin(20)
.onClick(() = > {
// 修改內(nèi)部狀態(tài)變量count
this.count += this.increaseBy
})
}
}
@Prop
@Prop與@State有相同的語義,但初始化方式不同。@Prop裝飾的變量必須使用其父組件提供的@State變量進(jìn)行初始化,允許組件內(nèi)部修改@Prop變量,但變量的更改不會通知給父組件,父組件變量的更改會同步到@prop裝飾的變量,即@Prop屬于單向數(shù)據(jù)綁定。
@Prop狀態(tài)數(shù)據(jù)具有以下特征:
- 支持簡單類型:僅支持number、string、boolean等簡單數(shù)據(jù)類型;
- 私有:僅支持組件內(nèi)訪問;
- 支持多個實例:一個組件中可以定義多個標(biāo)有@Prop的屬性;
- 創(chuàng)建自定義組件時將值傳遞給@Prop變量進(jìn)行初始化:在創(chuàng)建組件的新實例時,必須初始化所有@Prop變量,不支持在組件內(nèi)部進(jìn)行初始化。
說明:
@Prop修飾的變量不能在組件內(nèi)部進(jìn)行初始化。
示例:
在下面的示例中,當(dāng)按“+1”或“-1”按鈕時,父組件狀態(tài)發(fā)生變化,重新執(zhí)行build方法,此時將創(chuàng)建一個新的CountDownComponent組件實例。父組件的countDownStartValue狀態(tài)變量被用于初始化子組件的@Prop變量,當(dāng)按下子組件的“count - costOfOneAttempt”按鈕時,其@Prop變量count將被更改,CountDownComponent重新渲染,但是count值的更改不會影響父組件的countDownStartValue值。
// xxx.ets
@Entry
@Component
struct ParentComponent {
@State countDownStartValue: number = 10 // 初始化countDownStartValue
build() {
Column() {
Text(`Grant ${this.countDownStartValue} nuggets to play.`).fontSize(18)
Button('+1 - Nuggets in New Game')
.margin(15)
.onClick(() = > {
this.countDownStartValue += 1
})
Button('-1 - Nuggets in New Game')
.margin(15)
.onClick(() = > {
this.countDownStartValue -= 1
})
// 創(chuàng)建子組件時,必須在構(gòu)造函數(shù)參數(shù)中提供其@Prop變量count的初始值,同時初始化常規(guī)變量costOfOneAttempt(非Prop變量)
CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
}
}
}
@Component
struct CountDownComponent {
@Prop count: number
private costOfOneAttempt: number
build() {
Column() {
if (this.count > 0) {
Text(`You have ${this.count} Nuggets left`).fontSize(18)
} else {
Text('Game over!').fontSize(18)
}
Button('count - costOfOneAttempt')
.margin(15)
.onClick(() = > {
this.count -= this.costOfOneAttempt
})
}
}
}
@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)行初始化。
簡單類型示例:
@Link語義是從''操作符引出,即isPlaying是this.isPlaying內(nèi)部狀態(tài)的雙向數(shù)據(jù)綁定。當(dāng)單擊子組件PlayButton中的按鈕時,@Link變量更改,PlayButton與父組件中的Text和Button將同時進(jìn)行刷新,同樣地,當(dāng)點(diǎn)擊父組件中的Button修改this.isPlaying時,子組件PlayButton與父組件中的Text和Button也將同時刷新。
// xxx.ets
@Entry
@Component
struct Player {
@State isPlaying: boolean = false
build() {
Column() {
PlayButton({ buttonPlaying: $isPlaying })
Text(`Player is ${this.isPlaying ? '' : 'not'} playing`).fontSize(18)
Button('Parent:' + this.isPlaying)
.margin(15)
.onClick(() = > {
this.isPlaying = !this.isPlaying
})
}
}
}
@Component
struct PlayButton {
@Link buttonPlaying: boolean
build() {
Column() {
Button(this.buttonPlaying ? 'pause' : 'play')
.margin(20)
.onClick(() = > {
this.buttonPlaying = !this.buttonPlaying
})
}
}
}
復(fù)雜類型示例:
// xxx.ets
@Entry
@Component
struct Parent {
@State arr: number[] = [1, 2, 3]
build() {
Column() {
Child({ items: $arr })
Button('Parent Button: splice')
.margin(10)
.onClick(() = > {
this.arr.splice(0, 1, 60)
})
ForEach(this.arr, item = > {
Text(item.toString()).fontSize(18).margin(10)
}, item = > item.toString())
}
}
}
@Component
struct Child {
@Link items: number[]
build() {
Column() {
Button('Child Button1: push')
.margin(15)
.onClick(() = > {
this.items.push(100)
})
Button('Child Button2: replace whole item')
.margin(15)
.onClick(() = > {
this.items = [100, 200, 300]
})
}
}
}
@Link、@State和@Prop結(jié)合使用示例:
下面示例中,ParentView包含ChildA和ChildB兩個子組件,ParentView的狀態(tài)變量counter分別用于初始化ChildA的@Prop變量和ChildB的@Link變量。
- ChildB使用@Link建立雙向數(shù)據(jù)綁定,當(dāng)ChildB修改counterRef狀態(tài)變量值時,該更改將同步到ParentView和ChildA共享;
- ChildA使用@Prop建立從ParentView到自身的單向數(shù)據(jù)綁定,當(dāng)ChildA修改counterVal狀態(tài)變量值時,ChildA將重新渲染,但該更改不會傳達(dá)給ParentView和ChildB。
// xxx.ets
@Entry
@Component
struct ParentView {
@State counter: number = 0
build() {
Column() {
ChildA({ counterVal: this.counter })
ChildB({ counterRef: $counter })
}
}
}
@Component
struct ChildA {
@Prop counterVal: number
build() {
Button(`ChildA: (${this.counterVal}) + 1`)
.margin(15)
.onClick(() = > {
this.counterVal += 1
})
}
}
@Component
struct ChildB {
@Link counterRef: number
build() {
Button(`ChildB: (${this.counterRef}) + 1`)
.margin(15)
.onClick(() = > {
this.counterRef += 1
})
}
}
@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的值變化時,會通知所有子組件同步變化。
然而,上述例子是針對某個數(shù)據(jù)對象進(jìn)行的整體同步,而當(dāng)開發(fā)者只想針對父組件中某個數(shù)據(jù)對象的部分信息進(jìn)行同步時,使用@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)訪問。
示例
// xxx.ets
// 父組件ViewB中的類對象ClassA與子組件ViewA保持?jǐn)?shù)據(jù)同步時,可以使用@ObjectLink和@Observed,綁定該數(shù)據(jù)對象的父組件和其他子組件同步更新
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++
}
}
@Component
struct ViewA {
label: string = 'ViewA1'
@ObjectLink a: ClassA
build() {
Row() {
Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`)
.onClick(() = > {
this.a.c += 1
})
}.margin({ top: 10 })
}
}
@Entry
@Component
struct ViewB {
@State arrA: ClassA[] = [new ClassA(0), new ClassA(0)]
build() {
Column() {
ForEach(this.arrA, (item) = > {
ViewA({ label: `#${item.id}`, a: item })
}, (item) = > item.id.toString())
ViewA({ label: `this.arrA[first]`, a: this.arrA[0] })
ViewA({ label: `this.arrA[last]`, a: this.arrA[this.arrA.length - 1] })
Button(`ViewB: reset array`)
.margin({ top: 10 })
.onClick(() = > {
this.arrA = [new ClassA(0), new ClassA(0)]
})
Button(`ViewB: push`)
.margin({ top: 10 })
.onClick(() = > {
this.arrA.push(new ClassA(0))
})
Button(`ViewB: shift`)
.margin({ top: 10 })
.onClick(() = > {
this.arrA.shift()
})
}.width('100%')
}
}
@Provide和@Consume
@Provide作為數(shù)據(jù)的提供方,可以更新其子孫節(jié)點(diǎn)的數(shù)據(jù),并觸發(fā)頁面渲染。@Consume在感知到@Provide數(shù)據(jù)的更新后,會觸發(fā)當(dāng)前自定義組件的重新渲染。
說明:
使用@Provide和@Consume時應(yīng)避免循環(huán)引用導(dǎo)致死循環(huán)。
@Provide
名稱 | 說明 |
---|---|
裝飾器參數(shù) | 是一個string類型的常量,用于給裝飾的變量起別名。如果規(guī)定別名,則提供對應(yīng)別名的數(shù)據(jù)更新。如果沒有,則使用變量名作為別名。推薦使用@Provide(‘a(chǎn)lias’)這種形式。 |
同步機(jī)制 | @Provide的變量類似@State,可以修改對應(yīng)變量進(jìn)行頁面重新渲染。也可以修改@Consume裝飾的變量,反向修改@State變量。 |
初始值 | 必須設(shè)置初始值。 |
頁面重渲染場景 | 基礎(chǔ)類型(boolean,string,number)變量的改變;@Observed class類型變量及其屬性的修改;添加,刪除,更新數(shù)組中的元素。 |
@Consume
類型 | 說明 |
---|---|
初始值 | 不可設(shè)置默認(rèn)初始值。 |
示例
// xxx.ets
@Entry
@Component
struct CompA {
@Provide("reviewVote") reviewVotes: number = 0;
build() {
Column() {
CompB()
Button(`CompA: ${this.reviewVotes}`)
.margin(10)
.onClick(() = > {
this.reviewVotes += 1;
})
}
}
}
@Component
struct CompB {
build() {
Column() {
CompC()
}
}
}
@Component
struct CompC {
@Consume("reviewVote") reviewVotes: number
build() {
Column() {
Button(`CompC: ${this.reviewVotes}`)
.margin(10)
.onClick(() = > {
this.reviewVotes += 1
})
}.width('100%')
}
}
@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)聽其變化。
說明:
深層次數(shù)據(jù)修改不會觸發(fā)@Watch回調(diào),例如無法監(jiān)聽數(shù)組中對象值的改變。
// 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
return this.totalPurchase
}
// 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)
}
}
}
審核編輯 黃宇
-
變量
+關(guān)注
關(guān)注
0文章
607瀏覽量
28257 -
鴻蒙
+關(guān)注
關(guān)注
56文章
2267瀏覽量
42491 -
Harmony
+關(guān)注
關(guān)注
0文章
51瀏覽量
2556
發(fā)布評論請先 登錄
相關(guān)推薦
評論