• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![en-us_image_0000001251090821](figures/en-us_image_0000001251090821.png)
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![en-us_image_0000001206450834](figures/en-us_image_0000001206450834.png)
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