@State裝飾的變量阔馋,或稱為狀態(tài)變量玛荞,一旦變量擁有了狀態(tài)屬性,就和自定義組件的渲染綁定起來呕寝。當(dāng)狀態(tài)改變時(shí)勋眯,UI會(huì)發(fā)生對(duì)應(yīng)的渲染改變。
在狀態(tài)變量相關(guān)裝飾器中下梢,@State是最基礎(chǔ)的客蹋,使變量擁有狀態(tài)屬性的裝飾器,它也是大部分狀態(tài)變量的數(shù)據(jù)源孽江。
說明:
從API version 9開始讶坯,該裝飾器支持在ArkTS卡片中使用。
概述
@State裝飾的變量岗屏,與聲明式范式中的其他被裝飾變量一樣辆琅,是私有的,只能從組件內(nèi)部訪問这刷,在聲明時(shí)必須指定其類型和本地初始化婉烟。初始化也可選擇使用命名參數(shù)機(jī)制從父組件完成初始化。
@State裝飾的變量擁有以下特點(diǎn):
@State裝飾的變量與子組件中的@Prop裝飾變量之間建立單向數(shù)據(jù)同步,與@Link崭歧、@ObjectLink裝飾變量之間建立雙向數(shù)據(jù)同步隅很。
@State裝飾的變量生命周期與其所屬自定義組件的生命周期相同。
裝飾器使用規(guī)則說明
@State變量裝飾器 | 說明 |
---|---|
裝飾器參數(shù) | 無 |
同步類型 | 不與父組件中任何類型的變量同步。 |
允許裝飾的變量類型 | Object叔营、class屋彪、string、number绒尊、boolean畜挥、enum類型,以及這些類型的數(shù)組婴谱。 支持Date類型蟹但。 API11及以上支持Map、Set類型谭羔。 支持undefined和null類型华糖。 支持類型的場(chǎng)景請(qǐng)參考觀察變化。 API11及以上支持上述支持類型的聯(lián)合類型瘟裸,比如string | number, string | undefined 或者 ClassA | null客叉,示例見@State支持聯(lián)合類型實(shí)例。 注意 當(dāng)使用undefined和null的時(shí)候话告,建議顯式指定類型兼搏,遵循TypeScipt類型校驗(yàn),比如: @State a : string | undefined = undefiend 是推薦的沙郭,不推薦@State a: string = undefined 佛呻。 |
支持AkrUI框架定義的聯(lián)合類型Length、ResourceStr病线、ResourceColor類型吓著。 類型必須被指定。 不支持any氧苍。 | |
被裝飾變量的初始值 | 必須本地初始化夜矗。 |
變量的傳遞/訪問規(guī)則說明
傳遞/訪問 | 說明 |
---|---|
從父組件初始化 | 可選,從父組件初始化或者本地初始化让虐。如果從父組件初始化將會(huì)覆蓋本地初始化紊撕。 支持父組件中常規(guī)變量(常規(guī)變量對(duì)@State賦值,只是數(shù)值的初始化赡突,常規(guī)變量的變化不會(huì)觸發(fā)UI刷新对扶,只有狀態(tài)變量才能觸發(fā)UI刷新)、@State惭缰、@Link浪南、@Prop、@Provide漱受、@Consume络凿、@ObjectLink、@StorageLink、@StorageProp絮记、@LocalStorageLink和@LocalStorageProp裝飾的變量摔踱,初始化子組件的@State。 |
用于初始化子組件 | @State裝飾的變量支持初始化子組件的常規(guī)變量怨愤、@State派敷、@Link、@Prop撰洗、@Provide篮愉。 |
是否支持組件外訪問 | 不支持,只能在組件內(nèi)訪問差导。 |
圖1 初始化規(guī)則圖示
觀察變化和行為表現(xiàn)
并不是狀態(tài)變量的所有更改都會(huì)引起UI的刷新试躏,只有可以被框架觀察到的修改才會(huì)引起UI刷新。本小節(jié)將介紹什么樣的修改才能被觀察到设褐,以及觀察到變化后冗酿,框架的是怎么引起UI刷新的,即框架的行為表現(xiàn)是什么络断。
觀察變化
-
當(dāng)裝飾的數(shù)據(jù)類型為boolean、string项玛、number類型時(shí)貌笨,可以觀察到數(shù)值的變化。
// for simple type @State count: number = 0; // value changing can be observed this.count = 1;
-
當(dāng)裝飾的數(shù)據(jù)類型為class或者Object時(shí)襟沮,可以觀察到自身的賦值的變化锥惋,和其屬性賦值的變化,即Object.keys(observedObject)返回的所有屬性开伏。例子如下膀跌。
聲明ClassA和Model類。class ClassA { public value: string; constructor(value: string) { this.value = value; } } class Model { public value: string; public name: ClassA; constructor(value: string, a: ClassA) { this.value = value; this.name = a; } }
@State裝飾的類型是Model
// class類型 @State title: Model = new Model('Hello', new ClassA('World'));
對(duì)@State裝飾變量的賦值固灵。
// class類型賦值 this.title = new Model('Hi', new ClassA('ArkUI'));
對(duì)@State裝飾變量的屬性賦值捅伤。
// class屬性的賦值 this.title.value = 'Hi';
嵌套屬性的賦值觀察不到。
// 嵌套的屬性賦值觀察不到 this.title.name.value = 'ArkUI';
-
當(dāng)裝飾的對(duì)象是array時(shí)巫玻,可以觀察到數(shù)組本身的賦值和添加丛忆、刪除、更新數(shù)組的變化仍秤。例子如下熄诡。
聲明Model類。class Model { public value: number; constructor(value: number) { this.value = value; } }
@State裝飾的對(duì)象為Model類型數(shù)組時(shí)诗力。
// 數(shù)組類型 @State title: Model[] = [new Model(11), new Model(1)];
數(shù)組自身的賦值可以觀察到凰浮。
// 數(shù)組賦值 this.title = [new Model(2)];
數(shù)組項(xiàng)的賦值可以觀察到。
// 數(shù)組項(xiàng)賦值 this.title[0] = new Model(2);
刪除數(shù)組項(xiàng)可以觀察到。
// 數(shù)組項(xiàng)更改 this.title.pop();
新增數(shù)組項(xiàng)可以觀察到袜茧。
// 數(shù)組項(xiàng)更改 this.title.push(new Model(12));
數(shù)組項(xiàng)中屬性的賦值觀察不到菜拓。
// 嵌套的屬性賦值觀察不到 this.title[0].value = 6;
-
當(dāng)裝飾的對(duì)象是Date時(shí),可以觀察到Date整體的賦值惫周,同時(shí)可通過調(diào)用Date的接口
setFullYear
,setMonth
,setDate
,setHours
,setMinutes
,setSeconds
,setMilliseconds
,setTime
,setUTCFullYear
,setUTCMonth
,setUTCDate
,setUTCHours
,setUTCMinutes
,setUTCSeconds
,setUTCMilliseconds
更新Date的屬性尘惧。@Entry @Component struct DatePickerExample { @State selectedDate: Date = new Date('2021-08-08') build() { Column() { Button('set selectedDate to 2023-07-08') .margin(10) .onClick(() => { this.selectedDate = new Date('2023-07-08') }) Button('increase the year by 1') .margin(10) .onClick(() => { this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1) }) Button('increase the month by 1') .margin(10) .onClick(() => { this.selectedDate.setMonth(this.selectedDate.getMonth() + 1) }) Button('increase the day by 1') .margin(10) .onClick(() => { this.selectedDate.setDate(this.selectedDate.getDate() + 1) }) DatePicker({ start: new Date('1970-1-1'), end: new Date('2100-1-1'), selected: this.selectedDate }) }.width('100%') } }
當(dāng)裝飾的變量是Map時(shí),可以觀察到Map整體的賦值递递,同時(shí)可通過調(diào)用Map的接口
set
,clear
,delete
更新Map的值喷橙。當(dāng)裝飾的變量是Set時(shí),可以觀察到Set整體的賦值登舞,同時(shí)可通過調(diào)用Set的接口
add
,clear
,delete
更新Set的值贰逾。
框架行為
當(dāng)狀態(tài)變量被改變時(shí),查詢依賴該狀態(tài)變量的組件菠秒;
執(zhí)行依賴該狀態(tài)變量的組件的更新方法疙剑,組件更新渲染;
和該狀態(tài)變量不相關(guān)的組件或者UI描述不會(huì)發(fā)生重新渲染践叠,從而實(shí)現(xiàn)頁(yè)面渲染的按需更新言缤。
使用場(chǎng)景
裝飾簡(jiǎn)單類型的變量
以下示例為@State裝飾的簡(jiǎn)單類型,count被@State裝飾成為狀態(tài)變量禁灼,count的改變引起B(yǎng)utton組件的刷新:
當(dāng)狀態(tài)變量count改變時(shí)管挟,查詢到只有Button組件關(guān)聯(lián)了它;
執(zhí)行Button組件的更新方法弄捕,實(shí)現(xiàn)按需刷新僻孝。
@Entry
@Component
struct MyComponent {
@State count: number = 0;
build() {
Button(`click times: ${this.count}`)
.onClick(() => {
this.count += 1;
})
}
}
裝飾class對(duì)象類型的變量
自定義組件MyComponent定義了被@State裝飾的狀態(tài)變量count和title,其中title的類型為自定義類Model守谓。如果count或title的值發(fā)生變化穿铆,則查詢MyComponent中使用該狀態(tài)變量的UI組件,并進(jìn)行重新渲染斋荞。
EntryComponent中有多個(gè)MyComponent組件實(shí)例荞雏,第一個(gè)MyComponent內(nèi)部狀態(tài)的更改不會(huì)影響第二個(gè)MyComponent。
class Model {
public value: string;
constructor(value: string) {
this.value = value;
}
}
@Entry
@Component
struct EntryComponent {
build() {
Column() {
// 此處指定的參數(shù)都將在初始渲染時(shí)覆蓋本地定義的默認(rèn)值譬猫,并不是所有的參數(shù)都需要從父組件初始化
MyComponent({ count: 1, increaseBy: 2 })
.width(300)
MyComponent({ title: new Model('Hello World 2'), count: 7 })
}
}
}
@Component
struct MyComponent {
@State title: Model = new Model('Hello World');
@State count: number = 0;
private increaseBy: number = 1;
build() {
Column() {
Text(`${this.title.value}`)
.margin(10)
Button(`Click to change title`)
.onClick(() => {
// @State變量的更新將觸發(fā)上面的Text組件內(nèi)容更新
this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI';
})
.width(300)
.margin(10)
Button(`Click to increase count = ${this.count}`)
.onClick(() => {
// @State變量的更新將觸發(fā)該Button組件的內(nèi)容更新
this.count += this.increaseBy;
})
.width(300)
.margin(10)
}
}
}
從該示例中讯檐,我們可以了解到@State變量首次渲染的初始化流程:
-
使用默認(rèn)的本地初始化:
@State title: Model = new Model('Hello World'); @State count: number = 0;
對(duì)于@State來說,命名參數(shù)機(jī)制傳遞的值并不是必選的染服,如果沒有命名參數(shù)傳值别洪,則使用本地初始化的默認(rèn)值:
class C1 {
public count:number;
public increaseBy:number;
constructor(count: number, increaseBy:number) {
this.count = count;
this.increaseBy = increaseBy;
}
}
let obj = new C1(1, 2)
MyComponent(obj)
裝飾Map類型變量
說明:
從API version 11開始,@State支持Map類型柳刮。
在下面的示例中挖垛,message類型為Map<number, string>痒钝,點(diǎn)擊Button改變message的值,視圖會(huì)隨之刷新痢毒。
@Entry
@Component
struct MapSample {
@State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
build() {
Row() {
Column() {
ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
Text(`${item[0]}`).fontSize(30)
Text(`${item[1]}`).fontSize(30)
Divider()
})
Button('init map').onClick(() => {
this.message = new Map([[0, "a"], [1, "b"], [3, "c"]])
})
Button('set new one').onClick(() => {
this.message.set(4, "d")
})
Button('clear').onClick(() => {
this.message.clear()
})
Button('replace the first one').onClick(() => {
this.message.set(0, "aa")
})
Button('delete the first one').onClick(() => {
this.message.delete(0)
})
}
.width('100%')
}
.height('100%')
}
}
裝飾Set類型變量
說明:
從API version 11開始送矩,@State支持Set類型。
在下面的示例中哪替,message類型為Set<number>栋荸,點(diǎn)擊Button改變message的值,視圖會(huì)隨之刷新凭舶。
@Entry
@Component
struct SetSample {
@State message: Set<number> = new Set([0, 1, 2, 3, 4])
build() {
Row() {
Column() {
ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
Text(`${item[0]}`).fontSize(30)
Divider()
})
Button('init set').onClick(() => {
this.message = new Set([0, 1, 2, 3, 4])
})
Button('set new one').onClick(() => {
this.message.add(5)
})
Button('clear').onClick(() => {
this.message.clear()
})
Button('delete the first one').onClick(() => {
this.message.delete(0)
})
}
.width('100%')
}
.height('100%')
}
}
State支持聯(lián)合類型實(shí)例
@State支持聯(lián)合類型和undefined和null晌块,在下面的示例中,count類型為number | undefined帅霜,點(diǎn)擊Button改變count的屬性或者類型匆背,視圖會(huì)隨之刷新。
@Entry
@Component
struct EntryComponent {
build() {
Column() {
MyComponent()
}
}
}
@Component
struct MyComponent {
@State count: number | undefined = 0;
build() {
Column() {
Text(`count(${this.count})`)
Button('change')
.onClick(() => {
this.count = undefined;
})
}
}
}
常見問題
使用箭頭函數(shù)改變狀態(tài)變量未生效
箭頭函數(shù)體內(nèi)的this對(duì)象身冀,就是定義該函數(shù)時(shí)所在的作用域指向的對(duì)象钝尸,而不是使用時(shí)所在的作用域指向的對(duì)象。所以在該場(chǎng)景下搂根, changeCoverUrl的this指向PlayDetailViewModel珍促,而不是被裝飾器@State代理的狀態(tài)變量。
反例:
export default class PlayDetailViewModel {
coverUrl: string = '#00ff00'
changeCoverUrl= ()=> {
this.coverUrl = '#00F5FF'
}
}
import PlayDetailViewModel from './PlayDetailViewModel'
@Entry
@Component
struct PlayDetailPage {
@State vm: PlayDetailViewModel = new PlayDetailViewModel()
build() {
Stack() {
Text(this.vm.coverUrl).width(100).height(100).backgroundColor(this.vm.coverUrl)
Row() {
Button('點(diǎn)擊改變顏色')
.onClick(() => {
this.vm.changeCoverUrl()
})
}
}
.width('100%')
.height('100%')
.alignContent(Alignment.Top)
}
}
所以要將當(dāng)前this.vm傳入剩愧,調(diào)用代理狀態(tài)變量的屬性賦值踢星。
正例:
export default class PlayDetailViewModel {
coverUrl: string = '#00ff00'
changeCoverUrl= (model:PlayDetailViewModel)=> {
model.coverUrl = '#00F5FF'
}
}
import PlayDetailViewModel from './PlayDetailViewModel'
@Entry
@Component
struct PlayDetailPage {
@State vm: PlayDetailViewModel = new PlayDetailViewModel()
build() {
Stack() {
Text(this.vm.coverUrl).width(100).height(100).backgroundColor(this.vm.coverUrl)
Row() {
Button('點(diǎn)擊改變顏色')
.onClick(() => {
let self = this.vm
this.vm.changeCoverUrl(self)
})
}
}
.width('100%')
.height('100%')
.alignContent(Alignment.Top)
}
}
狀態(tài)變量的修改放在構(gòu)造函數(shù)內(nèi)未生效
在狀態(tài)管理中,類會(huì)被一層“代理”進(jìn)行包裝隙咸。當(dāng)在組件中改變?cè)擃惖某蓡T變量時(shí),會(huì)被該代理進(jìn)行攔截成洗,在更改數(shù)據(jù)源中值的同時(shí)五督,也會(huì)將變化通知給綁定的組件,從而實(shí)現(xiàn)觀測(cè)變化與觸發(fā)刷新瓶殃。當(dāng)開發(fā)者把狀態(tài)變量的修改放在構(gòu)造函數(shù)里時(shí)充包,此修改不會(huì)經(jīng)過代理(因?yàn)槭侵苯訉?duì)數(shù)據(jù)源中的值進(jìn)行修改),即使修改成功執(zhí)行遥椿,也無法觀測(cè)UI的刷新基矮。
【反例】
@Entry
@Component
struct Index {
@State viewModel: TestModel = new TestModel();
build() {
Row() {
Column() {
Text(this.viewModel.isSuccess ? 'success' : 'failed')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.viewModel.query()
})
}.width('100%')
}.height('100%')
}
}
export class TestModel {
isSuccess: boolean = false
model: Model
constructor() {
this.model = new Model(() => {
this.isSuccess = true
console.log(`this.isSuccess: ${this.isSuccess}`)
})
}
query() {
this.model.query()
}
}
export class Model {
callback: () => void
constructor(cb: () => void) {
this.callback = cb
}
query() {
this.callback()
}
}
上文示例代碼將狀態(tài)變量的修改放在構(gòu)造函數(shù)內(nèi),界面開始時(shí)顯示“failed”冠场,點(diǎn)擊后日志打印“this.isSuccess: true”說明修改成功家浇,但界面依舊顯示“failed”,未實(shí)現(xiàn)刷新碴裙。
【正例】
@Entry
@Component
struct Index {
@State viewModel: TestModel = new TestModel();
build() {
Row() {
Column() {
Text(this.viewModel.isSuccess ? 'success' : 'failed')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
this.viewModel.query()
})
}.width('100%')
}.height('100%')
}
}
export class TestModel {
isSuccess: boolean = false
model: Model = new Model(() => {
})
query() {
this.model = new Model(() => {
this.isSuccess = true
})
this.model.query()
}
}
export class Model {
callback: () => void
constructor(cb: () => void) {
this.callback = cb
}
query() {
this.callback()
}
}
上文示例代碼將狀態(tài)變量的修改放在類的普通方法中钢悲,界面開始時(shí)顯示“failed”点额,點(diǎn)擊后顯示“success”。
寫在最后
- 如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助莺琳,我想邀請(qǐng)你幫我三個(gè)小忙:
- 點(diǎn)贊还棱,轉(zhuǎn)發(fā),有你們的 『點(diǎn)贊和評(píng)論』惭等,才是我創(chuàng)造的動(dòng)力珍手。
- 關(guān)注小編,同時(shí)可以期待后續(xù)文章ing??辞做,不定期分享原創(chuàng)知識(shí)琳要。
- 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識(shí)點(diǎn),請(qǐng)移步前往小編:
https://gitee.com/MNxiaona/733GH/blob/master/jianshu