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