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