1# Migrating Applications from V1 to V2
2
3## Overview
4ArkUI state management automatically synchronizes observable data changes to the UI to implement data-driven UI re-render, enabling you to focus on UI implementation and design.
5
6During the evolution of the state management framework, state management V1 and V2 are released. V1 emphasizes state management of the components, while V2 enhances the in-depth observation and management of data objects. With V2, you can control data and state more flexibly, facilitating a more efficient UI re-render. For details about the differences between V1 and V2, see [State Management Overview](./arkts-state-management-overview.md).
7
8## How to Use
91. V2 is an enhanced version of V1 and provides more functions and flexibility.
102. For new applications, you are advised to use V2 for development.
113. If the functions and performance of the application can meet the requirements in V1, you do not need to migrate the application to V2 immediately. However, if you cannot observe data changes in a lower level during development, it is recommended that you migrate the application to V2 as soon as possible to achieve smooth application transition and improvement in the future.
124. For details about the mixed use of V1 and V2, see [Mixing Use of Custom Components](./arkts-custom-component-mixed-scenarios.md). The compiler, toolchain, and DevEco Studio can verify some misuse and mixed use scenarios that are not recommended. Although you may bypass these verifications using special methods, you are still advised to follow the guidelines in [Mixing Use of Custom Components](./arkts-custom-component-mixed-scenarios.md) to avoid uncertainty caused by dual proxies.
13
14## Purpose
151. For developers who want to migrate applications from V1 to V2, this document provides systematic templates and guidance.
162. For developers who want to gradually transition applications from V1 to V2, this document together with [Mixing Use of Custom Components](./arkts-custom-component-mixed-scenarios.md) provide guidelines and reference.
173. For developers who have not started to develop applications but are familiar with the state management of V1, this document and documents of the decorators and APIs of V2 provide reference for application development in V2.
18
19## Capability Comparison and Migration Between V1 and V2
20| V1 Decorator               | V2 Decorator                 | Description|
21|------------------------|--------------------------|--------------------------|
22| \@Observed              | \@ObservedV2              | Indicates that this object is an observable object. However, they have different capabilities.<br>\@Observed is used to observe the top-level properties and it takes effect only when it is used together with \@ObjectLink.<br>\@ObservedV2 does not have the observation capability. It only indicates that this class is observable. To observe the class properties, use together with \@Trace. |
23| \@Track                 | \@Trace                   | \@Track is used for accurate observation. If it is not used, class properties cannot be accurately observed.<br>\@Trace decorated properties can be accurately traced and observed.|
24| \@Component             | \@ComponentV2             | \@Component is the custom component decorator used with the state variables of V1.<br>@ComponentV2 is the custom component decorator used with the state variables of V2.|
25|\@State                 | No external initialization: @Local<br>External initialization once: \@Param and \@Once| Similar to \@Local, \@State decorated variables can work as the data source which can be directly migrated without external initialization. If the external initialization is required, use \@Param and \@Once. For details, see [@State -> @Local](#state---local). |
26| \@Prop                  | \@Param                   | Similar to \@Param, \@Prop is used to decorate custom component variables. When the input parameter is of the complex type, \@Prop is used to deep copy and \@Param is used to import the parameter.|
27| \@Link                  | \@Param\@Event    | \@Link implements a two-way synchronization encapsulated by the framework of V1. Developers using V2 can implement the two-way synchronization through @Param and @Event.|
28| \@ObjectLink            | \@Param                   | Compatible. \@ObjectLink needs to be initialized by the instance of the @Observed decorated class, but \@Param does not have this constraint.|
29| \@Provide               | \@Provider                | Compatible.|
30| \@Consume               | \@Consumer                | Compatible.|
31| \@Watch               | \@Monitor                | \@Watch is used to listen for the changes of state variables and their top-level properties in V1. Observable changes of state variables can trigger the \@Watch listening event.<br>\@Monitor is used to listen for the changes of state variables in V2. Used together with \@Trace, in-depth changes can be listened. When a state variable changes frequently in an event, only the final result is used to determine whether to trigger the \@Monitor listening event.|
32| LocalStorage               | Global \@ObservedV2 and \@Trace  | Compatible.|
33| AppStorage               | AppStorageV2   | Compatible.|
34| Environment       | Calls the ability APIs to obtain system environment variables.  | This capability is coupled with the AppStorage. In V2, you can directly call the ability APIs to obtain system environment variables.|
35| PersistentStorage     | PersistenceV2   | The persistence capability of PersistentStorage is coupled with the AppStorage, while that of PersistenceV2 can be used independently.|
36
37## Decorator Migration Examples
38
39### @State -> @Local
40
41#### Migration Rules
42In V1, the \@State decorator is used to decorate state variables inside a component. In V2, the \@Local decorator is provided as a substitute. However, the observation capability and initialization rules of the two decorators are obviously different. The migration policies for different use scenarios are as follows:
43
44- For simple type: Directly replace \@State with \@Local.
45- For complex type: In V1, @State can be used to observe the top-level property changes of a complex object. In V2, \@Local can be used to observe only the changes of the object itself. To listen for the internal property changes of an object, you can use \@ObservedV2 and \@Trace together.
46- For state variable of external initialization: In V1, \@State supports external initialization, while \@Local in V2 does not support. If the initial value needs to be passed in externally, you can use the \@Param and \@Once decorators.
47
48#### Example
49
50**Simple type**
51
52For simple variables, @State of V1 can be replaced with @Local of V2.
53
54V1:
55
56```ts
57@Entry
58@Component
59struct Child {
60  @State val: number = 10;
61  build(){
62    Text(this.val.toString())
63  }
64}
65```
66
67V2:
68
69```ts
70@Entry
71@ComponentV2
72struct Child {
73  @Local val: number = 10;
74  build(){
75    Text(this.val.toString())
76  }
77}
78```
79
80**Complex type**
81
82@State of V1 can observe the changes of the top-level properties of complex objects, but @Local of V2 cannot observe the internal changes of objects. To solve this problem, you need to add @ObservedV2 to the class and add @Trace to the properties to observe in V2. In this way, V2 can listen for property changes inside the object.
83
84V1:
85
86```ts
87class Child {
88  value: number = 10;
89}
90
91@Component
92@Entry
93struct example {
94  @State child: Child = new Child();
95  build(){
96    Column() {
97      Text(this.child.value.toString())
98      // @State can be used to observe the top-level changes.
99      Button('value+1')
100        .onClick(() => {
101          this.child.value++;
102        })
103    }
104  }
105}
106```
107
108V2:
109
110```ts
111@ObservedV2
112class Child {
113  @Trace public value: number = 10;
114}
115
116@ComponentV2
117@Entry
118struct example {
119  @Local child: Child = new Child();
120  build(){
121    Column() {
122      Text(this.child.value.toString())
123      // @Local can only observe itself. Add @ObservedV2 and @Trace to Child.
124      Button('value+1')
125        .onClick(() => {
126          this.child.value++;
127        })
128    }
129  }
130}
131```
132
133**State variable of external initialization**
134
135The @State decorated state variable of V1 can be initialized externally, but the @Local decorated state variable of V2 cannot. To implement similar functions, replace @State with @Param and @Once in V2 to allow passing in initial value externally and ensure that the value is synchronized only once during initialization.
136
137V1:
138
139```ts
140@Component
141struct Child {
142  @State value: number = 0;
143  build() {
144    Text(this.value.toString())
145  }
146}
147
148@Entry
149@Component
150struct Parent {
151  build() {
152    Column(){
153      // @State supports external initialization.
154      Child({ value: 30 })
155    }
156  }
157}
158```
159
160V2:
161
162```ts
163@ComponentV2
164struct Child {
165  @Param @Once value: number = 0;
166  build() {
167    Text(this.value.toString())
168  }
169}
170
171@Entry
172@ComponentV2
173struct Parent {
174  build() {
175    Column(){
176      // @Local does not support external initialization. Use @Param and @Once instead.
177      Child({ value: 30 })
178    }
179  }
180}
181```
182
183### @Link -> @Param/@Event
184
185#### Migration Rules
186In V1, @Link allows two-way binding between parent and child components. When migrating to V2, you can use @Param and @Event to simulate two-way synchronization. In this way, @Param implements one-way passing from the parent to the child component, and then the child component triggers the state update of the parent component through the @Event callback.
187
188#### Example
189
190V1:
191
192```ts
193@Component
194struct Child {
195  // @Link can synchronize data in a two-way manner.
196  @Link val: number;
197  build() {
198    Column(){
199      Text("child: " + this.val.toString())
200      Button("+1")
201        .onClick(() => {
202          this.val++;
203        })
204    }
205  }
206}
207
208@Entry
209@Component
210struct Parent {
211  @State myVal: number = 10;
212  build() {
213    Column(){
214      Text("parent: " + this.myVal.toString())
215      Child({val: this.myVal})
216    }
217  }
218}
219```
220
221V2:
222
223```ts
224@ComponentV2
225struct Child {
226  // @Param works with @Event to synchronize data in a two-way manner.
227  @Param val: number  = 0;
228  @Event addOne: () => void;
229  build() {
230    Column(){
231      Text("child: " + this.val.toString())
232      Button("+1")
233        .onClick(()=> {
234          this.addOne();
235        })
236    }
237  }
238}
239
240@Entry
241@ComponentV2
242struct Parent {
243  @Local myVal: number = 10
244  build() {
245    Column() {
246      Text("parent: " + this.myVal.toString())
247      Child({ val: this.myVal, addOne: () => this.myVal++})
248    }
249  }
250}
251```
252
253### @Prop -> @Param
254
255#### Migration Rules
256In V1, the @Prop decorator is used to pass in parameters from the parent component to the child component. These parameters can be directly changed in the child component. In V2, @Param replaces @Prop. However, @Param decorated parameter is read only and cannot be changed in the child component. Therefore, the migration policies for different use scenarios are as follows:
257
258- For simple type: Directly replace@Prop with @Param.
259- For complex type: If a complex object is passed and a strict one-way data binding is required, deep copy can be performed on the object to prevent the child component from changing the parent component data.
260- For variable to change: If a child component needs to change an input parameter, use @Once to allow the child component to change the variable locally. Note that if \@Once is used, the current child component is initialized only once, and the parent component cannot be synchronized to the child component.
261
262#### Example
263
264**Simple type**
265
266For variables of simple type, directly replace @Prop of V1 with @Param of V2.
267
268V1:
269
270```ts
271@Component
272struct Child {
273  @Prop value: number;
274  build() {
275    Text(this.value.toString())
276  }
277}
278
279@Entry
280@Component
281struct Parent {
282  build() {
283    Column(){
284      Child({ value: 30 })
285    }
286  }
287}
288```
289
290V2:
291
292```ts
293@ComponentV2
294struct Child {
295  @Param value: number = 0;
296  build() {
297    Text(this.value.toString())
298  }
299}
300
301@Entry
302@ComponentV2
303struct Parent {
304  build() {
305    Column(){
306      Child({ value: 30 })
307    }
308  }
309}
310```
311**Complex type**
312
313In V2, if you want to implement a strict one-way data binding when passing complex types to prevent child components from changing the parent component data, you need to perform deep copy when using @Param to pass complex objects to avoid object reference.
314
315V1:
316
317```ts
318class Fruit {
319  apple: number = 5;
320  orange: number = 10;
321}
322
323@Component
324struct Child {
325  // @Prop passes the Fruit class. When the properties of the child class are changed, the parent class is not affected.
326  @Prop fruit: Fruit;
327  build() {
328    Column() {
329      Text("child apple: "+ this.fruit.apple.toString())
330      Text("child orange: "+ this.fruit.orange.toString())
331      Button("apple+1")
332        .onClick(() => {
333          this.fruit.apple++;
334        })
335      Button("orange+1")
336        .onClick(() => {
337          this.fruit.orange++;
338        })
339    }
340  }
341}
342
343@Entry
344@Component
345struct Parent {
346  @State parentFruit: Fruit = new Fruit();
347  build() {
348    Column(){
349      Text("parent apple: "+this.parentFruit.apple.toString())
350      Text("parent orange: "+this.parentFruit.orange.toString())
351      Child({ fruit: this.parentFruit })
352    }
353  }
354}
355```
356
357V2:
358
359```ts
360@ObservedV2
361class Fruit{
362  @Trace apple: number = 5;
363  @Trace orange: number = 10;
364  // Implement the deep copy to prevent the child component from changing the parent component data.
365  clone(): Fruit {
366    let newFruit: Fruit = new Fruit();
367    newFruit.apple = this.apple;
368    newFruit.orange = this.orange;
369    return newFruit;
370  }
371}
372
373@ComponentV2
374struct Child {
375  @Param fruit: Fruit = new Fruit();
376  build() {
377    Column() {
378      Text("child")
379      Text(this.fruit.apple.toString())
380      Text(this.fruit.orange.toString())
381      Button("apple+1")
382        .onClick( ()=> {
383          this.fruit.apple++;
384        })
385      Button("orange+1")
386        .onClick(() => {
387          this.fruit.orange++;
388        })
389    }
390  }
391}
392
393@Entry
394@ComponentV2
395struct Parent {
396  @Local parentFruit: Fruit = new Fruit();
397  build() {
398    Column(){
399      Text("parent")
400      Text(this.parentFruit.apple.toString())
401      Text(this.parentFruit.orange.toString())
402      Child({ fruit: this.parentFruit.clone()})
403    }
404  }
405}
406```
407
408**Variable to change**
409
410In V1, the child component can change the @Prop decorated variable. In V2, however, @Param decorated variable is read only. If the child component needs to change an input value, you can use @Param and @Once to allow changing the value locally.
411
412V1:
413
414```ts
415@Component
416struct Child {
417  // @Prop can be used to directly change the variable.
418  @Prop value: number;
419  build() {
420    Column(){
421      Text(this.value.toString())
422      Button("+1")
423        .onClick(()=> {
424          this.value++;
425        })
426    }
427  }
428}
429
430@Entry
431@Component
432struct Parent {
433  build() {
434    Column(){
435      Child({ value: 30 })
436    }
437  }
438}
439```
440
441V2:
442
443```ts
444@ComponentV2
445struct Child {
446  // @Param used together with @Once can change the variable locally.
447  @Param @Once value: number = 0;
448  build() {
449    Column(){
450      Text(this.value.toString())
451      Button("+1")
452        .onClick(() => {
453          this.value++;
454        })
455    }
456  }
457}
458
459@Entry
460@ComponentV2
461struct Parent {
462  build() {
463    Column(){
464      Child({ value: 30 })
465    }
466  }
467}
468```
469
470In V1, the child component can modify the variables of \@Prop. These variables are updated only locally and are not synchronized to the parent component. When the data source of the parent component is updated, the child component is notified of the update and its local values of \@Prop are overwritten.
471
472V1:
473- If **localValue** of the child component **Child** is changed, the change is not synchronized to the parent component **Parent**.
474- When the parent component updates the value, **Child** is notified of the update and its local values of **localValue** are overwritten.
475
476```ts
477@Component
478struct Child {
479  @Prop localValue: number = 0;
480
481  build() {
482    Column() {
483      Text(`${this.localValue}`).fontSize(25)
484      Button('Child +100')
485        .onClick(() => {
486          // The change of localValue is not synchronized to Parent.
487          this.localValue += 100;
488        })
489    }
490  }
491}
492
493@Entry
494@Component
495struct Parent {
496  @State value: number = 10;
497  build() {
498    Column() {
499      Button('Parent +1')
500        .onClick(() => {
501          // Change the value and notify Child of the update.
502          this.value += 1;
503        })
504      Child({ localValue: this.value })
505    }
506  }
507}
508```
509In V2, \@Param cannot be written locally. When used together with \@Once, it is synchronized only once. To make the child component writable locally and ensure that the parent component can notify the child component of the update, you can use \@Monitor.
510
511V2:
512- When **Parent** is updated, it notifies the child component of the value update and calls back the **onValueChange** callback decorated by \@Monitor. This callback assigns the updated value to **localValue**.
513- If the value of **localValue** is changed, the change is not synchronized to **Parent**.
514- If value is changed again in **Parent**, the child component is notified of the change and its **localValue** is overwritten.
515
516```ts
517@ComponentV2
518struct Child {
519  @Local localValue: number = 0;
520  @Param value: number = 0;
521  @Monitor('value')
522  onValueChange(mon: IMonitor) {
523    console.info(`value has been changed from ${mon.value()?.before} to ${mon.value()?.now}`);
524    // When the value of the Parent changes, Child is notified of the value update and the Monitor function is called back to overwrite the updated value to the local value.
525    this.localValue = this.value;
526  }
527
528  build() {
529    Column() {
530      Text(`${this.localValue}`).fontSize(25)
531      Button('Child +100')
532        .onClick(() => {
533          // The change of localValue is not synchronized to Parent.
534          this.localValue += 100;
535        })
536    }
537  }
538}
539
540@Entry
541@ComponentV2
542struct Parent {
543  @Local value: number = 10;
544  build() {
545    Column() {
546      Button('Parent +1')
547        .onClick(() => {
548          // Change the value and notify Child of the update.
549          this.value += 1;
550        })
551      Child({ value: this.value })
552    }
553  }
554}
555```
556
557### @ObjectLink/@Observed/@Track -> @ObservedV2/@Trace
558#### Migration Rules
559In V1, the @Observed and @ObjectLink decorators are used to observe the changes of class objects and their nested properties. However, V1 can only directly observe the top-level object properties. The properties of nested objects must be observed through custom components and @ObjectLink. In addition, V1 provides the @Track decorator to implement precise control over property level changes.
560
561In V2, @ObservedV2 and @Trace are used together to efficiently observe in-depth changes of class objects and their nested properties, eliminating the dependency on custom components and simplifying the development process. In addition, the @Trace decorator, which replaces the @Track of V1, can update class properties precisely, achieving more efficient UI re-render control. The migration policies for different use scenarios are as follows:
562
563- Observing properties of nested objects: In V1, you need to observe nested properties through custom components and @ObjectLink. In V2, you can use @ObservedV2 and @Trace to directly observe nested objects, simplifying the code structure.
564- Updating class properties precisely: @Track of V1 can be replaced with @Trace of V2. @Trace can be used to observe and precisely update property changes at the same time, making the code simpler and more efficient.
565
566#### Example
567**Observing properties of nested objects**
568
569In V1, the property changes of nested objects cannot be directly observed. Only the top-level property changes can be observed. You must create a custom component and use @ObjectLink to observe the properties of nested objects. In V2, @ObservedV2 and @Trace are used to directly observe the properties of nested objects, reducing complexity.
570
571V1:
572
573```ts
574@Observed
575class Address {
576  city: string;
577
578  constructor(city: string) {
579    this.city = city;
580  }
581}
582
583@Observed
584class User {
585  name: string;
586  address: Address;
587
588  constructor(name: string, address: Address) {
589    this.name = name;
590    this.address = address;
591  }
592}
593
594@Component
595struct AddressView {
596  // The address decorated by @ObjectLink in the child component is initialized from the parent component and receives the address instance decorated by @Observed.
597  @ObjectLink address: Address;
598
599  build() {
600    Column() {
601      Text(`City: ${this.address.city}`)
602      Button("city +a")
603        .onClick(() => {
604          this.address.city += "a";
605        })
606    }
607  }
608}
609
610@Entry
611@Component
612struct UserProfile {
613  @State user: User = new User("Alice", new Address("New York"));
614
615  build() {
616    Column() {
617      Text(`Name: ${this.user.name}`)
618      // The property changes of nested objects cannot be directly observed, for example, this.user.address.city.
619      // Only the top-level property changes of the object can be observed. Therefore, the nested object Address needs to be extracted to the custom component AddressView.
620      AddressView({ address: this.user.address })
621    }
622  }
623}
624```
625
626V2:
627
628```ts
629@ObservedV2
630class Address {
631  @Trace city: string;
632
633  constructor(city: string) {
634    this.city = city;
635  }
636}
637
638@ObservedV2
639class User {
640  @Trace name: string;
641  @Trace address: Address;
642
643  constructor(name: string, address: Address) {
644    this.name = name;
645    this.address = address;
646  }
647}
648
649@Entry
650@ComponentV2
651struct UserProfile {
652  @Local user: User = new User("Alice", new Address("New York"));
653
654  build() {
655    Column() {
656      Text(`Name: ${this.user.name}`)
657      // Use @ObservedV2 and @Trace to directly observe the properties of nested objects.
658      Text(`City: ${this.user.address.city}`)
659      Button("city +a")
660        .onClick(() => {
661          this.user.address.city += "a";
662        })
663    }
664  }
665}
666```
667**Observing class properties**
668
669In V1, @Observed is used to observe the changes of class instances and their properties, and @Track is used to optimize property-level changes so that only the @Track decorated properties can trigger UI re-renders. In V2, @Trace combines the capability of observing and updating property level changes and works with @ObservedV2 to implement efficient UI re-renders.
670
671V1:
672
673```ts
674@Observed
675class User {
676  @Track name: string;
677  @Track age: number;
678
679  constructor(name: string, age: number) {
680    this.name = name;
681    this.age = age;
682  }
683}
684
685@Entry
686@Component
687struct UserProfile {
688  @State user: User = new User('Alice', 30);
689
690  build() {
691    Column() {
692      Text(`Name: ${this.user.name}`)
693      Text(`Age: ${this.user.age}`)
694      Button("increase age")
695        .onClick(() => {
696          this.user.age++;
697        })
698    }
699  }
700}
701```
702
703V2:
704
705```ts
706@ObservedV2
707class User {
708  @Trace name: string;
709  @Trace age: number;
710
711  constructor(name: string, age: number) {
712    this.name = name;
713    this.age = age;
714  }
715}
716
717@Entry
718@ComponentV2
719struct UserProfile {
720  @Local user: User = new User('Alice', 30);
721
722  build() {
723    Column() {
724      Text(`Name: ${this.user.name}`)
725      Text(`Age: ${this.user.age}`)
726      Button("Increase age")
727        .onClick(() => {
728          this.user.age++;
729        })
730    }
731  }
732}
733```
734
735### @Provide/@Consume -> @Provider/@Consumer
736#### Migration Rules
737The positioning capability and functions of @Provide and @Consume in V1 are similar to those of @Provider and @Consumer in V2. The former two decorators can be smoothly replaced with the later two. However, there are still some differences that allow you to determine whether to adjust them based on your code implementation.
738In V1, @Provide and @Consume are used for data sharing between parent and child components. They can be matched by alias or attribute name. In addition, @Consume must depend on @Provide of the parent component and cannot be initialized locally. In V2, @Provider and @Consumer enhance these features to make data sharing more flexible. The migration policies for different use scenarios are as follows:
739
740- In V1, \@Provide or \@Consume can be directly used if no alias is specified. In V2, \@Provider or \@Consumer is a standard decorator and the parameters are optional. Therefore, the alias must be followed by parentheses regardless of whether it is specified.
741- Rules for matching aliases and attribute names: In V1, @Provide and @Consume can be matched by aliases or attribute names. In V2, alias is the unique matching key. Only alias can be used for matching when it is specified.
742- Local initialization: In V1, @Consume does not support local initialization and must depend on the parent component. In V2, @Consumer supports local initialization. If the corresponding @Provider cannot be found, the local default value is used.
743- Initialization from the parent component: In V1, @Provide can be directly initialized from the parent component. In V2, @Provider does not support external initialization. You need to use @Param and @Once to receive the initial value and assign it to @Provider.
744- Overloading support: In V1, @Provide does not support overloading by default. You need to set **allowOverride**. In V2, @Provider supports overloading and @Consumer can search for the nearest @Provider upwards.
745#### Example
746**Rules for matching aliases and attribute names**
747
748In V1, @Provide and @Consume can be matched by alias or attribute name. In V2, an alias is a unique key. If an alias is specified in @Consumer, the alias instead of the attribute name can be used for matching.
749
750V1:
751
752```ts
753@Component
754struct Child {
755  // Both the alias and attribute name are keys and can be used to match.
756  @Consume('text') childMessage: string;
757  @Consume message: string;
758  build(){
759    Column(){
760      Text(this.childMessage)
761      Text(this.message) // The value of Text is "Hello World".
762    }
763  }
764}
765
766@Entry
767@Component
768struct Parent {
769  @Provide('text') message: string = "Hello World";
770  build(){
771    Column(){
772      Child()
773    }
774  }
775}
776```
777
778V2:
779
780```ts
781@ComponentV2
782struct Child {
783  // The alias is the unique matching key. If the alias exists, the attribute name cannot be used for matching.
784  @Consumer('text') childMessage: string = "default";
785  @Consumer() message: string = "default";
786  build(){
787    Column(){
788      Text(this.childMessage)
789      Text(this.message) // The value of Text is "default".
790    }
791  }
792}
793
794@Entry
795@ComponentV2
796struct Parent {
797  @Provider('text') message: string = "Hello World";
798  build(){
799    Column(){
800      Child()
801    }
802  }
803}
804```
805
806**Local initialization support**
807
808In V1, @Consume does not allow variables to initialize locally and must depend on @Provide of the parent component. Otherwise, an exception is thrown. After migration to V2, @Consumer allows local initialization. If the corresponding @Provider cannot be found, the local default value is used.
809
810V1:
811
812```ts
813@Component
814struct Child {
815  // @Consume prohibits local initialization. If the corresponding @Provide cannot be found, an exception is thrown.
816  @Consume message: string;
817  build(){
818    Text(this.message)
819  }
820}
821
822@Entry
823@Component
824struct Parent {
825  @Provide message: string = "Hello World";
826  build(){
827    Column(){
828      Child()
829    }
830  }
831}
832```
833
834V2:
835
836```ts
837@ComponentV2
838struct Child {
839  // @Consumer allows local initialization. Local default value will be used when \@Provider is not found.
840  @Consumer() message: string = "Hello World";
841  build(){
842    Text(this.message)
843  }
844}
845
846@Entry
847@ComponentV2
848struct Parent {
849  build(){
850    Column(){
851      Child()
852    }
853  }
854}
855```
856
857**Initialization from the parent component**
858
859In V1, @Provide allows initialization from the parent component, and initial values can be passed directly through component parameters. In V2, @Provider prohibits external initialization. To implement the same function, you can use @Param and @Once in the child component to receive the initial value and assign the value to the @Provider variable.
860
861V1:
862
863```ts
864@Entry
865@Component
866struct Parent {
867  @State parentValue: number = 42;
868  build() {
869    Column() {
870      // @Provide supports initialization from the parent component.
871      Child({ childValue: this.parentValue })
872    }
873  }
874}
875
876@Component
877struct Child {
878  @Provide childValue: number = 0;
879  build(){
880    Column(){
881      Text(this.childValue.toString())
882    }
883  }
884}
885```
886
887V2:
888
889```ts
890@Entry
891@ComponentV2
892struct Parent {
893  @Local parentValue: number = 42;
894  build() {
895    Column() {
896      // @Provider prohibits localization from the parent component. Alternatively, you can use @Param to receive the value and then assign it to @Provider.
897      Child({ initialValue: this.parentValue })
898    }
899  }
900}
901
902@ComponentV2
903struct Child {
904  @Param @Once initialValue: number = 0;
905  @Provider() childValue: number = this.initialValue;
906  build() {
907    Column(){
908      Text(this.childValue.toString())
909    }
910  }
911}
912```
913
914**Overloading support**
915
916In V1, @Provide does not support overloading by default and cannot override the @Provide with the same name in the upper-level component. To support overloading, **allowOverride** must be set. In V2, @Provider supports overloading by default. @Consumer searches for the nearest @Provider upwards. No additional configuration is required.
917
918V1:
919
920```ts
921@Entry
922@Component
923struct GrandParent {
924  @Provide("reviewVotes") reviewVotes: number = 40;
925  build() {
926    Column(){
927      Parent()
928    }
929  }
930}
931
932@Component
933struct Parent {
934  // @Provide does not support overloading by default. Set the **allowOverride** function to enable.
935  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20;
936  build() {
937    Child()
938  }
939}
940
941@Component
942struct Child {
943  @Consume("reviewVotes") reviewVotes: number;
944  build() {
945    Text(this.reviewVotes.toString ()) // The value of Text is 20.
946  }
947}
948```
949
950V2:
951
952```ts
953@Entry
954@ComponentV2
955struct GrandParent {
956  @Provider("reviewVotes") reviewVotes: number = 40;
957  build() {
958    Column(){
959      Parent()
960    }
961  }
962}
963
964@ComponentV2
965struct Parent {
966  // @Provider supports overloading by default. @Consumer searches for the nearest @Provider upwards.
967  @Provider() reviewVotes: number = 20;
968  build() {
969    Child()
970  }
971}
972
973@ComponentV2
974struct Child {
975  @Consumer() reviewVotes: number = 0;
976  build() {
977    Text(this.reviewVotes.toString ()) // The value of Text is 20.
978  }
979}
980```
981
982### @Watch -> @Monitor
983#### Migration Rules
984In V1, \@Watch is used to listen for the changes of state variables and the specified callback is invoked when the variables change. In V2, \@Monitor replaces \@Watch to listen for variable changes more flexibly and obtain variable values before and after the changes. The migration policies for different use scenarios are as follows:
985
986- Single-variable listening: In simple scenarios, use @Monitor instead of @Watch.
987- Multi-variable listening: @Watch of V1 cannot obtain the value before the change. In V2, \@Monitor can listen for multiple variables at the same time and can access the states of the variables before and after the change.
988#### Example
989**Single-variable listening**
990
991Use @Monitor of V2 instead of @Watch of V1.
992
993V1:
994
995```ts
996@Entry
997@Component
998struct watchExample {
999  @State @Watch('onAppleChange') apple: number = 0;
1000  onAppleChange(): void {
1001    console.log("apple count changed to "+this.apple);
1002  }
1003
1004  build() {
1005    Column(){
1006      Text(`apple count: ${this.apple}`)
1007      Button("add apple")
1008        .onClick(() => {
1009          this.apple++;
1010        })
1011    }
1012  }
1013}
1014```
1015
1016V2:
1017
1018```ts
1019@Entry
1020@ComponentV2
1021struct monitorExample {
1022  @Local apple: number = 0;
1023  @Monitor('apple')
1024  onFruitChange(monitor: IMonitor) {
1025    console.log(`apple changed from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1026  }
1027
1028  build() {
1029    Column(){
1030      Text(`apple count: ${this.apple}`)
1031      Button("add apple")
1032        .onClick(()=> {
1033          this.apple++;
1034        })
1035    }
1036  }
1037}
1038```
1039
1040**Multi-variable listening**
1041
1042In V1, each @Watch callback can listen for only one variable and cannot obtain the value before the change. After migration to V2, you can use one @Monitor to listen for multiple variables at the same time and obtain the values of the variables before and after the change.
1043
1044V1:
1045
1046```ts
1047@Entry
1048@Component
1049struct watchExample {
1050  @State @Watch('onAppleChange') apple: number = 0;
1051  @State @Watch('onOrangeChange') orange: number = 0;
1052  // @Watch callback, which is used to listen for only a single variable but cannot obtain the value before change.
1053  onAppleChange(): void {
1054    console.log("apple count changed to "+this.apple);
1055  }
1056  onOrangeChange(): void {
1057    console.log("orange count changed to "+this.orange);
1058  }
1059
1060  build() {
1061    Column(){
1062      Text(`apple count: ${this.apple}`)
1063      Text(`orange count: ${this.orange}`)
1064      Button("add apple")
1065        .onClick(() => {
1066          this.apple++;
1067        })
1068      Button("add orange")
1069        .onClick(() => {
1070          this.orange++;
1071        })
1072    }
1073  }
1074}
1075```
1076
1077V2:
1078
1079```ts
1080@Entry
1081@ComponentV2
1082struct monitorExample {
1083  @Local apple: number = 0;
1084  @Local orange: number = 0;
1085
1086  // @Monitor callback, which is used to listen for multiple variables and obtain the value before change.
1087  @Monitor('apple','orange')
1088  onFruitChange(monitor: IMonitor) {
1089    monitor.dirty.forEach((name: string) => {
1090      console.log(`${name} changed from ${monitor.value(name)?.before} to ${monitor.value(name)?.now}`);
1091    });
1092  }
1093
1094  build() {
1095    Column() {
1096      Text(`apple count: ${this.apple}`)
1097      Text(`orange count: ${this.orange}`)
1098      Button("add apple")
1099        .onClick(() => {
1100          this.apple++;
1101        })
1102      Button("add orange")
1103        .onClick(() => {
1104          this.orange++;
1105        })
1106    }
1107  }
1108}
1109```
1110### @Computed
1111#### Migration Rules
1112V1 does not have the concept of computed attribute. Therefore, there is no way to reduce repeated computation in the UI. V2 provides the @Computed decorator to reduce repeated computation.
1113
1114V1:
1115In the following example, each time the **lastName** is changed, the **Text** component is re-rendered and **this.lastName +' ' + this.firstName** needs to be computed repeatedly.
1116```
1117@Entry
1118@Component
1119struct Index {
1120  @State firstName: string = 'Li';
1121  @State lastName: string = 'Hua';
1122
1123  build() {
1124    Column() {
1125      Text(this.lastName + ' ' + this.firstName)
1126      Text(this.lastName + ' ' + this.firstName)
1127      Button('changed lastName').onClick(() => {
1128        this.lastName += 'a';
1129      })
1130
1131    }
1132  }
1133}
1134```
1135
1136V2:
1137If \@Computed of V2 is used, the computation is triggered only once each time **lastName** is changed.
1138
1139```
1140@Entry
1141@ComponentV2
1142struct Index {
1143  @Local firstName: string = 'Li';
1144  @Local lastName: string = 'Hua';
1145
1146  @Computed
1147  get fullName() {
1148    return this.firstName + ' ' + this.lastName;
1149  }
1150
1151  build() {
1152    Column() {
1153      Text(this.fullName)
1154      Text(this.fullName)
1155      Button('changed lastName').onClick(() => {
1156        this.lastName += 'a';
1157      })
1158    }
1159  }
1160}
1161```
1162### LocalStorage -> Global @ObservedV2 or @Trace
1163#### Migration Rules
1164LocalStorage is used to share state variables between pages. This capability is provided because state variables of V1 are coupled with the view level and cannot be shared between pages.
1165For V2, the observation capability of state variables is embedded in the data and is not coupled with the view level. Therefore, V2 does not require a capability similar to **LocalStorage**. You can use the global @ObservedV2 or @Trace to import and export data by yourself, sharing state variables between pages.
1166
1167#### Example
1168**Common scenarios**
1169
1170V1:
1171Use the windowStage.[loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9) and [getShared](../reference/apis-arkui/arkui-ts/ts-state-management.md#getshared10) APIs to share state variables between pages.
1172```
1173// EntryAbility.ets
1174import { UIAbility } from '@kit.AbilityKit';
1175import { window } from '@kit.ArkUI';
1176
1177export default class EntryAbility extends UIAbility {
1178  para:Record<string, number> = { 'count': 47 };
1179  storage: LocalStorage = new LocalStorage(this.para);
1180
1181  onWindowStageCreate(windowStage: window.WindowStage): void {
1182    windowStage.loadContent('pages/Page1', this.storage);
1183  }
1184}
1185```
1186The following examples show that \@LocalStorageLink is used to synchronize local changes to **LocalStorage**.
1187
1188```
1189// Page1.ets
1190// Use the getShared API to obtain the LocalStorage instance shared by stage.
1191@Entry(LocalStorage.getShared())
1192@Component
1193struct Page1 {
1194  @LocalStorageLink('count') count: number = 0;
1195  pageStack: NavPathStack = new NavPathStack();
1196  build() {
1197    Navigation(this.pageStack) {
1198      Column() {
1199        Text(`${this.count}`)
1200          .fontSize(50)
1201          .onClick(() => {
1202            this.count++;
1203          })
1204        Button('push to Page2')
1205          .onClick(() => {
1206            this.pageStack.pushPathByName('Page2', null);
1207          })
1208      }
1209    }
1210  }
1211}
1212```
1213
1214```
1215// Page2.ets
1216@Builder
1217export function Page2Builder() {
1218  Page2()
1219}
1220
1221// The Page2 component obtains the LocalStorage instance of the parent component Page1.
1222@Component
1223struct Page2 {
1224  @LocalStorageLink('count') count: number = 0;
1225  pathStack: NavPathStack = new NavPathStack();
1226  build() {
1227    NavDestination() {
1228      Column() {
1229        Text(`${this.count}`)
1230          .fontSize(50)
1231          .onClick(() => {
1232            this.count++;
1233          })
1234      }
1235    }
1236    .onReady((context: NavDestinationContext) => {
1237      this.pathStack = context.pathStack;
1238    })
1239  }
1240}
1241```
1242When using **Navigation**, you need to add the **route_map.json** file to the **src/main/resources/base/profile** directory, replace the value of **pageSourceFile** with the path of **Page2**, and add **"routerMap": "$profile: route_map"** to the **module.json5** file.
1243```json
1244{
1245  "routerMap": [
1246    {
1247      "name": "Page2",
1248      "pageSourceFile": "src/main/ets/pages/Page2.ets",
1249      "buildFunction": "Page2Builder",
1250      "data": {
1251        "description" : "LocalStorage example"
1252      }
1253    }
1254  ]
1255}
1256```
1257V2:
1258- Declare the \@ObservedV2 decorated **MyStorage** class and import it to the page to use.
1259- Declare the \@Trace decorated properties as observable data shared between pages.
1260
1261```
1262// storage.ets
1263@ObservedV2
1264export class MyStorage {
1265  static singleton_: MyStorage;
1266  static instance() {
1267    if(!MyStorage.singleton_) {
1268      MyStorage.singleton_ = new MyStorage();
1269    };
1270    return MyStorage.singleton_;
1271  }
1272  @Trace count: number = 47;
1273}
1274```
1275
1276```
1277// Page1.ets
1278import { MyStorage } from './storage';
1279
1280@Entry
1281@ComponentV2
1282struct Page1 {
1283  storage: MyStorage = MyStorage.instance();
1284  pageStack: NavPathStack = new NavPathStack();
1285  build() {
1286    Navigation(this.pageStack) {
1287      Column() {
1288        Text(`${this.storage.count}`)
1289          .fontSize(50)
1290          .onClick(() => {
1291            this.storage.count++;
1292          })
1293        Button('push to Page2')
1294          .onClick(() => {
1295            this.pageStack.pushPathByName('Page2', null);
1296          })
1297      }
1298    }
1299  }
1300}
1301```
1302
1303```
1304// Page2.ets
1305import { MyStorage } from './storage';
1306
1307@Builder
1308export function Page2Builder() {
1309  Page2()
1310}
1311
1312@ComponentV2
1313struct Page2 {
1314  storage: MyStorage = MyStorage.instance();
1315  pathStack: NavPathStack = new NavPathStack();
1316  build() {
1317    NavDestination() {
1318      Column() {
1319        Text(`${this.storage.count}`)
1320          .fontSize(50)
1321          .onClick(() => {
1322            this.storage.count++;
1323          })
1324      }
1325    }
1326    .onReady((context: NavDestinationContext) => {
1327      this.pathStack = context.pathStack;
1328    })
1329  }
1330}
1331```
1332When using **Navigation**, you need to add the **route_map.json** file to the **src/main/resources/base/profile** directory, replace the value of **pageSourceFile** with the path of **Page2**, and add **"routerMap": "$profile: route_map"** to the **module.json5** file.
1333```json
1334{
1335  "routerMap": [
1336    {
1337      "name": "Page2",
1338      "pageSourceFile": "src/main/ets/pages/Page2.ets",
1339      "buildFunction": "Page2Builder",
1340      "data": {
1341        "description" : "LocalStorage example"
1342      }
1343    }
1344  ]
1345}
1346```
1347
1348If you do not want to synchronize the local change back to **LocalStorage**, see the following example:
1349- Change the value of **count** in **Page1**. Because **count** is decorated by \@LocalStorageProp, the change takes effect only locally and is not synchronized to **LocalStorage**.
1350- Click **push to Page2** to redirect to **Page2**. Changing the value of **count** in **Page1** does not synchronize to **LocalStorage**. Therefore, the **Text** component still displays its original value **47** in **Page2**.
1351- Click **change Storage Count**, call **setOrCreate** of **LocalStorage**, change the value of **count**, and notify all variables bound to the **key**.
1352
1353```ts
1354// Page1.ets
1355export let storage: LocalStorage = new LocalStorage();
1356storage.setOrCreate('count', 47);
1357
1358@Entry(storage)
1359@Component
1360struct Page1 {
1361  @LocalStorageProp('count') count: number = 0;
1362  pageStack: NavPathStack = new NavPathStack();
1363  build() {
1364    Navigation(this.pageStack) {
1365      Column() {
1366        Text(`${this.count}`)
1367          .fontSize(50)
1368          .onClick(() => {
1369            this.count++;
1370          })
1371        Button('change Storage Count')
1372          .onClick(() => {
1373            storage.setOrCreate('count', storage.get<number>('count') as number + 100);
1374          })
1375        Button('push to Page2')
1376          .onClick(() => {
1377            this.pageStack.pushPathByName('Page2', null);
1378          })
1379      }
1380    }
1381  }
1382}
1383```
1384
1385```ts
1386// Page2.ets
1387import { storage } from './Page1'
1388@Builder
1389export function Page2Builder() {
1390  Page2()
1391}
1392
1393// The Page2 component obtains the LocalStorage instance of the parent component Page1.
1394@Component
1395struct Page2 {
1396  @LocalStorageProp('count') count: number = 0;
1397  pathStack: NavPathStack = new NavPathStack();
1398  build() {
1399    NavDestination() {
1400      Column() {
1401        Text(`${this.count}`)
1402          .fontSize(50)
1403          .onClick(() => {
1404            this.count++;
1405          })
1406        Button('change Storage Count')
1407          .onClick(() => {
1408            storage.setOrCreate('count', storage.get<number>('count') as number + 100);
1409          })
1410      }
1411    }
1412    .onReady((context: NavDestinationContext) => {
1413      this.pathStack = context.pathStack;
1414    })
1415  }
1416}
1417```
1418In V2, you can use \@Local and \@Monitor to achieve similar effects.
1419- The **count** variable decorated by \@Local is the local value of the component, whose change is not synchronized back to **storage**.
1420- \@Monitor listens for the change of **storage.count**. When **storage.count** changes, the value of \@Local is changed in the callback function of \@Monitor.
1421
1422```ts
1423// Page1.ets
1424import { MyStorage } from './storage';
1425
1426@Entry
1427@ComponentV2
1428struct Page1 {
1429  storage: MyStorage = MyStorage.instance();
1430  pageStack: NavPathStack = new NavPathStack();
1431  @Local count: number = this.storage.count;
1432
1433  @Monitor('storage.count')
1434  onCountChange(mon: IMonitor) {
1435    console.log(`Page1 ${mon.value()?.before} to ${mon.value()?.now}`);
1436    this.count = this.storage.count;
1437  }
1438  build() {
1439    Navigation(this.pageStack) {
1440      Column() {
1441        Text(`${this.count}`)
1442          .fontSize(50)
1443          .onClick(() => {
1444            this.count++;
1445          })
1446        Button('change Storage Count')
1447          .onClick(() => {
1448            this.storage.count += 100;
1449          })
1450        Button('push to Page2')
1451          .onClick(() => {
1452            this.pageStack.pushPathByName('Page2', null);
1453          })
1454      }
1455    }
1456  }
1457}
1458```
1459
1460```ts
1461// Page2.ets
1462import { MyStorage } from './storage';
1463
1464@Builder
1465export function Page2Builder() {
1466  Page2()
1467}
1468
1469@ComponentV2
1470struct Page2 {
1471  storage: MyStorage = MyStorage.instance();
1472  pathStack: NavPathStack = new NavPathStack();
1473  @Local count: number = this.storage.count;
1474
1475  @Monitor('storage.count')
1476  onCountChange(mon: IMonitor) {
1477    console.log(`Page2 ${mon.value()?.before} to ${mon.value()?.now}`);
1478    this.count = this.storage.count;
1479  }
1480  build() {
1481    NavDestination() {
1482      Column() {
1483        Text(`${this.count}`)
1484          .fontSize(50)
1485          .onClick(() => {
1486            this.count++;
1487          })
1488        Button('change Storage Count')
1489          .onClick(() => {
1490            this.storage.count += 100;
1491          })
1492      }
1493    }
1494    .onReady((context: NavDestinationContext) => {
1495      this.pathStack = context.pathStack;
1496    })
1497  }
1498}
1499```
1500
1501**Receiving LocalStorage instance by custom components**
1502
1503To adapt to the scenario where **Navigation** is used, **LocalStorage** supports the input parameters of a custom component and passed them to all child custom components that use the current custom component as the root node.
1504In this scenario, you can use multiple global \@ObservedV2 or \@Trace instances instead.
1505
1506V1:
1507```ts
1508let localStorageA: LocalStorage = new LocalStorage();
1509localStorageA.setOrCreate('PropA', 'PropA');
1510
1511let localStorageB: LocalStorage = new LocalStorage();
1512localStorageB.setOrCreate('PropB', 'PropB');
1513
1514let localStorageC: LocalStorage = new LocalStorage();
1515localStorageC.setOrCreate('PropC', 'PropC');
1516
1517@Entry
1518@Component
1519struct MyNavigationTestStack {
1520  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
1521
1522  @Builder
1523  PageMap(name: string) {
1524    if (name === 'pageOne') {
1525      // Pass multiple LocalStorage instances.
1526      pageOneStack({}, localStorageA)
1527    } else if (name === 'pageTwo') {
1528      pageTwoStack({}, localStorageB)
1529    } else if (name === 'pageThree') {
1530      pageThreeStack({}, localStorageC)
1531    }
1532  }
1533
1534  build() {
1535    Column({ space: 5 }) {
1536      Navigation(this.pageInfo) {
1537        Column() {
1538          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1539            .width('80%')
1540            .height(40)
1541            .margin(20)
1542            .onClick(() => {
1543              this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack.
1544            })
1545        }
1546      }.title('NavIndex')
1547      .navDestination(this.PageMap)
1548      .mode(NavigationMode.Stack)
1549      .borderWidth(1)
1550    }
1551  }
1552}
1553
1554@Component
1555struct pageOneStack {
1556  @Consume('pageInfo') pageInfo: NavPathStack;
1557  @LocalStorageLink('PropA') PropA: string = 'Hello World';
1558
1559  build() {
1560    NavDestination() {
1561      Column() {
1562        // Display "PropA".
1563        NavigationContentMsgStack()
1564        // Display "PropA".
1565        Text(`${this.PropA}`)
1566        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1567          .width('80%')
1568          .height(40)
1569          .margin(20)
1570          .onClick(() => {
1571            this.pageInfo.pushPathByName('pageTwo', null);
1572          })
1573      }.width('100%').height('100%')
1574    }.title('pageOne')
1575    .onBackPressed(() => {
1576      this.pageInfo.pop();
1577      return true;
1578    })
1579  }
1580}
1581
1582@Component
1583struct pageTwoStack {
1584  @Consume('pageInfo') pageInfo: NavPathStack;
1585  @LocalStorageLink('PropB') PropB: string = 'Hello World';
1586
1587  build() {
1588    NavDestination() {
1589      Column() {
1590        // Display "Hello". This localStorageB does not have the value corresponding to PropA, therefore, the local default value is used.
1591        NavigationContentMsgStack()
1592        // Display "PropB".
1593        Text(`${this.PropB}`)
1594        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1595          .width('80%')
1596          .height(40)
1597          .margin(20)
1598          .onClick(() => {
1599            this.pageInfo.pushPathByName('pageThree', null);
1600          })
1601
1602      }.width('100%').height('100%')
1603    }.title('pageTwo')
1604    .onBackPressed(() => {
1605      this.pageInfo.pop();
1606      return true;
1607    })
1608  }
1609}
1610
1611@Component
1612struct pageThreeStack {
1613  @Consume('pageInfo') pageInfo: NavPathStack;
1614  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';
1615
1616  build() {
1617    NavDestination() {
1618      Column() {
1619        // Display "Hello". This localStorageC does not have the value corresponding to PropA, therefore, the local default value is used.
1620        NavigationContentMsgStack()
1621        // Display "PropC".
1622        Text(`${this.PropC}`)
1623        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1624          .width('80%')
1625          .height(40)
1626          .margin(20)
1627          .onClick(() => {
1628            this.pageInfo.pushPathByName('pageOne', null);
1629          })
1630
1631      }.width('100%').height('100%')
1632    }.title('pageThree')
1633    .onBackPressed(() => {
1634      this.pageInfo.pop();
1635      return true;
1636    })
1637  }
1638}
1639
1640@Component
1641struct NavigationContentMsgStack {
1642  @LocalStorageLink('PropA') PropA: string = 'Hello';
1643
1644  build() {
1645    Column() {
1646      Text(`${this.PropA}`)
1647        .fontSize(30)
1648        .fontWeight(FontWeight.Bold)
1649    }
1650  }
1651}
1652```
1653V2:
1654
1655Declare the \@ObservedV2 decorated class to replace **LocalStorage**. The key of **LocalStorage** can be replaced with the \@Trace decorated attribute.
1656```ts
1657// storage.ets
1658@ObservedV2
1659export class MyStorageA {
1660  @Trace propA: string = 'Hello';
1661  constructor(propA?: string) {
1662      this.propA = propA? propA : this.propA;
1663  }
1664}
1665
1666@ObservedV2
1667export class MyStorageB extends MyStorageA {
1668  @Trace propB: string = 'Hello';
1669  constructor(propB: string) {
1670    super();
1671    this.propB = propB;
1672  }
1673}
1674
1675@ObservedV2
1676export class MyStorageC extends MyStorageA {
1677  @Trace propC: string = 'Hello';
1678  constructor(propC: string) {
1679    super();
1680    this.propC = propC;
1681  }
1682}
1683```
1684
1685Create the **MyStorageA**, **MyStorageB**, and **MyStorageC** instances in the **pageOneStack**, **pageTwoStack**, and **pageThreeStack** components, and pass the instances to the child component **NavigationContentMsgStack** through \@Param. In this way, the **LocalStorage** instance can be shared in the child component tree.
1686
1687```ts
1688// Index.ets
1689import { MyStorageA, MyStorageB, MyStorageC } from './storage';
1690
1691@Entry
1692@ComponentV2
1693struct MyNavigationTestStack {
1694   pageInfo: NavPathStack = new NavPathStack();
1695
1696  @Builder
1697  PageMap(name: string) {
1698    if (name === 'pageOne') {
1699      pageOneStack()
1700    } else if (name === 'pageTwo') {
1701      pageTwoStack()
1702    } else if (name === 'pageThree') {
1703      pageThreeStack()
1704    }
1705  }
1706
1707  build() {
1708    Column({ space: 5 }) {
1709      Navigation(this.pageInfo) {
1710        Column() {
1711          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1712            .width('80%')
1713            .height(40)
1714            .margin(20)
1715            .onClick(() => {
1716              this.pageInfo.pushPath({ name: 'pageOne' }); // Push the navigation destination page specified by name to the navigation stack.
1717            })
1718        }
1719      }.title('NavIndex')
1720      .navDestination(this.PageMap)
1721      .mode(NavigationMode.Stack)
1722      .borderWidth(1)
1723    }
1724  }
1725}
1726
1727@ComponentV2
1728struct pageOneStack {
1729  pageInfo: NavPathStack = new NavPathStack();
1730  @Local storageA: MyStorageA = new MyStorageA('PropA');
1731
1732  build() {
1733    NavDestination() {
1734      Column() {
1735        // Display "PropA".
1736        NavigationContentMsgStack({storage: this.storageA})
1737        // Display "PropA".
1738        Text(`${this.storageA.propA}`)
1739        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1740          .width('80%')
1741          .height(40)
1742          .margin(20)
1743          .onClick(() => {
1744            this.pageInfo.pushPathByName('pageTwo', null);
1745          })
1746      }.width('100%').height('100%')
1747    }.title('pageOne')
1748    .onBackPressed(() => {
1749      this.pageInfo.pop();
1750      return true;
1751    })
1752    .onReady((context: NavDestinationContext) => {
1753      this.pageInfo = context.pathStack;
1754    })
1755  }
1756}
1757
1758@ComponentV2
1759struct pageTwoStack {
1760  pageInfo: NavPathStack = new NavPathStack();
1761  @Local storageB: MyStorageB = new MyStorageB('PropB');
1762
1763  build() {
1764    NavDestination() {
1765      Column() {
1766        // Display "Hello".
1767        NavigationContentMsgStack({ storage: this.storageB })
1768        // Display "PropB".
1769        Text(`${this.storageB.propB}`)
1770        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1771          .width('80%')
1772          .height(40)
1773          .margin(20)
1774          .onClick(() => {
1775            this.pageInfo.pushPathByName('pageThree', null);
1776          })
1777
1778      }.width('100%').height('100%')
1779    }.title('pageTwo')
1780    .onBackPressed(() => {
1781      this.pageInfo.pop();
1782      return true;
1783    })
1784    .onReady((context: NavDestinationContext) => {
1785      this.pageInfo = context.pathStack;
1786    })
1787  }
1788}
1789
1790@ComponentV2
1791struct pageThreeStack {
1792  pageInfo: NavPathStack = new NavPathStack();
1793  @Local storageC: MyStorageC = new MyStorageC("PropC");
1794
1795  build() {
1796    NavDestination() {
1797      Column() {
1798        // Display "Hello".
1799        NavigationContentMsgStack({ storage: this.storageC })
1800        // Display "PropC".
1801        Text(`${this.storageC.propC}`)
1802        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1803          .width('80%')
1804          .height(40)
1805          .margin(20)
1806          .onClick(() => {
1807            this.pageInfo.pushPathByName('pageOne', null);
1808          })
1809
1810      }.width('100%').height('100%')
1811    }.title('pageThree')
1812    .onBackPressed(() => {
1813      this.pageInfo.pop();
1814      return true;
1815    })
1816    .onReady((context: NavDestinationContext) => {
1817      this.pageInfo = context.pathStack;
1818    })
1819  }
1820}
1821
1822@ComponentV2
1823struct NavigationContentMsgStack {
1824  @Require@Param storage: MyStorageA;
1825
1826  build() {
1827    Column() {
1828      Text(`${this.storage.propA}`)
1829        .fontSize(30)
1830        .fontWeight(FontWeight.Bold)
1831    }
1832  }
1833}
1834```
1835
1836### AppStorage -> AppStorageV2
1837In the previous section, the global @ObserveV2 or @Trace reconstruction is not suitable for cross-ability data sharing. In this case, **AppStorageV2** can be used.
1838
1839V1:
1840**AppStorage** is bound to an application process and can share data across abilities.
1841In the following example, \@StorageLink is used to synchronize local changes to **AppStorage**.
1842
1843```
1844// EntryAbility Index.ets
1845import { common, Want } from '@kit.AbilityKit';
1846@Entry
1847@Component
1848struct Index {
1849  @StorageLink('count') count: number = 0;
1850  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
1851  build() {
1852    Column() {
1853      Text(`EntryAbility count: ${this.count}`)
1854        .fontSize(50)
1855        .onClick(() => {
1856          this.count++;
1857        })
1858      Button('Jump to EntryAbility1').onClick(() => {
1859        let wantInfo: Want = {
1860          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
1861          abilityName: 'EntryAbility1'
1862        };
1863        this.context.startAbility(wantInfo);
1864      })
1865    }
1866  }
1867}
1868```
1869
1870```
1871// EntryAbility1 Index1.ets
1872import { common, Want } from '@kit.AbilityKit';
1873@Entry
1874@Component
1875struct Index1 {
1876  @StorageLink('count') count: number = 0;
1877  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
1878  build() {
1879    Column() {
1880      Text(`EntryAbility1 count: ${this.count}`)
1881        .fontSize(50)
1882        .onClick(() => {
1883          this.count++;
1884        })
1885      Button('Jump to EntryAbility').onClick(() => {
1886        let wantInfo: Want = {
1887          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
1888          abilityName: 'EntryAbility'
1889        };
1890        this.context.startAbility(wantInfo);
1891      })
1892    }
1893  }
1894}
1895```
1896V2:
1897**AppStorageV2** can be used to share data across abilities.
1898Example:
1899
1900```
1901import { common, Want } from '@kit.AbilityKit';
1902import { AppStorageV2 } from '@kit.ArkUI';
1903
1904@ObservedV2
1905export class MyStorage {
1906  @Trace count: number = 0
1907}
1908
1909@Entry
1910@ComponentV2
1911struct Index {
1912  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
1913  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
1914  build() {
1915    Column() {
1916      Text(`EntryAbility1 count: ${this.storage.count}`)
1917        .fontSize(50)
1918        .onClick(() => {
1919          this.storage.count++;
1920        })
1921      Button('Jump to EntryAbility1').onClick(() => {
1922        let wantInfo: Want = {
1923          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
1924          abilityName: 'EntryAbility1'
1925        };
1926        this.context.startAbility(wantInfo);
1927      })
1928    }
1929  }
1930}
1931
1932```
1933
1934```
1935import { common, Want } from '@kit.AbilityKit';
1936import { AppStorageV2 } from '@kit.ArkUI';
1937
1938@ObservedV2
1939export class MyStorage {
1940  @Trace count: number = 0
1941}
1942
1943@Entry
1944@ComponentV2
1945struct Index1 {
1946  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
1947  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
1948    build() {
1949      Column() {
1950        Text(`EntryAbility1 count: ${this.storage.count}`)
1951          .fontSize(50)
1952          .onClick(() => {
1953            this.storage.count++;
1954          })
1955        Button('Jump to EntryAbility').onClick(() => {
1956          let wantInfo: Want = {
1957            bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
1958            abilityName: 'EntryAbility'
1959          };
1960          this.context.startAbility(wantInfo);
1961        })
1962      }
1963    }
1964}
1965```
1966
1967If you do not want to synchronize local changes to **AppStorage** but the changes of **AppStorage** can be notified to components using \@StorageProp, you can refer to the following examples.
1968
1969V1:
1970
1971```ts
1972// EntryAbility Index.ets
1973import { common, Want } from '@kit.AbilityKit';
1974@Entry
1975@Component
1976struct Index {
1977  @StorageProp('count') count: number = 0;
1978  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
1979  build() {
1980    Column() {
1981      Text(`EntryAbility count: ${this.count}`)
1982        .fontSize(25)
1983        .onClick(() => {
1984          this.count++;
1985        })
1986      Button('change Storage Count')
1987        .onClick(() => {
1988          AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100);
1989        })
1990      Button('Jump to EntryAbility1').onClick(() => {
1991        let wantInfo: Want = {
1992          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
1993          abilityName: 'EntryAbility1'
1994        };
1995        this.context.startAbility(wantInfo);
1996      })
1997    }
1998  }
1999}
2000```
2001
2002```ts
2003// EntryAbility1 Index1.ets
2004import { common, Want } from '@kit.AbilityKit';
2005@Entry
2006@Component
2007struct Index1 {
2008  @StorageProp('count') count: number = 0;
2009  private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
2010  build() {
2011    Column() {
2012      Text(`EntryAbility1 count: ${this.count}`)
2013        .fontSize(50)
2014        .onClick(() => {
2015          this.count++;
2016        })
2017      Button('change Storage Count')
2018        .onClick(() => {
2019          AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100);
2020        })
2021      Button('Jump to EntryAbility').onClick(() => {
2022        let wantInfo: Want = {
2023          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
2024          abilityName: 'EntryAbility'
2025        };
2026        this.context.startAbility(wantInfo);
2027      })
2028    }
2029  }
2030}
2031```
2032
2033V2:
2034The following examples show that you can use \@Monitor and \@Local to achieve similar effects.
2035
2036```ts
2037import { common, Want } from '@kit.AbilityKit';
2038import { AppStorageV2 } from '@kit.ArkUI';
2039
2040@ObservedV2
2041export class MyStorage {
2042  @Trace count: number = 0;
2043}
2044
2045@Entry
2046@ComponentV2
2047struct Index {
2048  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
2049  @Local count: number = this.storage.count;
2050  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
2051
2052  @Monitor('storage.count')
2053  onCountChange(mon: IMonitor) {
2054    console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`);
2055    this.count = this.storage.count;
2056  }
2057  build() {
2058    Column() {
2059      Text(`EntryAbility1 count: ${this.count}`)
2060        .fontSize(25)
2061        .onClick(() => {
2062          this.count++;
2063        })
2064      Button('change Storage Count')
2065        .onClick(() => {
2066          this.storage.count += 100;
2067        })
2068      Button('Jump to EntryAbility1').onClick(() => {
2069        let wantInfo: Want = {
2070          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
2071          abilityName: 'EntryAbility1'
2072        };
2073        this.context.startAbility(wantInfo);
2074      })
2075    }
2076  }
2077}
2078```
2079
2080```ts
2081import { common, Want } from '@kit.AbilityKit';
2082import { AppStorageV2 } from '@kit.ArkUI';
2083
2084@ObservedV2
2085export class MyStorage {
2086  @Trace count: number = 0;
2087}
2088
2089@Entry
2090@ComponentV2
2091struct Index1 {
2092  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
2093  @Local count: number = this.storage.count;
2094  private context: common.UIAbilityContext= getContext(this) as common.UIAbilityContext;
2095
2096  @Monitor('storage.count')
2097  onCountChange(mon: IMonitor) {
2098    console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`);
2099    this.count = this.storage.count;
2100  }
2101
2102  build() {
2103    Column() {
2104      Text(`EntryAbility1 count: ${this.count}`)
2105        .fontSize(25)
2106        .onClick(() => {
2107          this.count++;
2108        })
2109      Button('change Storage Count')
2110        .onClick(() => {
2111          this.storage.count += 100;
2112        })
2113      Button('Jump to EntryAbility').onClick(() => {
2114        let wantInfo: Want = {
2115          bundleName: 'com.example.myapplication', // Replace it with the bundle name in AppScope/app.json5.
2116          abilityName: 'EntryAbility'
2117        };
2118        this.context.startAbility(wantInfo);
2119      })
2120    }
2121  }
2122}
2123```
2124
2125### Environment -> Ability APIs
2126In V1, you can obtain environment variables through **Environment**. However, the result obtained by **Environment** cannot be directly used. You need to use **Environment** together with **AppStorage** to obtain the value of the corresponding environment variable.
2127After migration to V2, you can directly obtain the system environment variables through the [config](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#properties) property of **UIAbilityContext** without using **Environment**.
2128V1:
2129The following uses **languageCode** as an example.
2130```ts
2131// Save the device language code to AppStorage.
2132Environment.envProp('languageCode', 'en');
2133
2134@Entry
2135@Component
2136struct Index {
2137  @StorageProp('languageCode') languageCode: string = 'en';
2138  build() {
2139    Row() {
2140      Column() {
2141        // Output the current device language code.
2142        Text(this.languageCode)
2143      }
2144    }
2145  }
2146}
2147```
2148
2149V2:
2150Encapsulates the **Env** type to pass multiple system environment variables.
2151
2152```
2153// Env.ts
2154import { ConfigurationConstant } from '@kit.AbilityKit';
2155
2156export class Env {
2157  language: string | undefined;
2158  colorMode: ConfigurationConstant.ColorMode | undefined;
2159  fontSizeScale: number | undefined;
2160  fontWeightScale: number | undefined;
2161}
2162
2163export let env: Env = new Env();
2164```
2165Obtain the required system environment variables from **onCreate**.
2166```
2167// EntryAbility.ets
2168import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
2169import { window } from '@kit.ArkUI';
2170import { env } from '../pages/Env';
2171
2172export default class EntryAbility extends UIAbility {
2173  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
2174    env.language = this.context.config.language;
2175    env.colorMode = this.context.config.colorMode;
2176    env.fontSizeScale = this.context.config.fontSizeScale;
2177    env.fontWeightScale = this.context.config.fontWeightScale;
2178  }
2179
2180  onWindowStageCreate(windowStage: window.WindowStage): void {
2181    windowStage.loadContent('pages/Index');
2182  }
2183}
2184
2185```
2186Obtain the current value of **Env** on the page.
2187```
2188// Index.ets
2189import { env } from '../pages/Env';
2190
2191@Entry
2192@ComponentV2
2193struct Index {
2194  build() {
2195    Row() {
2196      Column() {
2197        // Output the environment variables of the current device.
2198        Text(`languageCode: ${env.language}`).fontSize(20)
2199        Text(`colorMode: ${env.colorMode}`).fontSize(20)
2200        Text(`fontSizeScale: ${env.fontSizeScale}`).fontSize(20)
2201        Text(`fontWeightScale: ${env.fontWeightScale}`).fontSize(20)
2202      }
2203    }
2204  }
2205}
2206```
2207
2208### PersistentStorage -> PersistenceV2
2209In V1, **PersistentStorage** provides the capability of persisting UI data. In V2, **PersistenceV2** APIs are provided to replace **PersistentStorage**.
2210- The triggering time of **PersistentStorage** depends on the observation capability of **AppStorage** and is coupled with **AppStorage**. You cannot select the time to write or read persistent data.
2211- **PersistentStorage** uses serialization and deserialization. Without inputting types, **PersistentStorage** will lose its type and the property method of the object cannot be persisted.
2212
2213For PersistenceV2:
2214- The change of the \@Trace decorated property of the \@ObservedV2 object associated with PersistenceV2 triggers the automatic persistency of the entire associated object.
2215- You can also call the [PersistenceV2.save](./arkts-new-persistencev2.md#save-persisting-stored-data-manually) and [PersistenceV2.globalConnect](./arkts-new-persistencev2.md#connect-creating-or-obtaining-stored-data) APIs to manually trigger persistent writing and reading.
2216
2217V1:
2218
2219```
2220PersistentStorage.persistProp('aProp', 47);
2221
2222@Entry
2223@Component
2224struct Index {
2225  @StorageLink('aProp') aProp: number = 48;
2226
2227  build() {
2228    Row() {
2229      Column() {
2230      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2231        Text(`${this.aProp}`)
2232          .onClick(() => {
2233            this.aProp += 1;
2234          })
2235      }
2236    }
2237  }
2238}
2239```
2240
2241V2:
2242
2243The following case shows:
2244- The persistent data of **PersistentStorage** is migrated to PersistenceV2. In V2, the data marked by @Trace can be automatically persisted. For non-@Trace data, you need to manually call the **save** API to persist the data.
2245- In the following example, the **move** function and the components to display are placed in the same ETS. You can define your own **move()** and place it in a proper position for unified migration.
2246    - Click **aProp** and the UI is re-rendered.
2247    - Click **bProp** but the UI is not re-rendered.
2248    - Click **save storage** to flush the **PersistentStorage** link data to disks.
2249    - Exit and restart the application. The values of **aProp** and **bProp** displayed in the **Text** component are the values changed last time.
2250```
2251import { PersistenceV2 } from '@kit.ArkUI';
2252// Data center
2253@ObservedV2
2254class Storage {
2255  @Trace aProp: number = 0;
2256  bProp: number = 10;
2257}
2258
2259// Callback used to receive serialization failure.
2260PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
2261  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
2262});
2263
2264@Entry
2265@ComponentV2
2266struct Page1 {
2267  // Create a KV pair whose key is Sample in PersistenceV2 (if the key exists, the data in PersistenceV2 is returned) and associate it with prop.
2268  @Local storage: Storage = PersistenceV2.connect(Storage, () => new Storage())!;
2269
2270  build() {
2271    Column() {
2272      Text(`@Trace aProp: ${this.storage.aProp}`)
2273        .fontSize(30)
2274        .onClick(() => {
2275          this.storage.aProp++;
2276        })
2277      Text(`bProp:: ${this.storage.bProp}`)
2278        .fontSize(30)
2279        .onClick(() => {
2280          // The page is not re-rendered, but the value of bProp is changed.
2281          this.p.V2.name += 'a';
2282        })
2283
2284      Button('save storage')
2285        .onClick(() => {
2286          // Different from V1, PersistenceV2 does not depend on the capability of observing state variables. You can perform persistence proactively.
2287          PersistenceV2.save(Storage);
2288        })
2289    }
2290  }
2291}
2292```
2293
2294## Existing Application Migration
2295For large-scale applications that have been developed using V1, it is unlikely to migrate them from V1 to V2 at a time. Instead, they are migrated in batches and by component. As a result, V1 and V2 have to be used together.
2296
2297In this case, the parent components are of V1, and the migrated child component are of V2. Take the following components as an example:
2298- The parent component is \@Component, and the data source is \@LocalStorageLink.
2299- The child component is \@ComponentV2 and uses \@Param to receive data from the data source.
2300
2301In this case, the following policies can be used for migration:
2302- Declare a class decorated by \@ObservedV2 to encapsulate the data of V1.
2303- Define a custom component \@Component between \@Component and \@ComponentV2.
2304- At the bridging layer:
2305    - To synchronize data from V1 to V2, use the listening of \@Watch to trigger value changes of the class decorated by \@ObservedV2.
2306    - To synchronize data from V2 to V1, declare **Monitor** in the class decorated by \@ObservedV2 and use the API of **LocalStorage** to reversely notify state variables of V1.
2307
2308Example:
2309```
2310let storage: LocalStorage = new LocalStorage();
2311
2312@ObservedV2
2313class V1StorageData {
2314  @Trace title: string = 'V1OldComponent'
2315  @Monitor('title')
2316  onStrChange(monitor: IMonitor) {
2317    monitor.dirty.forEach((path: string) => {
2318      console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
2319      if (path === 'title') {
2320        storage.setOrCreate('title', this.title);
2321      }
2322    })
2323  }
2324}
2325let v1Data: V1StorageData = new V1StorageData();
2326
2327@Entry(storage)
2328@Component
2329struct V1OldComponent {
2330  @LocalStorageLink('title') title: string = 'V1OldComponent';
2331
2332  build() {
2333    Column() {
2334      Text(`V1OldComponent: ${this.title}`)
2335        .fontSize(20)
2336        .onClick(() => {
2337          this.title = 'new value from V1OldComponent';
2338        })
2339      Bridge()
2340    }
2341  }
2342}
2343
2344
2345@Component
2346struct Bridge {
2347  @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge';
2348  titleWatch() {
2349    v1Data.title = this.title;
2350  }
2351
2352  build() {
2353    NewV2Component()
2354  }
2355}
2356@ComponentV2
2357struct NewV2Component {
2358  build() {
2359    Column() {
2360      Text(`NewV2Component: ${v1Data.title}`)
2361        .fontSize(20)
2362        .onClick(() => {
2363          v1Data.title = 'NewV2Component';
2364        })
2365    }
2366  }
2367}
2368```
2369
2370## Other Migrations
2371
2372### Scrolling Components
2373
2374#### List
2375
2376You can use [ChildrenMainSize](../reference/apis-arkui/arkui-ts/ts-container-list.md#childrenmainsize12) to set the size of the child component of **List** along the main axis.
2377
2378V1:
2379
2380In V1, you can use [\@State](./arkts-state.md) to observe the API invoking.
2381
2382Example:
2383
2384```ts
2385@Entry
2386@Component
2387struct ListExample {
2388  private arr: Array<number> = new Array(10).fill(0);
2389  private scroller: ListScroller = new ListScroller();
2390  @State listSpace: number = 10;
2391  @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100);
2392
2393  build() {
2394    Column() {
2395      Button('change Default').onClick(() => {
2396        this.listChildrenSize.childDefaultSize += 10;
2397      })
2398
2399      Button('splice 5').onClick(() => {
2400        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2401      })
2402
2403      Button('update 5').onClick(() => {
2404        this.listChildrenSize.update(0, 200);
2405      })
2406
2407      List({ space: this.listSpace, scroller: this.scroller }) {
2408        ForEach(this.arr, (item: number) => {
2409          ListItem() {
2410            Text(`item-` + item)
2411          }.backgroundColor(Color.Pink)
2412        })
2413      }
2414      .childrenMainSize(this.listChildrenSize) // 10
2415    }
2416  }
2417}
2418```
2419
2420V2:
2421
2422In V2, however, [\@Local](./arkts-new-local.md) can only observe its own changes, but cannot observe the top-level changes. In addition, because **ChildrenMainSize** is defined in the framework, you cannot use [\@Trace](./arkts-new-observedV2-and-trace.md) to mark the attributes of **ChildrenMainSize**. In this case, you can use [makeObserved](./arkts-new-makeObserved.md) instead.
2423
2424Example:
2425
2426```ts
2427import { UIUtils } from '@kit.ArkUI';
2428
2429@Entry
2430@ComponentV2
2431struct ListExample {
2432  private arr: Array<number> = new Array(10).fill(0);
2433  private scroller: ListScroller = new ListScroller();
2434  listSpace: number = 10;
2435  // Use the makeObserved capability to observe ChildrenMainSize.
2436  listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100));
2437
2438  build() {
2439    Column() {
2440      Button('change Default').onClick(() => {
2441        this.listChildrenSize.childDefaultSize += 10;
2442      })
2443
2444      Button('splice 5').onClick(() => {
2445        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2446      })
2447
2448      Button('update 5').onClick(() => {
2449        this.listChildrenSize.update(0, 200);
2450      })
2451
2452      List({ space: this.listSpace, scroller: this.scroller }) {
2453        ForEach(this.arr, (item: number) => {
2454          ListItem() {
2455            Text(`item-` + item)
2456          }.backgroundColor(Color.Pink)
2457        })
2458      }
2459      .childrenMainSize(this.listChildrenSize) // 10
2460    }
2461  }
2462}
2463```
2464
2465#### WaterFlow
2466
2467You can use [WaterFlowSections](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12) to set the water flow item sections.
2468
2469Note that the length of **arr** must be the same as the total length of **itemsCount** of all **SectionOptions** in **WaterFlowSections**. Otherwise, **WaterFlow** cannot process the array and the UI cannot be re-rendered.
2470
2471The following two examples shows buttons **push option**, **splice option**, and **update option** are clicked in sequence.
2472
2473V1:
2474
2475In V1, you can use [\@State](./arkts-state.md) to observe the API invoking.
2476
2477Example:
2478
2479```ts
2480@Entry
2481@Component
2482struct WaterFlowSample {
2483  @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2484  @State sections: WaterFlowSections = new WaterFlowSections();
2485  scroller: Scroller = new Scroller();
2486  @State private arr: Array<number> = new Array(9).fill(0);
2487  oneColumnSection: SectionOptions = {
2488    itemsCount: 4,
2489    crossCount: 1,
2490    columnsGap: '5vp',
2491    rowsGap: 10,
2492  };
2493  twoColumnSection: SectionOptions = {
2494    itemsCount: 2,
2495    crossCount: 2,
2496  };
2497  lastSection: SectionOptions = {
2498    itemsCount: 3,
2499    crossCount: 3,
2500  };
2501
2502  aboutToAppear(): void {
2503    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2504    this.sections.splice(0, 0, sectionOptions);
2505  }
2506
2507  build() {
2508    Column() {
2509      Text(`${this.arr.length}`)
2510
2511      Button('push option').onClick(() => {
2512        let section: SectionOptions = {
2513          itemsCount: 1,
2514          crossCount: 1,
2515        };
2516        this.sections.push(section);
2517        this.arr.push(100);
2518      })
2519
2520      Button('splice option').onClick(() => {
2521        let section: SectionOptions = {
2522          itemsCount: 8,
2523          crossCount: 2,
2524        };
2525        this.sections.splice(0, this.arr.length, [section]);
2526        this.arr = new Array(8).fill(10);
2527      })
2528
2529      Button('update option').onClick(() => {
2530        let section: SectionOptions = {
2531          itemsCount: 8,
2532          crossCount: 2,
2533        };
2534        this.sections.update(1, section);
2535        this.arr = new Array(16).fill(1);
2536      })
2537
2538      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2539        ForEach(this.arr, (item: number) => {
2540          FlowItem() {
2541            Text(`${item}`)
2542              .border({ width: 1 })
2543              .backgroundColor(this.colors[item % 6])
2544              .height(30)
2545              .width(50)
2546          }
2547        })
2548      }
2549    }
2550  }
2551}
2552```
2553
2554V2:
2555
2556In V2, however, [\@Local](./arkts-new-local.md) can only observe its own changes, but cannot observe the top-level changes. In addition, because **WaterFlowSections** is defined in the framework, you cannot use [\@Trace](./arkts-new-observedV2-and-trace.md) to mark the attributes of **WaterFlowSections**. In this case, you can use [makeObserved](./arkts-new-makeObserved.md) instead.
2557
2558Example:
2559
2560```ts
2561import { UIUtils } from '@kit.ArkUI';
2562
2563@Entry
2564@ComponentV2
2565struct WaterFlowSample {
2566  colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2567  // Use the makeObserved capability to observe WaterFlowSections.
2568  sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections());
2569  scroller: Scroller = new Scroller();
2570  @Local private arr: Array<number> = new Array(9).fill(0);
2571  oneColumnSection: SectionOptions = {
2572    itemsCount: 4,
2573    crossCount: 1,
2574    columnsGap: '5vp',
2575    rowsGap: 10,
2576  };
2577  twoColumnSection: SectionOptions = {
2578    itemsCount: 2,
2579    crossCount: 2,
2580  };
2581  lastSection: SectionOptions = {
2582    itemsCount: 3,
2583    crossCount: 3,
2584  };
2585
2586  aboutToAppear(): void {
2587    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2588    this.sections.splice(0, 0, sectionOptions);
2589  }
2590
2591  build() {
2592    Column() {
2593      Text(`${this.arr.length}`)
2594
2595      Button('push option').onClick(() => {
2596        let section: SectionOptions = {
2597          itemsCount: 1,
2598          crossCount: 1,
2599        };
2600        this.sections.push(section);
2601        this.arr.push(100);
2602      })
2603
2604      Button('splice option').onClick(() => {
2605        let section: SectionOptions = {
2606          itemsCount: 8,
2607          crossCount: 2,
2608        };
2609        this.sections.splice(0, this.arr.length, [section]);
2610        this.arr = new Array(8).fill(10);
2611      })
2612
2613      Button('update option').onClick(() => {
2614        let section: SectionOptions = {
2615          itemsCount: 8,
2616          crossCount: 2,
2617        };
2618        this.sections.update(1, section);
2619        this.arr = new Array(16).fill(1);
2620      })
2621
2622      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2623        ForEach(this.arr, (item: number) => {
2624          FlowItem() {
2625            Text(`${item}`)
2626              .border({ width: 1 })
2627              .backgroundColor(this.colors[item % 6])
2628              .height(30)
2629              .width(50)
2630          }
2631        })
2632      }
2633    }
2634  }
2635}
2636```
2637
2638### Modifier
2639
2640#### attributeModifier
2641
2642You can use [attributeModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier) to dynamically set component attributes.
2643
2644V1:
2645
2646In V1, you can use [\@State](./arkts-state.md) to observe changes.
2647
2648Example:
2649
2650```ts
2651class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2652  isDark: boolean = false;
2653
2654  applyNormalAttribute(instance: ButtonAttribute): void {
2655    if (this.isDark) {
2656      instance.backgroundColor(Color.Black);
2657    } else {
2658      instance.backgroundColor(Color.Red);
2659    }
2660  }
2661}
2662
2663@Entry
2664@Component
2665struct AttributeDemo {
2666  @State modifier: MyButtonModifier = new MyButtonModifier();
2667
2668  build() {
2669    Row() {
2670      Column() {
2671        Button('Button')
2672          .attributeModifier(this.modifier)
2673          .onClick(() => {
2674            this.modifier.isDark = !this.modifier.isDark;
2675          })
2676      }
2677      .width('100%')
2678    }
2679    .height('100%')
2680  }
2681}
2682```
2683
2684V2:
2685
2686In V2, however, [\@Local](./arkts-new-local.md) can only observe its own changes, but cannot observe the top-level changes. To observe the attribute changes of **attributeModifier**, use[makeObserved](./arkts-new-makeObserved.md) instead.
2687
2688Example:
2689
2690```ts
2691import { UIUtils } from '@kit.ArkUI';
2692
2693class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2694  isDark: boolean = false;
2695
2696  applyNormalAttribute(instance: ButtonAttribute): void {
2697    if (this.isDark) {
2698      instance.backgroundColor(Color.Black);
2699    } else {
2700      instance.backgroundColor(Color.Red);
2701    }
2702  }
2703}
2704
2705@Entry
2706@ComponentV2
2707struct AttributeDemo {
2708  // Use the makeObserved capability to observe the this.modifier attribute of attributeModifier.
2709  modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier());
2710
2711  build() {
2712    Row() {
2713      Column() {
2714        Button('Button')
2715          .attributeModifier(this.modifier)
2716          .onClick(() => {
2717            this.modifier.isDark = !this.modifier.isDark;
2718          })
2719      }
2720      .width('100%')
2721    }
2722    .height('100%')
2723  }
2724}
2725```
2726
2727#### CommonModifier
2728
2729Dynamically sets attributes on the current component. The following uses [CommonModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#custom-modifier) as an example.
2730
2731V1:
2732
2733In V1, you can use [\@State](./arkts-state.md) to observe changes.
2734
2735Example:
2736
2737```ts
2738import { CommonModifier } from '@ohos.arkui.modifier';
2739
2740class MyModifier extends CommonModifier {
2741  applyNormalAttribute(instance: CommonAttribute): void {
2742    super.applyNormalAttribute?.(instance);
2743  }
2744
2745  public setGroup1(): void {
2746    this.borderStyle(BorderStyle.Dotted);
2747    this.borderWidth(8);
2748  }
2749
2750  public setGroup2(): void {
2751    this.borderStyle(BorderStyle.Dashed);
2752    this.borderWidth(8);
2753  }
2754}
2755
2756@Component
2757struct MyImage1 {
2758  @Link modifier: CommonModifier;
2759
2760  build() {
2761    // 'app.media.app_icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
2762    Image($r('app.media.app_icon'))
2763      .attributeModifier(this.modifier as MyModifier)
2764  }
2765}
2766
2767@Entry
2768@Component
2769struct Index {
2770  @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10);
2771  index: number = 0;
2772
2773  build() {
2774    Column() {
2775      Button($r('app.string.EntryAbility_label'))
2776        .margin(10)
2777        .onClick(() => {
2778          console.log('Modifier', 'onClick');
2779          this.index++;
2780          if (this.index % 2 === 1) {
2781            (this.myModifier as MyModifier).setGroup1();
2782            console.log('Modifier', 'setGroup1');
2783          } else {
2784            (this.myModifier as MyModifier).setGroup2();
2785            console.log('Modifier', 'setGroup2');
2786          }
2787        })
2788
2789      MyImage1({ modifier: this.myModifier })
2790    }
2791    .width('100%')
2792  }
2793}
2794```
2795
2796V2:
2797
2798In V2, however, [\@Local](./arkts-new-local.md) can only observe its own changes, but cannot observe the top-level changes. In addition, [CommonModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#custom-modifier) is re-rendered through its properties in the framework, in this case, you can use [makeObserved](./arkts-new-makeObserved.md) instead.
2799
2800Example:
2801
2802```ts
2803import { UIUtils } from '@kit.ArkUI';
2804import { CommonModifier } from '@ohos.arkui.modifier';
2805
2806class MyModifier extends CommonModifier {
2807  applyNormalAttribute(instance: CommonAttribute): void {
2808    super.applyNormalAttribute?.(instance);
2809  }
2810
2811  public setGroup1(): void {
2812    this.borderStyle(BorderStyle.Dotted);
2813    this.borderWidth(8);
2814  }
2815
2816  public setGroup2(): void {
2817    this.borderStyle(BorderStyle.Dashed);
2818    this.borderWidth(8);
2819  }
2820}
2821
2822@ComponentV2
2823struct MyImage1 {
2824  @Param @Require modifier: CommonModifier;
2825
2826  build() {
2827    // 'app.media.app_icon' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
2828    Image($r('app.media.app_icon'))
2829      .attributeModifier(this.modifier as MyModifier)
2830  }
2831}
2832
2833@Entry
2834@ComponentV2
2835struct Index {
2836  // Use the makeObserved capability to observe CommonModifier.
2837  @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
2838  index: number = 0;
2839
2840  build() {
2841    Column() {
2842      Button($r('app.string.EntryAbility_label'))
2843        .margin(10)
2844        .onClick(() => {
2845          console.log('Modifier', 'onClick');
2846          this.index++;
2847          if (this.index % 2 === 1) {
2848            (this.myModifier as MyModifier).setGroup1();
2849            console.log('Modifier', 'setGroup1');
2850          } else {
2851            (this.myModifier as MyModifier).setGroup2();
2852            console.log('Modifier', 'setGroup2');
2853          }
2854        })
2855
2856      MyImage1({ modifier: this.myModifier })
2857    }
2858    .width('100%')
2859  }
2860}
2861```
2862
2863#### Component Modifier
2864
2865Dynamically sets attributes on the current component. The following uses the **Text** component as an example.
2866
2867V1:
2868
2869In V1, you can use [\@State](./arkts-state.md) to observe changes.
2870
2871Example:
2872
2873```ts
2874import { TextModifier } from '@ohos.arkui.modifier';
2875
2876class MyModifier extends TextModifier {
2877  applyNormalAttribute(instance: TextModifier): void {
2878    super.applyNormalAttribute?.(instance);
2879  }
2880
2881  public setGroup1(): void {
2882    this.fontSize(50);
2883    this.fontColor(Color.Pink);
2884  }
2885
2886  public setGroup2(): void {
2887    this.fontSize(50);
2888    this.fontColor(Color.Gray);
2889  }
2890}
2891
2892@Component
2893struct MyImage1 {
2894  @Link modifier: TextModifier;
2895  index: number = 0;
2896
2897  build() {
2898    Column() {
2899      Text('Test')
2900        .attributeModifier(this.modifier as MyModifier)
2901
2902      Button($r('app.string.EntryAbility_label'))
2903        .margin(10)
2904        .onClick(() => {
2905          console.log('Modifier', 'onClick');
2906          this.index++;
2907          if (this.index % 2 === 1) {
2908            (this.modifier as MyModifier).setGroup1();
2909            console.log('Modifier', 'setGroup1');
2910          } else {
2911            (this.modifier as MyModifier).setGroup2();
2912            console.log('Modifier', 'setGroup2');
2913          }
2914        })
2915    }
2916  }
2917}
2918
2919@Entry
2920@Component
2921struct Index {
2922  @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10);
2923  index: number = 0;
2924
2925  build() {
2926    Column() {
2927      MyImage1({ modifier: this.myModifier })
2928
2929      Button('replace whole')
2930        .margin(10)
2931        .onClick(() => {
2932          this.myModifier = new MyModifier().backgroundColor(Color.Orange);
2933        })
2934    }
2935    .width('100%')
2936  }
2937}
2938```
2939
2940V2:
2941
2942In V2, however, [\@Local](./arkts-new-local.md) can only observe its own changes, but cannot observe the top-level changes. In this case, you can use [makeObserved](./arkts-new-makeObserved.md) instead.
2943
2944Example:
2945
2946```ts
2947import { UIUtils } from '@kit.ArkUI';
2948import { TextModifier } from '@ohos.arkui.modifier';
2949
2950class MyModifier extends TextModifier {
2951  applyNormalAttribute(instance: TextModifier): void {
2952    super.applyNormalAttribute?.(instance);
2953  }
2954
2955  public setGroup1(): void {
2956    this.fontSize(50);
2957    this.fontColor(Color.Pink);
2958  }
2959
2960  public setGroup2(): void {
2961    this.fontSize(50);
2962    this.fontColor(Color.Gray);
2963  }
2964}
2965
2966@ComponentV2
2967struct MyImage1 {
2968  @Param @Require modifier: TextModifier;
2969  index: number = 0;
2970
2971  build() {
2972    Column() {
2973      Text('Test')
2974        .attributeModifier(this.modifier as MyModifier)
2975
2976      Button($r('app.string.EntryAbility_label'))
2977        .margin(10)
2978        .onClick(() => {
2979          console.log('Modifier', 'onClick');
2980          this.index++;
2981          if (this.index % 2 === 1) {
2982            (this.modifier as MyModifier).setGroup1();
2983            console.log('Modifier', 'setGroup1');
2984          } else {
2985            (this.modifier as MyModifier).setGroup2();
2986            console.log('Modifier', 'setGroup2');
2987          }
2988        })
2989    }
2990  }
2991}
2992
2993@Entry
2994@ComponentV2
2995struct Index {
2996  // Use the makeObserved capability to observe TextModifier.
2997  @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
2998  index: number = 0;
2999
3000  build() {
3001    Column() {
3002      MyImage1({ modifier: this.myModifier })
3003
3004      Button('replace whole')
3005        .margin(10)
3006        .onClick(() => {
3007          this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange));
3008        })
3009    }
3010    .width('100%')
3011  }
3012}
3013```
3014
3015<!--no_check-->