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.Connect](./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 2278 Text(`bProp:: ${this.storage.bProp}`) 2279 .fontSize(30) 2280 .onClick(() => { 2281 // The page is not re-rendered, but the value of bProp is changed. 2282 this.storage.bProp++; 2283 }) 2284 2285 Button('save storage') 2286 .onClick(() => { 2287 // Different from V1, PersistenceV2 does not depend on the capability of observing state variables. You can perform persistence proactively. 2288 PersistenceV2.save(Storage); 2289 }) 2290 } 2291 } 2292} 2293``` 2294 2295## Existing Application Migration 2296 2297For 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. 2298 2299In this case, the parent components are of V1, and the migrated child component are of V2. Take the following components as an example: 2300- The parent component is \@Component, and the data source is \@LocalStorageLink. 2301- The child component is \@ComponentV2 and uses \@Param to receive data from the data source. 2302 2303In this case, the following policies can be used for migration: 2304- Declare a class decorated by \@ObservedV2 to encapsulate the data of V1. 2305- Define a custom component \@Component between \@Component and \@ComponentV2. 2306- At the bridging layer: 2307 - To synchronize data from V1 to V2, use the listening of \@Watch to trigger value changes of the class decorated by \@ObservedV2. 2308 - 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. 2309 2310Example: 2311``` 2312let storage: LocalStorage = new LocalStorage(); 2313 2314@ObservedV2 2315class V1StorageData { 2316 @Trace title: string = 'V1OldComponent' 2317 @Monitor('title') 2318 onStrChange(monitor: IMonitor) { 2319 monitor.dirty.forEach((path: string) => { 2320 console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`) 2321 if (path === 'title') { 2322 storage.setOrCreate('title', this.title); 2323 } 2324 }) 2325 } 2326} 2327let v1Data: V1StorageData = new V1StorageData(); 2328 2329@Entry(storage) 2330@Component 2331struct V1OldComponent { 2332 @LocalStorageLink('title') title: string = 'V1OldComponent'; 2333 2334 build() { 2335 Column() { 2336 Text(`V1OldComponent: ${this.title}`) 2337 .fontSize(20) 2338 .onClick(() => { 2339 this.title = 'new value from V1OldComponent'; 2340 }) 2341 Bridge() 2342 } 2343 } 2344} 2345 2346 2347@Component 2348struct Bridge { 2349 @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge'; 2350 titleWatch() { 2351 v1Data.title = this.title; 2352 } 2353 2354 build() { 2355 NewV2Component() 2356 } 2357} 2358@ComponentV2 2359struct NewV2Component { 2360 build() { 2361 Column() { 2362 Text(`NewV2Component: ${v1Data.title}`) 2363 .fontSize(20) 2364 .onClick(() => { 2365 v1Data.title = 'NewV2Component'; 2366 }) 2367 } 2368 } 2369} 2370``` 2371 2372## Other Migrations 2373 2374### Scrolling Components 2375 2376#### List 2377 2378You 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. 2379 2380V1: 2381 2382In V1, you can use [\@State](./arkts-state.md) to observe the API invoking. 2383 2384Example: 2385 2386```ts 2387@Entry 2388@Component 2389struct ListExample { 2390 private arr: Array<number> = new Array(10).fill(0); 2391 private scroller: ListScroller = new ListScroller(); 2392 @State listSpace: number = 10; 2393 @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100); 2394 2395 build() { 2396 Column() { 2397 Button('change Default').onClick(() => { 2398 this.listChildrenSize.childDefaultSize += 10; 2399 }) 2400 2401 Button('splice 5').onClick(() => { 2402 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2403 }) 2404 2405 Button('update 5').onClick(() => { 2406 this.listChildrenSize.update(0, 200); 2407 }) 2408 2409 List({ space: this.listSpace, scroller: this.scroller }) { 2410 ForEach(this.arr, (item: number) => { 2411 ListItem() { 2412 Text(`item-` + item) 2413 }.backgroundColor(Color.Pink) 2414 }) 2415 } 2416 .childrenMainSize(this.listChildrenSize) // 10 2417 } 2418 } 2419} 2420``` 2421 2422V2: 2423 2424In 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. 2425 2426Example: 2427 2428```ts 2429import { UIUtils } from '@kit.ArkUI'; 2430 2431@Entry 2432@ComponentV2 2433struct ListExample { 2434 private arr: Array<number> = new Array(10).fill(0); 2435 private scroller: ListScroller = new ListScroller(); 2436 listSpace: number = 10; 2437 // Use the makeObserved capability to observe ChildrenMainSize. 2438 listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100)); 2439 2440 build() { 2441 Column() { 2442 Button('change Default').onClick(() => { 2443 this.listChildrenSize.childDefaultSize += 10; 2444 }) 2445 2446 Button('splice 5').onClick(() => { 2447 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2448 }) 2449 2450 Button('update 5').onClick(() => { 2451 this.listChildrenSize.update(0, 200); 2452 }) 2453 2454 List({ space: this.listSpace, scroller: this.scroller }) { 2455 ForEach(this.arr, (item: number) => { 2456 ListItem() { 2457 Text(`item-` + item) 2458 }.backgroundColor(Color.Pink) 2459 }) 2460 } 2461 .childrenMainSize(this.listChildrenSize) // 10 2462 } 2463 } 2464} 2465``` 2466 2467#### WaterFlow 2468 2469You can use [WaterFlowSections](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12) to set the water flow item sections. 2470 2471Note 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. 2472 2473The following two examples shows buttons **push option**, **splice option**, and **update option** are clicked in sequence. 2474 2475V1: 2476 2477In V1, you can use [\@State](./arkts-state.md) to observe the API invoking. 2478 2479Example: 2480 2481```ts 2482@Entry 2483@Component 2484struct WaterFlowSample { 2485 @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2486 @State sections: WaterFlowSections = new WaterFlowSections(); 2487 scroller: Scroller = new Scroller(); 2488 @State private arr: Array<number> = new Array(9).fill(0); 2489 oneColumnSection: SectionOptions = { 2490 itemsCount: 4, 2491 crossCount: 1, 2492 columnsGap: '5vp', 2493 rowsGap: 10, 2494 }; 2495 twoColumnSection: SectionOptions = { 2496 itemsCount: 2, 2497 crossCount: 2, 2498 }; 2499 lastSection: SectionOptions = { 2500 itemsCount: 3, 2501 crossCount: 3, 2502 }; 2503 2504 aboutToAppear(): void { 2505 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2506 this.sections.splice(0, 0, sectionOptions); 2507 } 2508 2509 build() { 2510 Column() { 2511 Text(`${this.arr.length}`) 2512 2513 Button('push option').onClick(() => { 2514 let section: SectionOptions = { 2515 itemsCount: 1, 2516 crossCount: 1, 2517 }; 2518 this.sections.push(section); 2519 this.arr.push(100); 2520 }) 2521 2522 Button('splice option').onClick(() => { 2523 let section: SectionOptions = { 2524 itemsCount: 8, 2525 crossCount: 2, 2526 }; 2527 this.sections.splice(0, this.arr.length, [section]); 2528 this.arr = new Array(8).fill(10); 2529 }) 2530 2531 Button('update option').onClick(() => { 2532 let section: SectionOptions = { 2533 itemsCount: 8, 2534 crossCount: 2, 2535 }; 2536 this.sections.update(1, section); 2537 this.arr = new Array(16).fill(1); 2538 }) 2539 2540 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2541 ForEach(this.arr, (item: number) => { 2542 FlowItem() { 2543 Text(`${item}`) 2544 .border({ width: 1 }) 2545 .backgroundColor(this.colors[item % 6]) 2546 .height(30) 2547 .width(50) 2548 } 2549 }) 2550 } 2551 } 2552 } 2553} 2554``` 2555 2556V2: 2557 2558In 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. 2559 2560Example: 2561 2562```ts 2563import { UIUtils } from '@kit.ArkUI'; 2564 2565@Entry 2566@ComponentV2 2567struct WaterFlowSample { 2568 colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2569 // Use the makeObserved capability to observe WaterFlowSections. 2570 sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections()); 2571 scroller: Scroller = new Scroller(); 2572 @Local private arr: Array<number> = new Array(9).fill(0); 2573 oneColumnSection: SectionOptions = { 2574 itemsCount: 4, 2575 crossCount: 1, 2576 columnsGap: '5vp', 2577 rowsGap: 10, 2578 }; 2579 twoColumnSection: SectionOptions = { 2580 itemsCount: 2, 2581 crossCount: 2, 2582 }; 2583 lastSection: SectionOptions = { 2584 itemsCount: 3, 2585 crossCount: 3, 2586 }; 2587 2588 aboutToAppear(): void { 2589 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2590 this.sections.splice(0, 0, sectionOptions); 2591 } 2592 2593 build() { 2594 Column() { 2595 Text(`${this.arr.length}`) 2596 2597 Button('push option').onClick(() => { 2598 let section: SectionOptions = { 2599 itemsCount: 1, 2600 crossCount: 1, 2601 }; 2602 this.sections.push(section); 2603 this.arr.push(100); 2604 }) 2605 2606 Button('splice option').onClick(() => { 2607 let section: SectionOptions = { 2608 itemsCount: 8, 2609 crossCount: 2, 2610 }; 2611 this.sections.splice(0, this.arr.length, [section]); 2612 this.arr = new Array(8).fill(10); 2613 }) 2614 2615 Button('update option').onClick(() => { 2616 let section: SectionOptions = { 2617 itemsCount: 8, 2618 crossCount: 2, 2619 }; 2620 this.sections.update(1, section); 2621 this.arr = new Array(16).fill(1); 2622 }) 2623 2624 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2625 ForEach(this.arr, (item: number) => { 2626 FlowItem() { 2627 Text(`${item}`) 2628 .border({ width: 1 }) 2629 .backgroundColor(this.colors[item % 6]) 2630 .height(30) 2631 .width(50) 2632 } 2633 }) 2634 } 2635 } 2636 } 2637} 2638``` 2639 2640### Modifier 2641 2642#### attributeModifier 2643 2644You can use [attributeModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier) to dynamically set component attributes. 2645 2646V1: 2647 2648In V1, you can use [\@State](./arkts-state.md) to observe changes. 2649 2650Example: 2651 2652```ts 2653class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2654 isDark: boolean = false; 2655 2656 applyNormalAttribute(instance: ButtonAttribute): void { 2657 if (this.isDark) { 2658 instance.backgroundColor(Color.Black); 2659 } else { 2660 instance.backgroundColor(Color.Red); 2661 } 2662 } 2663} 2664 2665@Entry 2666@Component 2667struct AttributeDemo { 2668 @State modifier: MyButtonModifier = new MyButtonModifier(); 2669 2670 build() { 2671 Row() { 2672 Column() { 2673 Button('Button') 2674 .attributeModifier(this.modifier) 2675 .onClick(() => { 2676 this.modifier.isDark = !this.modifier.isDark; 2677 }) 2678 } 2679 .width('100%') 2680 } 2681 .height('100%') 2682 } 2683} 2684``` 2685 2686V2: 2687 2688In 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. 2689 2690Example: 2691 2692```ts 2693import { UIUtils } from '@kit.ArkUI'; 2694 2695class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2696 isDark: boolean = false; 2697 2698 applyNormalAttribute(instance: ButtonAttribute): void { 2699 if (this.isDark) { 2700 instance.backgroundColor(Color.Black); 2701 } else { 2702 instance.backgroundColor(Color.Red); 2703 } 2704 } 2705} 2706 2707@Entry 2708@ComponentV2 2709struct AttributeDemo { 2710 // Use the makeObserved capability to observe the this.modifier attribute of attributeModifier. 2711 modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier()); 2712 2713 build() { 2714 Row() { 2715 Column() { 2716 Button('Button') 2717 .attributeModifier(this.modifier) 2718 .onClick(() => { 2719 this.modifier.isDark = !this.modifier.isDark; 2720 }) 2721 } 2722 .width('100%') 2723 } 2724 .height('100%') 2725 } 2726} 2727``` 2728 2729#### CommonModifier 2730 2731Dynamically 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. 2732 2733V1: 2734 2735In V1, you can use [\@State](./arkts-state.md) to observe changes. 2736 2737Example: 2738 2739```ts 2740import { CommonModifier } from '@ohos.arkui.modifier'; 2741 2742class MyModifier extends CommonModifier { 2743 applyNormalAttribute(instance: CommonAttribute): void { 2744 super.applyNormalAttribute?.(instance); 2745 } 2746 2747 public setGroup1(): void { 2748 this.borderStyle(BorderStyle.Dotted); 2749 this.borderWidth(8); 2750 } 2751 2752 public setGroup2(): void { 2753 this.borderStyle(BorderStyle.Dashed); 2754 this.borderWidth(8); 2755 } 2756} 2757 2758@Component 2759struct MyImage1 { 2760 @Link modifier: CommonModifier; 2761 2762 build() { 2763 // '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. 2764 Image($r('app.media.app_icon')) 2765 .attributeModifier(this.modifier as MyModifier) 2766 } 2767} 2768 2769@Entry 2770@Component 2771struct Index { 2772 @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10); 2773 index: number = 0; 2774 2775 build() { 2776 Column() { 2777 Button($r('app.string.EntryAbility_label')) 2778 .margin(10) 2779 .onClick(() => { 2780 console.log('Modifier', 'onClick'); 2781 this.index++; 2782 if (this.index % 2 === 1) { 2783 (this.myModifier as MyModifier).setGroup1(); 2784 console.log('Modifier', 'setGroup1'); 2785 } else { 2786 (this.myModifier as MyModifier).setGroup2(); 2787 console.log('Modifier', 'setGroup2'); 2788 } 2789 }) 2790 2791 MyImage1({ modifier: this.myModifier }) 2792 } 2793 .width('100%') 2794 } 2795} 2796``` 2797 2798V2: 2799 2800In 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. 2801 2802Example: 2803 2804```ts 2805import { UIUtils } from '@kit.ArkUI'; 2806import { CommonModifier } from '@ohos.arkui.modifier'; 2807 2808class MyModifier extends CommonModifier { 2809 applyNormalAttribute(instance: CommonAttribute): void { 2810 super.applyNormalAttribute?.(instance); 2811 } 2812 2813 public setGroup1(): void { 2814 this.borderStyle(BorderStyle.Dotted); 2815 this.borderWidth(8); 2816 } 2817 2818 public setGroup2(): void { 2819 this.borderStyle(BorderStyle.Dashed); 2820 this.borderWidth(8); 2821 } 2822} 2823 2824@ComponentV2 2825struct MyImage1 { 2826 @Param @Require modifier: CommonModifier; 2827 2828 build() { 2829 // '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. 2830 Image($r('app.media.app_icon')) 2831 .attributeModifier(this.modifier as MyModifier) 2832 } 2833} 2834 2835@Entry 2836@ComponentV2 2837struct Index { 2838 // Use the makeObserved capability to observe CommonModifier. 2839 @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 2840 index: number = 0; 2841 2842 build() { 2843 Column() { 2844 Button($r('app.string.EntryAbility_label')) 2845 .margin(10) 2846 .onClick(() => { 2847 console.log('Modifier', 'onClick'); 2848 this.index++; 2849 if (this.index % 2 === 1) { 2850 (this.myModifier as MyModifier).setGroup1(); 2851 console.log('Modifier', 'setGroup1'); 2852 } else { 2853 (this.myModifier as MyModifier).setGroup2(); 2854 console.log('Modifier', 'setGroup2'); 2855 } 2856 }) 2857 2858 MyImage1({ modifier: this.myModifier }) 2859 } 2860 .width('100%') 2861 } 2862} 2863``` 2864 2865#### Component Modifier 2866 2867Dynamically sets attributes on the current component. The following uses the **Text** component as an example. 2868 2869V1: 2870 2871In V1, you can use [\@State](./arkts-state.md) to observe changes. 2872 2873Example: 2874 2875```ts 2876import { TextModifier } from '@ohos.arkui.modifier'; 2877 2878class MyModifier extends TextModifier { 2879 applyNormalAttribute(instance: TextModifier): void { 2880 super.applyNormalAttribute?.(instance); 2881 } 2882 2883 public setGroup1(): void { 2884 this.fontSize(50); 2885 this.fontColor(Color.Pink); 2886 } 2887 2888 public setGroup2(): void { 2889 this.fontSize(50); 2890 this.fontColor(Color.Gray); 2891 } 2892} 2893 2894@Component 2895struct MyImage1 { 2896 @Link modifier: TextModifier; 2897 index: number = 0; 2898 2899 build() { 2900 Column() { 2901 Text('Test') 2902 .attributeModifier(this.modifier as MyModifier) 2903 2904 Button($r('app.string.EntryAbility_label')) 2905 .margin(10) 2906 .onClick(() => { 2907 console.log('Modifier', 'onClick'); 2908 this.index++; 2909 if (this.index % 2 === 1) { 2910 (this.modifier as MyModifier).setGroup1(); 2911 console.log('Modifier', 'setGroup1'); 2912 } else { 2913 (this.modifier as MyModifier).setGroup2(); 2914 console.log('Modifier', 'setGroup2'); 2915 } 2916 }) 2917 } 2918 } 2919} 2920 2921@Entry 2922@Component 2923struct Index { 2924 @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10); 2925 index: number = 0; 2926 2927 build() { 2928 Column() { 2929 MyImage1({ modifier: this.myModifier }) 2930 2931 Button('replace whole') 2932 .margin(10) 2933 .onClick(() => { 2934 this.myModifier = new MyModifier().backgroundColor(Color.Orange); 2935 }) 2936 } 2937 .width('100%') 2938 } 2939} 2940``` 2941 2942V2: 2943 2944In 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. 2945 2946Example: 2947 2948```ts 2949import { UIUtils } from '@kit.ArkUI'; 2950import { TextModifier } from '@ohos.arkui.modifier'; 2951 2952class MyModifier extends TextModifier { 2953 applyNormalAttribute(instance: TextModifier): void { 2954 super.applyNormalAttribute?.(instance); 2955 } 2956 2957 public setGroup1(): void { 2958 this.fontSize(50); 2959 this.fontColor(Color.Pink); 2960 } 2961 2962 public setGroup2(): void { 2963 this.fontSize(50); 2964 this.fontColor(Color.Gray); 2965 } 2966} 2967 2968@ComponentV2 2969struct MyImage1 { 2970 @Param @Require modifier: TextModifier; 2971 index: number = 0; 2972 2973 build() { 2974 Column() { 2975 Text('Test') 2976 .attributeModifier(this.modifier as MyModifier) 2977 2978 Button($r('app.string.EntryAbility_label')) 2979 .margin(10) 2980 .onClick(() => { 2981 console.log('Modifier', 'onClick'); 2982 this.index++; 2983 if (this.index % 2 === 1) { 2984 (this.modifier as MyModifier).setGroup1(); 2985 console.log('Modifier', 'setGroup1'); 2986 } else { 2987 (this.modifier as MyModifier).setGroup2(); 2988 console.log('Modifier', 'setGroup2'); 2989 } 2990 }) 2991 } 2992 } 2993} 2994 2995@Entry 2996@ComponentV2 2997struct Index { 2998 // Use the makeObserved capability to observe TextModifier. 2999 @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 3000 index: number = 0; 3001 3002 build() { 3003 Column() { 3004 MyImage1({ modifier: this.myModifier }) 3005 3006 Button('replace whole') 3007 .margin(10) 3008 .onClick(() => { 3009 this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange)); 3010 }) 3011 } 3012 .width('100%') 3013 } 3014} 3015``` 3016