1# \@State装饰器:组件内状态 2 3 4\@State装饰的变量,或称为状态变量,一旦变量拥有了状态属性,就和自定义组件的渲染绑定起来。当状态改变时,UI会发生对应的渲染改变。 5 6 7在状态变量相关装饰器中,\@State是最基础的,使变量拥有状态属性的装饰器,它也是大部分状态变量的数据源。 8 9 10> **说明:** 11> 12> 从API version 9开始,该装饰器支持在ArkTS卡片中使用。 13 14 15## 概述 16 17\@State装饰的变量,与声明式范式中的其他被装饰变量一样,是私有的,只能从组件内部访问,在声明时必须指定其类型和本地初始化。初始化也可选择使用命名参数机制从父组件完成初始化。 18 19\@State装饰的变量拥有以下特点: 20 21- \@State装饰的变量与子组件中的\@Prop装饰变量之间建立单向数据同步,与\@Link、\@ObjectLink装饰变量之间建立双向数据同步。 22 23- \@State装饰的变量生命周期与其所属自定义组件的生命周期相同。 24 25 26## 装饰器使用规则说明 27 28| \@State变量装饰器 | 说明 | 29| ------------------ | ------------------------------------------------------------ | 30| 装饰器参数 | 无 | 31| 同步类型 | 不与父组件中任何类型的变量同步。 | 32| 允许装饰的变量类型 | Object、class、string、number、boolean、enum类型,以及这些类型的数组。<br/>支持Date类型。<br/>支持类型的场景请参考[观察变化](#观察变化)。<br/>类型必须被指定。<br/>不支持any,不支持简单类型和复杂类型的联合类型,不允许使用undefined和null。<br/>**说明:**<br/>不支持Length、ResourceStr、ResourceColor类型,Length、ResourceStr、ResourceColor为简单类型和复杂类型的联合类型。 | 33| 被装饰变量的初始值 | 必须本地初始化。 | 34 35 36## 变量的传递/访问规则说明 37 38| 传递/访问 | 说明 | 39| ------------------ | ------------------------------------------------------------ | 40| 从父组件初始化 | 可选,从父组件初始化或者本地初始化。如果从父组件初始化将会覆盖本地初始化。<br/>支持父组件中常规变量(常规变量对@State赋值,只是数值的初始化,常规变量的变化不会触发UI刷新,只有状态变量才能触发UI刷新)、\@State、\@Link、\@Prop、\@Provide、\@Consume、\@ObjectLink、\@StorageLink、\@StorageProp、\@LocalStorageLink和\@LocalStorageProp装饰的变量,初始化子组件的\@State。 | 41| 用于初始化子组件 | \@State装饰的变量支持初始化子组件的常规变量、\@State、\@Link、\@Prop、\@Provide。 | 42| 是否支持组件外访问 | 不支持,只能在组件内访问。 | 43 44 **图1** 初始化规则图示 45 46 47 48 49## 观察变化和行为表现 50 51并不是状态变量的所有更改都会引起UI的刷新,只有可以被框架观察到的修改才会引起UI刷新。本小节将介绍什么样的修改才能被观察到,以及观察到变化后,框架的是怎么引起UI刷新的,即框架的行为表现是什么。 52 53 54### 观察变化 55 56- 当装饰的数据类型为boolean、string、number类型时,可以观察到数值的变化。 57 58 ```ts 59 // for simple type 60 @State count: number = 0; 61 // value changing can be observed 62 this.count = 1; 63 ``` 64 65- 当装饰的数据类型为class或者Object时,可以观察到自身的赋值的变化,和其属性赋值的变化,即Object.keys(observedObject)返回的所有属性。例子如下。 66 声明ClassA和Model类。 67 68 ```ts 69 class ClassA { 70 public value: string; 71 72 constructor(value: string) { 73 this.value = value; 74 } 75 } 76 77 class Model { 78 public value: string; 79 public name: ClassA; 80 constructor(value: string, a: ClassA) { 81 this.value = value; 82 this.name = a; 83 } 84 } 85 ``` 86 87 \@State装饰的类型是Model 88 89 ```ts 90 // class类型 91 @State title: Model = new Model('Hello', new ClassA('World')); 92 ``` 93 94 对\@State装饰变量的赋值。 95 96 ```ts 97 // class类型赋值 98 this.title = new Model('Hi', new ClassA('ArkUI')); 99 ``` 100 101 对\@State装饰变量的属性赋值。 102 103 ```ts 104 // class属性的赋值 105 this.title.value = 'Hi'; 106 ``` 107 108 嵌套属性的赋值观察不到。 109 110 ```ts 111 // 嵌套的属性赋值观察不到 112 this.title.name.value = 'ArkUI'; 113 ``` 114- 当装饰的对象是array时,可以观察到数组本身的赋值和添加、删除、更新数组的变化。例子如下。 115 声明Model类。 116 117 ```ts 118 class Model { 119 public value: number; 120 constructor(value: number) { 121 this.value = value; 122 } 123 } 124 ``` 125 126 \@State装饰的对象为Model类型数组时。 127 128 ```ts 129 // 数组类型 130 @State title: Model[] = [new Model(11), new Model(1)]; 131 ``` 132 133 数组自身的赋值可以观察到。 134 135 ```ts 136 // 数组赋值 137 this.title = [new Model(2)]; 138 ``` 139 140 数组项的赋值可以观察到。 141 142 ```ts 143 // 数组项赋值 144 this.title[0] = new Model(2); 145 ``` 146 147 删除数组项可以观察到。 148 149 ```ts 150 // 数组项更改 151 this.title.pop(); 152 ``` 153 154 新增数组项可以观察到。 155 156 ```ts 157 // 数组项更改 158 this.title.push(new Model(12)); 159 ``` 160 161 数组项中属性的赋值观察不到。 162 163 ```ts 164 // 嵌套的属性赋值观察不到 165 this.title[0].value = 6; 166 ``` 167 168- 当装饰的对象是Date时,可以观察到Date整体的赋值,同时可通过调用Date的接口`setFullYear`, `setMonth`, `setDate`, `setHours`, `setMinutes`, `setSeconds`, `setMilliseconds`, `setTime`, `setUTCFullYear`, `setUTCMonth`, `setUTCDate`, `setUTCHours`, `setUTCMinutes`, `setUTCSeconds`, `setUTCMilliseconds` 更新Date的属性。 169 170 ```ts 171 @Entry 172 @Component 173 struct DatePickerExample { 174 @State selectedDate: Date = new Date('2021-08-08') 175 176 build() { 177 Column() { 178 Button('set selectedDate to 2023-07-08') 179 .margin(10) 180 .onClick(() => { 181 this.selectedDate = new Date('2023-07-08') 182 }) 183 Button('increase the year by 1') 184 .margin(10) 185 .onClick(() => { 186 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1) 187 }) 188 Button('increase the month by 1') 189 .margin(10) 190 .onClick(() => { 191 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1) 192 }) 193 Button('increase the day by 1') 194 .margin(10) 195 .onClick(() => { 196 this.selectedDate.setDate(this.selectedDate.getDate() + 1) 197 }) 198 DatePicker({ 199 start: new Date('1970-1-1'), 200 end: new Date('2100-1-1'), 201 selected: this.selectedDate 202 }) 203 }.width('100%') 204 } 205 } 206 ``` 207 208### 框架行为 209 210- 当状态变量被改变时,查询依赖该状态变量的组件; 211 212- 执行依赖该状态变量的组件的更新方法,组件更新渲染; 213 214- 和该状态变量不相关的组件或者UI描述不会发生重新渲染,从而实现页面渲染的按需更新。 215 216 217## 使用场景 218 219 220### 装饰简单类型的变量 221 222以下示例为\@State装饰的简单类型,count被\@State装饰成为状态变量,count的改变引起Button组件的刷新: 223 224- 当状态变量count改变时,查询到只有Button组件关联了它; 225 226- 执行Button组件的更新方法,实现按需刷新。 227 228 229```ts 230@Entry 231@Component 232struct MyComponent { 233 @State count: number = 0; 234 235 build() { 236 Button(`click times: ${this.count}`) 237 .onClick(() => { 238 this.count += 1; 239 }) 240 } 241} 242``` 243 244 245### 装饰class对象类型的变量 246 247- 自定义组件MyComponent定义了被\@State装饰的状态变量count和title,其中title的类型为自定义类Model。如果count或title的值发生变化,则查询MyComponent中使用该状态变量的UI组件,并进行重新渲染。 248 249- EntryComponent中有多个MyComponent组件实例,第一个MyComponent内部状态的更改不会影响第二个MyComponent。 250 251 252 253```ts 254class Model { 255 public value: string; 256 257 constructor(value: string) { 258 this.value = value; 259 } 260} 261 262@Entry 263@Component 264struct EntryComponent { 265 build() { 266 Column() { 267 // 此处指定的参数都将在初始渲染时覆盖本地定义的默认值,并不是所有的参数都需要从父组件初始化 268 MyComponent({ count: 1, increaseBy: 2 }) 269 .width(300) 270 MyComponent({ title: new Model('Hello World 2'), count: 7 }) 271 } 272 } 273} 274 275@Component 276struct MyComponent { 277 @State title: Model = new Model('Hello World'); 278 @State count: number = 0; 279 private increaseBy: number = 1; 280 281 build() { 282 Column() { 283 Text(`${this.title.value}`) 284 .margin(10) 285 Button(`Click to change title`) 286 .onClick(() => { 287 // @State变量的更新将触发上面的Text组件内容更新 288 this.title.value = this.title.value === 'Hello ArkUI' ? 'Hello World' : 'Hello ArkUI'; 289 }) 290 .width(300) 291 .margin(10) 292 293 Button(`Click to increase count = ${this.count}`) 294 .onClick(() => { 295 // @State变量的更新将触发该Button组件的内容更新 296 this.count += this.increaseBy; 297 }) 298 .width(300) 299 .margin(10) 300 } 301 } 302} 303``` 304 305 306 307从该示例中,我们可以了解到\@State变量首次渲染的初始化流程: 308 309 3101. 使用默认的本地初始化: 311 312 ```ts 313 @State title: Model = new Model('Hello World'); 314 @State count: number = 0; 315 ``` 316 3172. 对于\@State来说,命名参数机制传递的值并不是必选的,如果没有命名参数传值,则使用本地初始化的默认值: 318 319 ```ts 320 class C1 { 321 public count:number; 322 public increaseBy:number; 323 constructor(count: number, increaseBy:number) { 324 this.count = count; 325 this.increaseBy = increaseBy; 326 } 327 } 328 let obj = new C1(1, 2) 329 MyComponent(obj) 330 ``` 331 332 333## 常见问题 334 335### 使用箭头函数改变状态变量未生效 336 337箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。所以在该场景下, changeCoverUrl的this指向PlayDetailViewModel,而不是被装饰器@State代理的状态变量。 338 339反例: 340 341```ts 342 343export default class PlayDetailViewModel { 344 coverUrl: string = '#00ff00' 345 346 changeCoverUrl= ()=> { 347 this.coverUrl = '#00F5FF' 348 } 349 350} 351``` 352 353```ts 354import PlayDetailViewModel from './PlayDetailViewModel' 355 356@Entry 357@Component 358struct PlayDetailPage { 359 @State vm: PlayDetailViewModel = new PlayDetailViewModel() 360 361 build() { 362 Stack() { 363 Text(this.vm.coverUrl).width(100).height(100).backgroundColor(this.vm.coverUrl) 364 Row() { 365 Button('点击改变颜色') 366 .onClick(() => { 367 this.vm.changeCoverUrl() 368 }) 369 } 370 } 371 .width('100%') 372 .height('100%') 373 .alignContent(Alignment.Top) 374 } 375} 376``` 377 378所以要将当前this.vm传入,调用代理状态变量的属性赋值。 379 380正例: 381 382```ts 383 384export default class PlayDetailViewModel { 385 coverUrl: string = '#00ff00' 386 387 changeCoverUrl= (model:PlayDetailViewModel)=> { 388 model.coverUrl = '#00F5FF' 389 } 390 391} 392``` 393 394```ts 395import PlayDetailViewModel from './PlayDetailViewModel' 396 397@Entry 398@Component 399struct PlayDetailPage { 400 @State vm: PlayDetailViewModel = new PlayDetailViewModel() 401 402 build() { 403 Stack() { 404 Text(this.vm.coverUrl).width(100).height(100).backgroundColor(this.vm.coverUrl) 405 Row() { 406 Button('点击改变颜色') 407 .onClick(() => { 408 let self = this.vm 409 this.vm.changeCoverUrl(self) 410 }) 411 } 412 } 413 .width('100%') 414 .height('100%') 415 .alignContent(Alignment.Top) 416 } 417} 418``` 419 420### 状态变量的修改放在构造函数内未生效 421 422在状态管理中,类会被一层“代理”进行包装。当在组件中改变该类的成员变量时,会被该代理进行拦截,在更改数据源中值的同时,也会将变化通知给绑定的组件,从而实现观测变化与触发刷新。当开发者把状态变量的修改放在构造函数里时,此修改不会经过代理(因为是直接对数据源中的值进行修改),即使修改成功执行,也无法观测UI的刷新。 423 424【反例】 425 426```ts 427@Entry 428@Component 429struct Index { 430 @State viewModel: TestModel = new TestModel(); 431 432 build() { 433 Row() { 434 Column() { 435 Text(this.viewModel.isSuccess ? 'success' : 'failed') 436 .fontSize(50) 437 .fontWeight(FontWeight.Bold) 438 .onClick(() => { 439 this.viewModel.query() 440 }) 441 }.width('100%') 442 }.height('100%') 443 } 444} 445 446export class TestModel { 447 isSuccess: boolean = false 448 model: Model 449 450 constructor() { 451 this.model = new Model(() => { 452 this.isSuccess = true 453 console.log(`this.isSuccess: ${this.isSuccess}`) 454 }) 455 } 456 457 query() { 458 this.model.query() 459 } 460} 461 462export class Model { 463 callback: () => void 464 465 constructor(cb: () => void) { 466 this.callback = cb 467 } 468 469 query() { 470 this.callback() 471 } 472} 473``` 474 475上文示例代码将状态变量的修改放在构造函数内,界面开始时显示“failed”,点击后日志打印“this.isSuccess: true”说明修改成功,但界面依旧显示“failed”,未实现刷新。 476 477【正例】 478 479```ts 480@Entry 481@Component 482struct Index { 483 @State viewModel: TestModel = new TestModel(); 484 485 build() { 486 Row() { 487 Column() { 488 Text(this.viewModel.isSuccess ? 'success' : 'failed') 489 .fontSize(50) 490 .fontWeight(FontWeight.Bold) 491 .onClick(() => { 492 this.viewModel.query() 493 }) 494 }.width('100%') 495 }.height('100%') 496 } 497} 498 499export class TestModel { 500 isSuccess: boolean = false 501 model: Model = new Model(() => { 502 }) 503 504 query() { 505 this.model = new Model(() => { 506 this.isSuccess = true 507 }) 508 this.model.query() 509 } 510} 511 512export class Model { 513 callback: () => void 514 515 constructor(cb: () => void) { 516 this.callback = cb 517 } 518 519 query() { 520 this.callback() 521 } 522} 523``` 524 525上文示例代码将状态变量的修改放在类的普通方法中,界面开始时显示“failed”,点击后显示“success”。