1# State Management with Page-level Variables 2 3This topic covers how to manage the states with page-level variables with the **@State**, **@Prop**, **@Link**, **@Provide**, **@Consume**, **@ObjectLink**, **@Observed**, and **@Watch** decorators. 4 5For details about the constraints of the **@State**, **@Provide**, **@Link**, and **@Consume** decorated state variables, see [Restrictions on Data Type Declarations of State Variables](./arkts-restrictions-and-extensions.md). 6 7## @State 8 9The **@State** decorated variable is the internal state data of the component. When the state data is modified, the **build** method of the component is called to refresh the UI. 10 11The **@State** data has the following features: 12 13- Support for multiple types: The following types are supported: strong types by value and by reference, including **class**, **number**, **boolean**, **string**, as well as arrays of these types, that is, **Array\<class>**, **Array\<number>**, **Array\<boolean>**, and **Array\<string>**. **object** and **any** are not supported. 14- Support for multiple instances: Multiple instances can coexist in a component. The internal state data of different instances is independent. 15- **Private**: An attribute marked with **@State** can only be accessed within the component. 16- Local initialization required: Initial values must be allocated to all **@State** decorated variables. Uninitialized variables may cause undefined framework exceptions. 17- Support for setting of initial attribute values based on the state variable name: When creating a component instance, you can explicitly specify the initial value of the **@State** decorated attribute based on the variable name. 18 19**Example** 20 21In the following example: 22 23- Two **@State** decorated variables, **count** and **title**, have been defined for **MyComponent**. If the value of **count** or **title** changes, the **build** method of **MyComponent** needs to be called to render the component again. 24 25- The **EntryComponent** has multiple **MyComponent** instances. The internal status change of the first **MyComponent** instance does not affect the second **MyComponent** instance. 26 27- When creating a **MyComponent** instance, initialize the variables in the component based on the variable name. For example: 28 29 ```ts 30 MyComponent({ title: { value: 'Hello World 2' }, count: 7 }) 31 ``` 32 33```ts 34// xxx.ets 35class Model { 36 value: string 37 38 constructor(value: string) { 39 this.value = value 40 } 41} 42 43@Entry 44@Component 45struct EntryComponent { 46 build() { 47 Column() { 48 MyComponent ({ count: 1,increaseBy:2 }) // First MyComponent instance 49 MyComponent({ title: { value:'Hello World 2' }, count: 7 }) // Second MyComponent instance 50 } 51 } 52} 53 54@Component 55struct MyComponent { 56 @State title: Model = { value: 'Hello World' } 57 @State count: number = 0 58 private toggle: string = 'Hello World' 59 private increaseBy: number = 1 60 61 build() { 62 Column() { 63 Text(`${this.title.value}`).fontSize(30) 64 Button('Click to change title') 65 .margin(20) 66 .onClick(() => { 67 // Change the value of the internal status variable title. 68 this.title.value = (this.toggle == this.title.value) ? 'Hello World' : 'Hello ArkUI' 69 }) 70 71 Button(`Click to increase count=${this.count}`) 72 .margin(20) 73 .onClick(() => { 74 // Change the value of the internal status variable count. 75 this.count += this.increaseBy 76 }) 77 } 78 } 79} 80``` 81 82 83## @Prop 84 85**@Prop** and **@State** have the same semantics but different initialization modes. A **@Prop** decorated variable in a component must be initialized using the **@State** decorated variable in its parent component. The **@Prop** decorated variable can be modified in the component, but the modification is not updated to the parent component; the modification to the **@State** decorated variable is synchronized to the **@Prop** decorated variable. That is, **@Prop** establishes one-way data binding. 86 87The **@Prop** decorated state variable has the following features: 88 89- Support for simple types: The number, string, and boolean types are supported. 90- Private: Data is accessed only within the component. 91- Support for multiple instances: A component can have multiple attributes decorated by **@Prop**. 92- Support for initialization with a value passed to the @Prop decorated variable: When a new instance of the component is created, all **@Prop** variables must be initialized. Initialization inside the component is not supported. 93 94**Example** 95 96In the following example, when the user presses **+1** or **-1**, the status of the parent component changes and the **build** method is executed again. In this case, a new **CountDownComponent** instance is created. The **countDownStartValue** attribute of the parent component is used to initialize the **@Prop** decorated variable of the child component. When the **count - costOfOneAttempt** button of the child component is touched, the value of the **@Prop** decorated variable **count** is changed. As a result, the **CountDownComponent** is rendered again. However, the change of the **count** value does not affect the **countDownStartValue** value of the parent component. 97 98```ts 99// xxx.ets 100@Entry 101@Component 102struct ParentComponent { 103 @State countDownStartValue: number = 10 // Initialize countDownStartValue 104 105 build() { 106 Column() { 107 Text(`Grant ${this.countDownStartValue} nuggets to play.`).fontSize(18) 108 Button('+1 - Nuggets in New Game') 109 .margin(15) 110 .onClick(() => { 111 this.countDownStartValue += 1 112 }) 113 114 Button('-1 - Nuggets in New Game') 115 .margin(15) 116 .onClick(() => { 117 this.countDownStartValue -= 1 118 }) 119 // When creating a child component, you must provide the initial value of its @Prop decorated variable count in the constructor parameter and initialize the regular variable costOfOneAttempt (not @Prop decorated). 120 CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 }) 121 } 122 } 123} 124 125@Component 126struct CountDownComponent { 127 @Prop count: number 128 private costOfOneAttempt: number 129 130 build() { 131 Column() { 132 if (this.count > 0) { 133 Text(`You have ${this.count} Nuggets left`).fontSize(18) 134 } else { 135 Text('Game over!').fontSize(18) 136 } 137 138 Button('count - costOfOneAttempt') 139 .margin(15) 140 .onClick(() => { 141 this.count -= this.costOfOneAttempt 142 }) 143 } 144 } 145} 146``` 147 148 149## @Link 150 151Two-way binding can be established between the **@Link** decorated variable and the **@State** decorated variable of the parent component. The **@Link** data has the following features: 152 153- Support for multiple types: The **@Link** decorated variables support the data types the same as the **@State** decorated variables; that is, the value can be of the following types: class, number, string, boolean, or arrays of these types. 154- Private: Data is accessed only within the component. 155- Single data source: The variable used to initialize the **@Link** decorated variable in a component must be a state variable defined in the parent component. 156- **Two-way binding**: When a child component changes the **@Link** decorated variable, the **@State** decorated variable of its parent component is also changed. 157- Support for initialization with the variable reference passed to the @Link decorated variable: When creating an instance of the component, you must use the naming parameter to initialize all **@Link** decorated variables. **@Link** decorated variables can be initialized by using the reference of the **@State** or **@Link** decorated variable. Wherein, the **@State** decorated variables can be referenced using the **'$'** operator. 158 159> **NOTE** 160> 161> A **@Link** decorated variable cannot be initialized inside the component. 162 163**Simple Type Example** 164 165The **@Link** semantics are derived from the '**$**' operator. In other words, **$isPlaying** is the two-way binding of the internal state **this.isPlaying**. When the button in the **PlayButton** child component is touched, the value of the **@Link** decorated variable is changed, and **PlayButton** together with the **\<Text>** and **\<Button>** components of the parent component is refreshed. Similarly, when the button in the parent component is touched, the value of **this.isPlaying** is changed, and **PlayButton** together with the **\<Text>** and **\<Button>** components of the parent component is refreshed. 166 167```ts 168// xxx.ets 169@Entry 170@Component 171struct Player { 172 @State isPlaying: boolean = false 173 174 build() { 175 Column() { 176 PlayButton({ buttonPlaying: $isPlaying }) 177 Text(`Player is ${this.isPlaying ? '' : 'not'} playing`).fontSize(18) 178 Button('Parent:' + this.isPlaying) 179 .margin(15) 180 .onClick(() => { 181 this.isPlaying = !this.isPlaying 182 }) 183 } 184 } 185} 186 187@Component 188struct PlayButton { 189 @Link buttonPlaying: boolean 190 191 build() { 192 Column() { 193 Button(this.buttonPlaying ? 'pause' : 'play') 194 .margin(20) 195 .onClick(() => { 196 this.buttonPlaying = !this.buttonPlaying 197 }) 198 } 199 } 200} 201``` 202 203**Complex Type Example** 204 205```ts 206// xxx.ets 207@Entry 208@Component 209struct Parent { 210 @State arr: number[] = [1, 2, 3] 211 212 build() { 213 Column() { 214 Child({ items: $arr }) 215 Button('Parent Button: splice') 216 .margin(10) 217 .onClick(() => { 218 this.arr.splice(0, 1, 60) 219 }) 220 ForEach(this.arr, item => { 221 Text(item.toString()).fontSize(18).margin(10) 222 }, item => item.toString()) 223 } 224 } 225} 226 227 228@Component 229struct Child { 230 @Link items: number[] 231 232 build() { 233 Column() { 234 Button('Child Button1: push') 235 .margin(15) 236 .onClick(() => { 237 this.items.push(100) 238 }) 239 Button('Child Button2: replace whole item') 240 .margin(15) 241 .onClick(() => { 242 this.items = [100, 200, 300] 243 }) 244 } 245 } 246} 247``` 248 249**Example of Using @Link, @State, and @Prop Together** 250 251In the following example, **ParentView** contains two child components: **ChildA** and **ChildB**. The **counter** state variable of **ParentView** is used to initialize the **@Prop** decorated variable of **ChildA** and the **@Link** decorated variable of **ChildB**. 252 253- **@Link** establishes two-way binding between **ChildB** and **ParentView**.Value changes of the **counterRef** state variable in **ChildB** will be synchronized to **ParentView** and **ChildA**. 254- **@Prop** establishes one-way binding between **ChildA** and **ParentView**. Value changes of the **counterVal** state variable in **ChildA** will trigger a re-render of **ChildA**, but will not be synchronized to **ParentView** or **ChildB**. 255 256```ts 257// xxx.ets 258@Entry 259@Component 260struct ParentView { 261 @State counter: number = 0 262 263 build() { 264 Column() { 265 ChildA({ counterVal: this.counter }) 266 ChildB({ counterRef: $counter }) 267 } 268 } 269} 270 271@Component 272struct ChildA { 273 @Prop counterVal: number 274 275 build() { 276 Button(`ChildA: (${this.counterVal}) + 1`) 277 .margin(15) 278 .onClick(() => { 279 this.counterVal += 1 280 }) 281 } 282} 283 284@Component 285struct ChildB { 286 @Link counterRef: number 287 288 build() { 289 Button(`ChildB: (${this.counterRef}) + 1`) 290 .margin(15) 291 .onClick(() => { 292 this.counterRef += 1 293 }) 294 } 295} 296``` 297 298## @Observed and @ObjectLink 299 300When you need to set up bidirectional synchronization for a parent variable (**parent_a**) between the parent and child components, you can use **@State** to decorate the variable (**parent_a**) in the parent component and use **@Link** to decorate the corresponding variable (**child_a**) in the child component. In this way, data can be synchronized between the parent component and the specific child component, and between the parent component and its other child components. As shown below, bidirectional synchronization is configured for variables of **ClassA** in the parent and child components. If attribute **c** of the variable in child component 1 has its value changed, the parent component will be notified to synchronize the change. If attribute **c** in the parent component has its value changed, all child components will be notified to synchronize the change. 301 302 303 304In the preceding example, full synchronization is performed for a data object. If you want to synchronize partial information of a data object in a parent component, and if the information is a class object, use **@ObjectLink** and **@Observed** instead, as shown below. 305 306 307 308### Configuration Requirements 309 310- **@Observed** applies to classes, and **@ObjectLink** applies to variables. 311 312- The variables decorated by **@ObjectLink** must be of the class type. 313 - The classes must be decorated by **@Observed**. 314 - Parameters of the simple types are not supported. You can use **@Prop** to perform unidirectional synchronization. 315 316- **@ObjectLink** decorated variables are immutable. 317 - Attribute changes are allowed. If an object is referenced by multiple **@ObjectLink** decorated variables, all custom components that have these variables will be notified for re-rendering. 318 319- Default values cannot be set for **@ObjectLink** decorated variables. 320 - The parent component must be initialized with a TypeScript expression that involves variables decorated by **@State**, **@Link**, **@StorageLink**, **@Provide**, or **@Consume**. 321 322- **@ObjectLink** decorated variables are private variables and can be accessed only within the component. 323 324 325### Example 326 327```ts 328// xxx.ets 329// Use @ObjectLink and @Observed to set up bidirectional synchronization for the class object ClassA between the parent component ViewB and the child component ViewA. In this way, changes made to ClassA in ViewA will be synchronized to ViewB and other child components bound to ClassA. 330var nextID: number = 0 331 332@Observed 333class ClassA { 334 public name: string 335 public c: number 336 public id: number 337 338 constructor(c: number, name: string = 'OK') { 339 this.name = name 340 this.c = c 341 this.id = nextID++ 342 } 343} 344 345@Component 346struct ViewA { 347 label: string = 'ViewA1' 348 @ObjectLink a: ClassA 349 350 build() { 351 Row() { 352 Button(`ViewA [${this.label}] this.a.c= ${this.a.c} +1`) 353 .onClick(() => { 354 this.a.c += 1 355 }) 356 }.margin({ top: 10 }) 357 } 358} 359 360@Entry 361@Component 362struct ViewB { 363 @State arrA: ClassA[] = [new ClassA(0), new ClassA(0)] 364 365 build() { 366 Column() { 367 ForEach(this.arrA, (item) => { 368 ViewA({ label: `#${item.id}`, a: item }) 369 }, (item) => item.id.toString()) 370 ViewA({ label: `this.arrA[first]`, a: this.arrA[0] }) 371 ViewA({ label: `this.arrA[last]`, a: this.arrA[this.arrA.length - 1] }) 372 373 Button(`ViewB: reset array`) 374 .margin({ top: 10 }) 375 .onClick(() => { 376 this.arrA = [new ClassA(0), new ClassA(0)] 377 }) 378 Button(`ViewB: push`) 379 .margin({ top: 10 }) 380 .onClick(() => { 381 this.arrA.push(new ClassA(0)) 382 }) 383 Button(`ViewB: shift`) 384 .margin({ top: 10 }) 385 .onClick(() => { 386 this.arrA.shift() 387 }) 388 }.width('100%') 389 } 390} 391``` 392 393 394## @Provide and @Consume 395 396As the data provider, **@Provide** can update the data of child nodes and trigger page rendering. After **@Consume** detects that the **@Provide** decorated variable is updated, it will initiate re-rendering of the current custom component. 397 398> **NOTE** 399> 400> When using **@Provide** and **@Consume**, avoid circular reference that may lead to infinite loops. 401 402### @Provide 403 404| Name | Description | 405| -------------- | ------------------------------------------------------------ | 406| Decorator parameter | A constant of the string type, which is used to set an alias for a decorated variable. If an alias is specified, implement the data update for this alias. If there is no alias, use the variable name as the alias. **@Provide(*'alias'*)** is recommended.| 407| Synchronization mechanism | The **@Provide** decorated variable is similar to the **@State** decorated variable. You can change the value of the variable to trigger a re-render. You can also modify the **@Consume** decorated variable to modify the **@State** decorated variable reversely.| 408| Initial value | The initial value must be set. | 409| Page re-rendering scenarios| Page re-rendering is triggered in the following scenarios:<br>- Changes of variables of simple types (boolean, string, and number)<br>- Changes of the **@Observed** decorated classes or their attributes<br>- Addition, deletion, or updating of elements in an array| 410 411### @Consume 412 413| Type | Description | 414| ------ | ---------------- | 415| Initial value| The default initial value cannot be set.| 416 417### Example 418 419```ts 420// xxx.ets 421@Entry 422@Component 423struct CompA { 424 @Provide("reviewVote") reviewVotes: number = 0; 425 426 build() { 427 Column() { 428 CompB() 429 Button(`CompA: ${this.reviewVotes}`) 430 .margin(10) 431 .onClick(() => { 432 this.reviewVotes += 1; 433 }) 434 } 435 } 436} 437 438@Component 439struct CompB { 440 build() { 441 Column() { 442 CompC() 443 } 444 } 445} 446 447@Component 448struct CompC { 449 @Consume("reviewVote") reviewVotes: number 450 451 build() { 452 Column() { 453 Button(`CompC: ${this.reviewVotes}`) 454 .margin(10) 455 .onClick(() => { 456 this.reviewVotes += 1 457 }) 458 }.width('100%') 459 } 460} 461``` 462 463## @Watch 464 465**@Watch** is used to listen for changes of state variables. The syntax structure is as follows: 466 467```ts 468@State @Watch("onChanged") count : number = 0 469``` 470 471As shown above, add an **@Watch** decorator to the target state variable to register an **onChanged** callback. When the state variable **count** is changed, the **onChanged** callback will be triggered. 472 473**@Watch** can be used to listen for value changes of variables decorated by **@State**, **@Prop**, **@Link**, **@ObjectLink**, **@Provide**, **@Consume**, **@StorageProp**, and **@StorageLink**. 474 475 476> **NOTE** 477> 478> **@Watch** cannot be used to listen for in-depth data modification, such as changes of object values in an array. 479 480```ts 481// xxx.ets 482@Entry 483@Component 484struct CompA { 485 @State @Watch('onBasketUpdated') shopBasket: Array<number> = [7, 12, 47, 3] 486 @State totalPurchase: number = 0 487 @State addPurchase: number = 0 488 489 aboutToAppear() { 490 this.updateTotal() 491 } 492 493 updateTotal(): void { 494 let sum = 0; 495 this.shopBasket.forEach((i) => { 496 sum += i 497 }) 498 // Calculate the total amount of items in the shopping basket. If the amount exceeds 100, the specified discount will be applied. 499 this.totalPurchase = (sum < 100) ? sum : 0.9 * sum 500 return this.totalPurchase 501 } 502 503 // This method is triggered when the value of shopBasket is changed. 504 onBasketUpdated(propName: string): void { 505 this.updateTotal() 506 } 507 508 build() { 509 Column() { 510 Button('add to basket ' + this.addPurchase) 511 .margin(15) 512 .onClick(() => { 513 this.addPurchase = Math.round(100 * Math.random()) 514 this.shopBasket.push(this.addPurchase) 515 }) 516 Text(`${this.totalPurchase}`) 517 .fontSize(30) 518 } 519 } 520} 521``` 522