• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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#globalconnect-creating-or-obtaining-stored-data) APIs to manually trigger persistent writing and reading.
2216
2217V1:
2218
2219```ts
2220class data {
2221  name: string = 'ZhangSan';
2222  id: number = 0;
2223}
2224
2225PersistentStorage.persistProp('numProp', 47);
2226PersistentStorage.persistProp('dataProp', new data());
2227
2228@Entry
2229@Component
2230struct Index {
2231  @StorageLink('numProp') numProp: number = 48;
2232  @StorageLink('dataProp') dataProp: data = new data();
2233
2234  build() {
2235    Column() {
2236      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2237      Text(`numProp: ${this.numProp}`)
2238        .onClick(() => {
2239          this.numProp += 1;
2240        })
2241        .fontSize(30)
2242
2243      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2244      Text(`dataProp.name: ${this.dataProp.name}`)
2245        .onClick(() => {
2246          this.dataProp.name += 'a';
2247        })
2248        .fontSize(30)
2249      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2250      Text(`dataProp.id: ${this.dataProp.id}`)
2251        .onClick(() => {
2252          this.dataProp.id += 1;
2253        })
2254        .fontSize(30)
2255
2256    }
2257    .width('100%')
2258  }
2259}
2260```
2261
2262V2:
2263
2264The following case shows:
2265- 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.
2266- 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.
2267```ts
2268// Migrate to GlobalConnect.
2269import { PersistenceV2, Type } from '@kit.ArkUI';
2270
2271// Callback used to receive serialization failure.
2272PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
2273  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
2274});
2275
2276class Data {
2277  name: string = 'ZhangSan';
2278  id: number = 0;
2279}
2280
2281@ObservedV2
2282class V2Data {
2283  @Trace name: string = '';
2284  @Trace Id: number = 1;
2285}
2286
2287@ObservedV2
2288export class Sample {
2289  // Complex objects need to be decorated by @Type to ensure successful serialization.
2290  @Type(V2Data)
2291  @Trace num: number = 1;
2292  @Trace V2: V2Data = new V2Data();
2293}
2294
2295// Auxiliary data used to determine whether data migration is complete
2296@ObservedV2
2297class StorageState {
2298  @Trace isCompleteMoving: boolean = false;
2299}
2300
2301function move() {
2302  let movingState = PersistenceV2.globalConnect({type: StorageState, defaultCreator: () => new StorageState()})!;
2303  if (!movingState.isCompleteMoving) {
2304    PersistentStorage.persistProp('numProp', 47);
2305    PersistentStorage.persistProp('dataProp', new Data());
2306    let num = AppStorage.get<number>('numProp')!;
2307    let V1Data = AppStorage.get<Data>('dataProp')!;
2308    PersistentStorage.deleteProp('numProp');
2309    PersistentStorage.deleteProp('dataProp');
2310
2311    // Create the corresponding data in V2.
2312    let migrate = PersistenceV2.globalConnect({type: Sample, key: 'connect2', defaultCreator: () => new Sample()})!;  // You can use the default constructor.
2313    // For assigned value decorated by @Trace, it is automatically saved. For non-@Trace objects, you can also call save() to save the data, for example, PersistenceV2.save('connect2').
2314    migrate.num = num;
2315    migrate.V2.name = V1Data.name;
2316    migrate.V2.Id = V1Data.id;
2317
2318    // Set the migration flag to true.
2319    movingState.isCompleteMoving = true;
2320  }
2321}
2322
2323move();
2324
2325@Entry
2326@ComponentV2
2327struct Page1 {
2328  @Local refresh: number = 0;
2329  // Use key:connect2 to store data.
2330  @Local p: Sample = PersistenceV2.globalConnect({type: Sample, key:'connect2', defaultCreator:() => new Sample()})!;
2331
2332  build() {
2333    Column({space: 5}) {
2334      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2335      Text(`numProp: ${this.p.num}`)
2336        .onClick(() => {
2337          this.p.num += 1;
2338        })
2339        .fontSize(30)
2340
2341      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2342      Text(`dataProp.name: ${this.p.V2.name}`)
2343        .onClick(() => {
2344          this.p.V2.name += 'a';
2345        })
2346        .fontSize(30)
2347      // The current result is saved when the application exits. After the restart, the last saved result is displayed.
2348      Text(`dataProp.id: ${this.p.V2.Id}`)
2349        .onClick(() => {
2350          this.p.V2.Id += 1;
2351        })
2352        .fontSize(30)
2353    }
2354    .width('100%')
2355  }
2356}
2357```
2358
2359## Existing Application Migration
2360
2361For 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.
2362
2363In this case, the parent components are of V1, and the migrated child component are of V2. Take the following components as an example:
2364- The parent component is \@Component, and the data source is \@LocalStorageLink.
2365- The child component is \@ComponentV2 and uses \@Param to receive data from the data source.
2366
2367In this case, the following policies can be used for migration:
2368- Declare a class decorated by \@ObservedV2 to encapsulate the data of V1.
2369- Define a custom component \@Component between \@Component and \@ComponentV2.
2370- At the bridging layer:
2371    - To synchronize data from V1 to V2, use the listening of \@Watch to trigger value changes of the class decorated by \@ObservedV2.
2372    - 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.
2373
2374Example:
2375```
2376let storage: LocalStorage = new LocalStorage();
2377
2378@ObservedV2
2379class V1StorageData {
2380  @Trace title: string = 'V1OldComponent'
2381  @Monitor('title')
2382  onStrChange(monitor: IMonitor) {
2383    monitor.dirty.forEach((path: string) => {
2384      console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
2385      if (path === 'title') {
2386        storage.setOrCreate('title', this.title);
2387      }
2388    })
2389  }
2390}
2391let v1Data: V1StorageData = new V1StorageData();
2392
2393@Entry(storage)
2394@Component
2395struct V1OldComponent {
2396  @LocalStorageLink('title') title: string = 'V1OldComponent';
2397
2398  build() {
2399    Column() {
2400      Text(`V1OldComponent: ${this.title}`)
2401        .fontSize(20)
2402        .onClick(() => {
2403          this.title = 'new value from V1OldComponent';
2404        })
2405      Bridge()
2406    }
2407  }
2408}
2409
2410
2411@Component
2412struct Bridge {
2413  @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge';
2414  titleWatch() {
2415    v1Data.title = this.title;
2416  }
2417
2418  build() {
2419    NewV2Component()
2420  }
2421}
2422@ComponentV2
2423struct NewV2Component {
2424  build() {
2425    Column() {
2426      Text(`NewV2Component: ${v1Data.title}`)
2427        .fontSize(20)
2428        .onClick(() => {
2429          v1Data.title = 'NewV2Component';
2430        })
2431    }
2432  }
2433}
2434```
2435
2436## Other Migrations
2437
2438### Scrolling Components
2439
2440#### List
2441
2442You 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.
2443
2444V1:
2445
2446In V1, you can use [\@State](./arkts-state.md) to observe the API invoking.
2447
2448Example:
2449
2450```ts
2451@Entry
2452@Component
2453struct ListExample {
2454  private arr: Array<number> = new Array(10).fill(0);
2455  private scroller: ListScroller = new ListScroller();
2456  @State listSpace: number = 10;
2457  @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100);
2458
2459  build() {
2460    Column() {
2461      Button('change Default').onClick(() => {
2462        this.listChildrenSize.childDefaultSize += 10;
2463      })
2464
2465      Button('splice 5').onClick(() => {
2466        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2467      })
2468
2469      Button('update 5').onClick(() => {
2470        this.listChildrenSize.update(0, 200);
2471      })
2472
2473      List({ space: this.listSpace, scroller: this.scroller }) {
2474        ForEach(this.arr, (item: number) => {
2475          ListItem() {
2476            Text(`item-` + item)
2477          }.backgroundColor(Color.Pink)
2478        })
2479      }
2480      .childrenMainSize(this.listChildrenSize) // 10
2481    }
2482  }
2483}
2484```
2485
2486V2:
2487
2488In 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.
2489
2490Example:
2491
2492```ts
2493import { UIUtils } from '@kit.ArkUI';
2494
2495@Entry
2496@ComponentV2
2497struct ListExample {
2498  private arr: Array<number> = new Array(10).fill(0);
2499  private scroller: ListScroller = new ListScroller();
2500  listSpace: number = 10;
2501  // Use the makeObserved capability to observe ChildrenMainSize.
2502  listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100));
2503
2504  build() {
2505    Column() {
2506      Button('change Default').onClick(() => {
2507        this.listChildrenSize.childDefaultSize += 10;
2508      })
2509
2510      Button('splice 5').onClick(() => {
2511        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2512      })
2513
2514      Button('update 5').onClick(() => {
2515        this.listChildrenSize.update(0, 200);
2516      })
2517
2518      List({ space: this.listSpace, scroller: this.scroller }) {
2519        ForEach(this.arr, (item: number) => {
2520          ListItem() {
2521            Text(`item-` + item)
2522          }.backgroundColor(Color.Pink)
2523        })
2524      }
2525      .childrenMainSize(this.listChildrenSize) // 10
2526    }
2527  }
2528}
2529```
2530
2531#### WaterFlow
2532
2533You can use [WaterFlowSections](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12) to set the water flow item sections.
2534
2535Note 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.
2536
2537The following two examples shows buttons **push option**, **splice option**, and **update option** are clicked in sequence.
2538
2539V1:
2540
2541In V1, you can use [\@State](./arkts-state.md) to observe the API invoking.
2542
2543Example:
2544
2545```ts
2546@Entry
2547@Component
2548struct WaterFlowSample {
2549  @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2550  @State sections: WaterFlowSections = new WaterFlowSections();
2551  scroller: Scroller = new Scroller();
2552  @State private arr: Array<number> = new Array(9).fill(0);
2553  oneColumnSection: SectionOptions = {
2554    itemsCount: 4,
2555    crossCount: 1,
2556    columnsGap: '5vp',
2557    rowsGap: 10,
2558  };
2559  twoColumnSection: SectionOptions = {
2560    itemsCount: 2,
2561    crossCount: 2,
2562  };
2563  lastSection: SectionOptions = {
2564    itemsCount: 3,
2565    crossCount: 3,
2566  };
2567
2568  aboutToAppear(): void {
2569    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2570    this.sections.splice(0, 0, sectionOptions);
2571  }
2572
2573  build() {
2574    Column() {
2575      Text(`${this.arr.length}`)
2576
2577      Button('push option').onClick(() => {
2578        let section: SectionOptions = {
2579          itemsCount: 1,
2580          crossCount: 1,
2581        };
2582        this.sections.push(section);
2583        this.arr.push(100);
2584      })
2585
2586      Button('splice option').onClick(() => {
2587        let section: SectionOptions = {
2588          itemsCount: 8,
2589          crossCount: 2,
2590        };
2591        this.sections.splice(0, this.arr.length, [section]);
2592        this.arr = new Array(8).fill(10);
2593      })
2594
2595      Button('update option').onClick(() => {
2596        let section: SectionOptions = {
2597          itemsCount: 8,
2598          crossCount: 2,
2599        };
2600        this.sections.update(1, section);
2601        this.arr = new Array(16).fill(1);
2602      })
2603
2604      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2605        ForEach(this.arr, (item: number) => {
2606          FlowItem() {
2607            Text(`${item}`)
2608              .border({ width: 1 })
2609              .backgroundColor(this.colors[item % 6])
2610              .height(30)
2611              .width(50)
2612          }
2613        })
2614      }
2615    }
2616  }
2617}
2618```
2619
2620V2:
2621
2622In 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.
2623
2624Example:
2625
2626```ts
2627import { UIUtils } from '@kit.ArkUI';
2628
2629@Entry
2630@ComponentV2
2631struct WaterFlowSample {
2632  colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2633  // Use the makeObserved capability to observe WaterFlowSections.
2634  sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections());
2635  scroller: Scroller = new Scroller();
2636  @Local private arr: Array<number> = new Array(9).fill(0);
2637  oneColumnSection: SectionOptions = {
2638    itemsCount: 4,
2639    crossCount: 1,
2640    columnsGap: '5vp',
2641    rowsGap: 10,
2642  };
2643  twoColumnSection: SectionOptions = {
2644    itemsCount: 2,
2645    crossCount: 2,
2646  };
2647  lastSection: SectionOptions = {
2648    itemsCount: 3,
2649    crossCount: 3,
2650  };
2651
2652  aboutToAppear(): void {
2653    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2654    this.sections.splice(0, 0, sectionOptions);
2655  }
2656
2657  build() {
2658    Column() {
2659      Text(`${this.arr.length}`)
2660
2661      Button('push option').onClick(() => {
2662        let section: SectionOptions = {
2663          itemsCount: 1,
2664          crossCount: 1,
2665        };
2666        this.sections.push(section);
2667        this.arr.push(100);
2668      })
2669
2670      Button('splice option').onClick(() => {
2671        let section: SectionOptions = {
2672          itemsCount: 8,
2673          crossCount: 2,
2674        };
2675        this.sections.splice(0, this.arr.length, [section]);
2676        this.arr = new Array(8).fill(10);
2677      })
2678
2679      Button('update option').onClick(() => {
2680        let section: SectionOptions = {
2681          itemsCount: 8,
2682          crossCount: 2,
2683        };
2684        this.sections.update(1, section);
2685        this.arr = new Array(16).fill(1);
2686      })
2687
2688      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2689        ForEach(this.arr, (item: number) => {
2690          FlowItem() {
2691            Text(`${item}`)
2692              .border({ width: 1 })
2693              .backgroundColor(this.colors[item % 6])
2694              .height(30)
2695              .width(50)
2696          }
2697        })
2698      }
2699    }
2700  }
2701}
2702```
2703
2704### Modifier
2705
2706#### attributeModifier
2707
2708You can use [attributeModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier) to dynamically set component attributes.
2709
2710V1:
2711
2712In V1, you can use [\@State](./arkts-state.md) to observe changes.
2713
2714Example:
2715
2716```ts
2717class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2718  isDark: boolean = false;
2719
2720  applyNormalAttribute(instance: ButtonAttribute): void {
2721    if (this.isDark) {
2722      instance.backgroundColor(Color.Black);
2723    } else {
2724      instance.backgroundColor(Color.Red);
2725    }
2726  }
2727}
2728
2729@Entry
2730@Component
2731struct AttributeDemo {
2732  @State modifier: MyButtonModifier = new MyButtonModifier();
2733
2734  build() {
2735    Row() {
2736      Column() {
2737        Button('Button')
2738          .attributeModifier(this.modifier)
2739          .onClick(() => {
2740            this.modifier.isDark = !this.modifier.isDark;
2741          })
2742      }
2743      .width('100%')
2744    }
2745    .height('100%')
2746  }
2747}
2748```
2749
2750V2:
2751
2752In 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.
2753
2754Example:
2755
2756```ts
2757import { UIUtils } from '@kit.ArkUI';
2758
2759class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2760  isDark: boolean = false;
2761
2762  applyNormalAttribute(instance: ButtonAttribute): void {
2763    if (this.isDark) {
2764      instance.backgroundColor(Color.Black);
2765    } else {
2766      instance.backgroundColor(Color.Red);
2767    }
2768  }
2769}
2770
2771@Entry
2772@ComponentV2
2773struct AttributeDemo {
2774  // Use the makeObserved capability to observe the this.modifier attribute of attributeModifier.
2775  modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier());
2776
2777  build() {
2778    Row() {
2779      Column() {
2780        Button('Button')
2781          .attributeModifier(this.modifier)
2782          .onClick(() => {
2783            this.modifier.isDark = !this.modifier.isDark;
2784          })
2785      }
2786      .width('100%')
2787    }
2788    .height('100%')
2789  }
2790}
2791```
2792
2793#### CommonModifier
2794
2795Dynamically 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.
2796
2797V1:
2798
2799In V1, you can use [\@State](./arkts-state.md) to observe changes.
2800
2801Example:
2802
2803```ts
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@Component
2823struct MyImage1 {
2824  @Link 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@Component
2835struct Index {
2836  @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10);
2837  index: number = 0;
2838
2839  build() {
2840    Column() {
2841      Button($r('app.string.EntryAbility_label'))
2842        .margin(10)
2843        .onClick(() => {
2844          console.log('Modifier', 'onClick');
2845          this.index++;
2846          if (this.index % 2 === 1) {
2847            (this.myModifier as MyModifier).setGroup1();
2848            console.log('Modifier', 'setGroup1');
2849          } else {
2850            (this.myModifier as MyModifier).setGroup2();
2851            console.log('Modifier', 'setGroup2');
2852          }
2853        })
2854
2855      MyImage1({ modifier: this.myModifier })
2856    }
2857    .width('100%')
2858  }
2859}
2860```
2861
2862V2:
2863
2864In 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.
2865
2866Example:
2867
2868```ts
2869import { UIUtils } from '@kit.ArkUI';
2870import { CommonModifier } from '@ohos.arkui.modifier';
2871
2872class MyModifier extends CommonModifier {
2873  applyNormalAttribute(instance: CommonAttribute): void {
2874    super.applyNormalAttribute?.(instance);
2875  }
2876
2877  public setGroup1(): void {
2878    this.borderStyle(BorderStyle.Dotted);
2879    this.borderWidth(8);
2880  }
2881
2882  public setGroup2(): void {
2883    this.borderStyle(BorderStyle.Dashed);
2884    this.borderWidth(8);
2885  }
2886}
2887
2888@ComponentV2
2889struct MyImage1 {
2890  @Param @Require modifier: CommonModifier;
2891
2892  build() {
2893    // '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.
2894    Image($r('app.media.app_icon'))
2895      .attributeModifier(this.modifier as MyModifier)
2896  }
2897}
2898
2899@Entry
2900@ComponentV2
2901struct Index {
2902  // Use the makeObserved capability to observe CommonModifier.
2903  @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
2904  index: number = 0;
2905
2906  build() {
2907    Column() {
2908      Button($r('app.string.EntryAbility_label'))
2909        .margin(10)
2910        .onClick(() => {
2911          console.log('Modifier', 'onClick');
2912          this.index++;
2913          if (this.index % 2 === 1) {
2914            (this.myModifier as MyModifier).setGroup1();
2915            console.log('Modifier', 'setGroup1');
2916          } else {
2917            (this.myModifier as MyModifier).setGroup2();
2918            console.log('Modifier', 'setGroup2');
2919          }
2920        })
2921
2922      MyImage1({ modifier: this.myModifier })
2923    }
2924    .width('100%')
2925  }
2926}
2927```
2928
2929#### Component Modifier
2930
2931Dynamically sets attributes on the current component. The following uses the **Text** component as an example.
2932
2933V1:
2934
2935In V1, you can use [\@State](./arkts-state.md) to observe changes.
2936
2937Example:
2938
2939```ts
2940import { TextModifier } from '@ohos.arkui.modifier';
2941
2942class MyModifier extends TextModifier {
2943  applyNormalAttribute(instance: TextModifier): void {
2944    super.applyNormalAttribute?.(instance);
2945  }
2946
2947  public setGroup1(): void {
2948    this.fontSize(50);
2949    this.fontColor(Color.Pink);
2950  }
2951
2952  public setGroup2(): void {
2953    this.fontSize(50);
2954    this.fontColor(Color.Gray);
2955  }
2956}
2957
2958@Component
2959struct MyImage1 {
2960  @Link modifier: TextModifier;
2961  index: number = 0;
2962
2963  build() {
2964    Column() {
2965      Text('Test')
2966        .attributeModifier(this.modifier as MyModifier)
2967
2968      Button($r('app.string.EntryAbility_label'))
2969        .margin(10)
2970        .onClick(() => {
2971          console.log('Modifier', 'onClick');
2972          this.index++;
2973          if (this.index % 2 === 1) {
2974            (this.modifier as MyModifier).setGroup1();
2975            console.log('Modifier', 'setGroup1');
2976          } else {
2977            (this.modifier as MyModifier).setGroup2();
2978            console.log('Modifier', 'setGroup2');
2979          }
2980        })
2981    }
2982  }
2983}
2984
2985@Entry
2986@Component
2987struct Index {
2988  @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10);
2989  index: number = 0;
2990
2991  build() {
2992    Column() {
2993      MyImage1({ modifier: this.myModifier })
2994
2995      Button('replace whole')
2996        .margin(10)
2997        .onClick(() => {
2998          this.myModifier = new MyModifier().backgroundColor(Color.Orange);
2999        })
3000    }
3001    .width('100%')
3002  }
3003}
3004```
3005
3006V2:
3007
3008In 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.
3009
3010Example:
3011
3012```ts
3013import { UIUtils } from '@kit.ArkUI';
3014import { TextModifier } from '@ohos.arkui.modifier';
3015
3016class MyModifier extends TextModifier {
3017  applyNormalAttribute(instance: TextModifier): void {
3018    super.applyNormalAttribute?.(instance);
3019  }
3020
3021  public setGroup1(): void {
3022    this.fontSize(50);
3023    this.fontColor(Color.Pink);
3024  }
3025
3026  public setGroup2(): void {
3027    this.fontSize(50);
3028    this.fontColor(Color.Gray);
3029  }
3030}
3031
3032@ComponentV2
3033struct MyImage1 {
3034  @Param @Require modifier: TextModifier;
3035  index: number = 0;
3036
3037  build() {
3038    Column() {
3039      Text('Test')
3040        .attributeModifier(this.modifier as MyModifier)
3041
3042      Button($r('app.string.EntryAbility_label'))
3043        .margin(10)
3044        .onClick(() => {
3045          console.log('Modifier', 'onClick');
3046          this.index++;
3047          if (this.index % 2 === 1) {
3048            (this.modifier as MyModifier).setGroup1();
3049            console.log('Modifier', 'setGroup1');
3050          } else {
3051            (this.modifier as MyModifier).setGroup2();
3052            console.log('Modifier', 'setGroup2');
3053          }
3054        })
3055    }
3056  }
3057}
3058
3059@Entry
3060@ComponentV2
3061struct Index {
3062  // Use the makeObserved capability to observe TextModifier.
3063  @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
3064  index: number = 0;
3065
3066  build() {
3067    Column() {
3068      MyImage1({ modifier: this.myModifier })
3069
3070      Button('replace whole')
3071        .margin(10)
3072        .onClick(() => {
3073          this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange));
3074        })
3075    }
3076    .width('100%')
3077  }
3078}
3079```
3080