1# V1->V2迁移指导 2 3## 概述 4ArkUI状态管理的主要职责是:负责将可观察数据的变化自动同步到UI界面,实现数据驱动的UI刷新,使开发者能更加够专注于UI界面的实现和设计。 5 6在状态管理框架的演进过程中,先后推出了状态管理V1和V2两个版本。V1强调组件层级的状态管理,而V2则增强了对数据对象的深度观察与管理能力,不再局限于组件层级。通过V2,开发者能够更灵活地控制数据和状态,实现更高效的UI刷新。具体V1和V2的区别可以参见[状态管理概述](./arkts-state-management-overview.md)。 7 8## V1V2使用指引 91. V2是V1的增强版本,为开发者提供更多功能和灵活性。 102. 对于新开发的应用,建议直接使用V2版本范式来进行开发。 113. 对于已经使用V1的应用,如果V1的功能和性能已能满足需求,则不必立即切换到V2。如果开发者在开发过程中受限于V1不能深度观察等特性,则建议开发者尽早规划向V2的迁移,以便未来实现平滑过渡和改进。 124. 对于需要在现阶段混用V1和V2的场景,请参阅[混用文档](./arkts-custom-component-mixed-scenarios.md)。编译器、工具链、IDE对某些不推荐的误用和混用场景会进行校验,虽然开发者可能可以通过特殊手段绕过这些校验,但还是强烈建议开发者遵循[混用文档](./arkts-custom-component-mixed-scenarios.md)的指导,避免因双重代理等问题给应用带来不确定性。 13 14## 迁移指南的目的 151. 对希望将现有V1应用迁移到V2的开发者,提供系统化的模板和指导,帮助完成V1到V2的迁移。 162. 对希望逐步将V1应用过渡到V2的开发者,提供参考,结合本迁移文档与[混用文档](./arkts-custom-component-mixed-scenarios.md),可以帮助开发者实现逐步改造。 173. 尚未开始开发应用但已熟悉V1状态管理规则的开发者,可以参考本迁移文档及V2各个装饰器和接口的文档,开始使用V2进行应用开发。 18 19## V1V2能力对比及迁移简表 20| V1装饰器名 | V2装饰器名 | 说明 | 21|------------------------|--------------------------|--------------------------| 22| \@Observed | \@ObservedV2 | 表明当前对象为可观察对象。但两者能力并不相同。 <br/>\@Observed可观察第一层的属性,需要搭配\@ObjectLink使用才能生效。 <br/>\@ObservedV2本身无观察能力,仅代表当前class可被观察,如果要观察其属性,需要搭配\@Trace使用。 | 23| \@Track | \@Trace | V1装饰器\@Track为精确观察,不使用则无法做到类属性的精准观察。 <br/>V2\@Trace装饰的属性可以被精确跟踪观察。| 24| \@Component | \@ComponentV2 | \@Component为搭配V1状态变量使用的自定义组件装饰器。<br/>@ComponentV2为搭配V2状态变量使用的自定义组件装饰器。 | 25|\@State | 无外部初始化:@Local<br/>外部初始化一次:\@Param\@Once | \@State和\@Local类似都是数据源的概念,在不需要外部传入初始化时,可直接迁移。如果需要外部传入初始化,则可以迁移为\@Param\@Once,详情见[@State->@Local](#state-local)。 | 26| \@Prop | \@Param | \@Prop和\@Param类似都是自定义组件参数的概念。当输入参数为复杂类型时,\@Prop为深拷贝,\@Param为引用。 | 27| \@Link | \@Param\@Event | \@Link是框架自己封装实现的双向同步,对于V2开发者可以通过@Param@Event自己实现双向同步。 | 28| \@ObjectLink | \@Param | 直接兼容,\@ObjectLink需要被@Observed装饰的class的实例初始化,\@Param没有此限制。 | 29| \@Provide | \@Provider | 兼容。 | 30| \@Consume | \@Consumer | 兼容。 | 31| \@Watch | \@Monitor | \@Watch用于监听V1状态变量的变化,具有监听状态变量本身和其第一层属性变化的能力。状态变量可观察到的变化会触发其\@Watch监听事件。<br/>\@Monitor用于监听V2状态变量的变化,搭配\@Trace使用,可有深层监听的能力。状态变量在一次事件中多次变化时,仅会以最终的结果判断是否触发\@Monitor监听事件。 | 32| LocalStorage | 全局\@ObservedV2\@Trace | 兼容。 | 33| AppStorage | AppStorageV2 | 兼容。 | 34| Environment | 调用Ability接口获取系统环境变量 | Environment获取环境变量能力和AppStorage耦合。在V2中可直接调用Ability接口获取系统环境变量。 | 35| PersistentStorage | PersistenceV2 | PersistentStorage持久化能力和AppStorage耦合,PersistenceV2持久化能力可独立使用。 | 36 37## 各装饰器迁移示例 38 39### @State->@Local 40 41#### 迁移规则 42在V1中,\@State装饰器用于装饰组件内部的状态变量,在V2中提供了\@Local作为其替代能力,但两者在观察能力和初始化规则上存在明显差异。针对不同的使用场景,迁移策略如下: 43 44- 简单类型:对于简单类型的变量,可以直接将\@State替换为\@Local。 45- 复杂类型:V1中的@State可以观察复杂对象的第一层属性变化,而V2中的\@Local只能观察对象自身的变化。如果需要追踪对象内部的属性变化,可以结合使用\@ObservedV2和\@Trace。 46- 外部初始化:V1中,\@State支持从外部传递初始值,但在V2中,\@Local禁止外部初始化。若需要从外部传递初始值,可以使用\@Param和\@Once装饰器来实现类似的效果。 47 48#### 示例 49 50**简单类型** 51 52对于简单类型变量,V1的@State可以直接替换为替换为V2的@Local。 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**复杂类型** 81 82V1的@State能够观察复杂对象的第一层属性变化,但V2的@Local无法观察对象内部变化。为了解决这个问题,需要在类上添加@ObservedV2,并在需要观察的属性上添加@Trace。这样,框架就能追踪对象内部的属性变化。 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可以观察第一层变化 99 Button('value+1') 100 .onClick(() => { 101 this.child.value++; 102 }) 103 } 104 } 105} 106``` 107 108V2迁移策略:使用@ObservedV2和@Trace。 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只能观察自身,需要给Child加上@ObservedV2和@Trace 124 Button('value+1') 125 .onClick(() => { 126 this.child.value++; 127 }) 128 } 129 } 130} 131``` 132 133**外部初始化状态变量** 134 135V1的@State变量可以从外部初始化,V2的@Local禁止外部初始化。为实现类似功能,需要用@Param和@Once代替@State,允许外部传入初始值,并确保该值只初始化时同步一次。 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可以从外部初始化 154 Child({ value: 30 }) 155 } 156 } 157} 158``` 159 160V2迁移策略:使用@Param和@Once。 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禁止从外部初始化,可以用@Param和@Once替代实现 177 Child({ value: 30 }) 178 } 179 } 180} 181``` 182 183### @Link -> @Param/@Event 184 185#### 迁移规则 186在V1中,@Link允许父组件和子组件之间进行双向数据绑定。迁移到V2时,可以用@Param和@Event模拟双向同步。@Param实现父到子的单向传递,子组件再通过@Event回调函数触发父组件的状态更新。 187 188#### 示例 189 190V1实现: 191 192```ts 193@Component 194struct Child { 195 // @Link可以双向同步数据 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迁移策略:使用@Param和@Event 222 223```ts 224@ComponentV2 225struct Child { 226 // @Param搭配@Event回调实现数据双向同步 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#### 迁移规则 256在V1中,@Prop装饰器用于从父组件传递参数给子组件,这些参数在子组件中可以被直接修改。在V2中,@Param取代了@Prop的作用,但@Param是只读的,子组件不能直接修改参数的值。因此,根据场景的不同,有几种迁移策略: 257 258- 简单类型:对于简单类型的参数,可以直接将@Prop替换@Param。 259- 复杂类型:如果传递的是复杂对象且需要严格的单向数据绑定,可以对对象进行深拷贝,防止子组件修改父组件的数据。 260- 子组件修改变量:如果子组件需要修改传入的参数,可以使用@Once来允许子组件对在本地修改该变量。但需要注意,如果使用了\@Once,则代表当前子组件只会被初始化一次,后续并没有父组件到子组件的同步能力。 261 262#### 示例 263 264**简单类型** 265 266对于简单类型变量,V1的@Prop可以直接替换为V2的@Param。 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**复杂类型的单向数据传递** 312 313在V2中,传递复杂类型时,如果希望实现严格的单向数据绑定,防止子组件修改父组件的数据,需要在使用@Param传递复杂对象时进行深拷贝以避免传递对象的引用。 314 315V1实现: 316 317```ts 318class Fruit { 319 apple: number = 5; 320 orange: number = 10; 321} 322 323@Component 324struct Child { 325 // @Prop传递Fruit类,当子类修改属性,父类不受影响 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 // 实现深拷贝,子组件不会修改父组件的数据 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**子组件修改变量** 409 410在V1中,子组件可以修改@Prop的变量,然而在V2中,@Param是只读的。如果子组件需要修改传入的值,可以使用@Param和@Once允许子组件在本地修改。 411 412V1实现: 413 414```ts 415@Component 416struct Child { 417 // @Prop可以直接修改变量值 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迁移策略:使用@Param和@Once 442 443```ts 444@ComponentV2 445struct Child { 446 // @Param搭配@Once使用,可以在本地修改@Param变量 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 470在V1中,子组件可以修改\@Prop的变量,且只会在本地更新,不会同步回父组件。父组件数据源更新时,会通知子组件更新,并覆写子组件本地\@Prop的值。 471 472V1: 473- 改变子组件`Child`的`localValue`,不会同步回父组件`Parent`。 474- 父组件更新`value`,通知子组件`Child`更新,并覆写本地子组件`localValue`的值。 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 // 改变localValue不会传递给父组件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 // 改变value的值,通知子组件Child value更新 502 this.value += 1; 503 }) 504 Child({ localValue: this.value }) 505 } 506 } 507} 508``` 509V2中,\@Param本地不可写,和\@Once搭配使用只会同步一次。如果要实现子组件本地可写,且父组件后续更新还是能通知子组件,可以借助\@Monitor来实现这一效果。 510 511V2实现: 512- 父组件`Parent`更新通知子组件`value`的刷新,并回调\@Monitor修饰的`onValueChange`回调方法,`onValueChange`将更新后的值赋值给`localValue`。 513- 子组件`Child`改变`localValue`的值,不会同步给父组件`Parent`。 514- 父组件`Parent`中再次改变`value`,将会继续通知给子组件,并覆写子组件本地`localValue`的值。 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 // 父组件value变化时,通知子组件value更新,回调Monitor函数,将更新的值覆写给本地的localValue 525 this.localValue = this.value; 526 } 527 528 build() { 529 Column() { 530 Text(`${this.localValue}`).fontSize(25) 531 Button('Child +100') 532 .onClick(() => { 533 // 改变localValue不会传递给父组件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 // 改变value的值,通知子组件Child value更新 549 this.value += 1; 550 }) 551 Child({ value: this.value }) 552 } 553 } 554} 555``` 556 557### @ObjectLink/@Observed/@Track -> @ObservedV2/@Trace 558#### 迁移规则 559在V1中,@Observed与@ObjectLink装饰器用于观察类对象及其嵌套属性的变化,但V1只能直接观察对象的第一层属性。对于嵌套对象的属性,必须通过自定义组件和@ObjectLink实现观察。此外,V1中提供了@Track装饰器来实现对属性级别变化的精确控制。 560 561在V2中,@ObservedV2与@Trace结合使用,可以高效地实现类对象及其嵌套属性的深度观察,省去了对自定义组件的依赖,简化了开发流程。同时,@Trace装饰器还具备精确更新的能力,替代了V1中的@Track,从而实现更高效的UI刷新控制。根据不同的场景,有以下迁移策略: 562 563- 嵌套对象的属性观察:V1中需要通过自定义组件和@ObjectLink观察嵌套属性,V2中则可以使用@ObservedV2和@Trace直接观察嵌套对象,简化了代码结构。 564- 类属性的精确更新:V1中的@Track可以用V2中的@Trace取代,@Trace可以同时观察和精确更新属性变化,使代码更简洁高效。 565 566#### 示例 567**嵌套对象属性观察方法** 568 569在V1中,无法直接观察嵌套对象的属性变化,只能观察到第一层属性的变化。必须通过创建自定义组件并使用@ObjectLink来实现对嵌套属性的观察。V2中使用@ObservedV2和@Trace,可以直接对嵌套对象的属性进行深度观察,减少复杂度。 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 // 子组件中@ObjectLink装饰的address从父组件初始化,接收被@Observed装饰的Address实例 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 // 无法直接观察嵌套对象的属性变化,例如this.user.address.city 619 // 只能观察到对象第一层属性变化,所以需要将嵌套的对象Address抽取到自定义组件AddressView 620 AddressView({ address: this.user.address }) 621 } 622 } 623} 624``` 625 626V2迁移策略:使用@ObservedV2和@Trace 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 // 通过@ObservedV2和@Trace可以直接观察嵌套属性 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**类属性变化观测** 668 669在V1中,@Observed用于观察类实例及其属性的变化,@Track则用于对属性级别的变化优化,使得只有被@Track装饰的属性触发UI更新。在V2中,@Trace结合了观察和更新属性级别变化的能力,搭配@ObservedV2实现高效的UI更新。 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迁移策略:使用@ObservedV2和@Trace 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#### 迁移规则 737V1的@Provide/@Consume和V2@Provider/@Consumer定位和作用大体类似,基本可以实现丝滑替换,但是有以下细微差距,开发者可根据自己代码实现来参考是否需要调整: 738在V1中,@Provide和@Consume用于父子组件之间的数据共享,可以通过alias(别名)或属性名匹配,同时@Consume必须依赖父组件的@Provide,不允许本地初始化。而V2中,@Provider和@Consumer增强了这些特性,使数据共享更加灵活。根据不同的场景,有以下迁移策略: 739 740- V1中\@Provide/\@Consume在没有指定alias的情况下,可以直接使用。V2中\@Provider/\@Consumer是标准装饰器,且参数可选,所以不管有无指定alias后面需要必须跟随“()”。 741- alias和属性名匹配规则:V1中,@Provide和@Consume可以通过alias或属性名匹配;V2中,alias是唯一的匹配key,指定alias后只能通过alias匹配。 742- 本地初始化支持:V1中,@Consume不允许本地初始化,必须依赖父组件;V2中,@Consumer支持本地初始化,当找不到对应的@Provider时使用本地默认值。 743- 从父组件初始化:V1中,@Provide可以直接从父组件初始化;V2中,@Provider不支持外部初始化,需用@Param和@Once接受初始值并赋给 @Provider。 744- 重载支持:V1中,@Provide默认不支持重载,需设置 allowOverride;V2中,@Provider默认支持重载,@Consumer会向上查找最近的@Provider。 745#### 示例 746**alias和属性名匹配规则** 747 748在V1中,@Provide和@Consume的匹配既可以通过alias,也可以通过属性名。在V2中,alias成为唯一的key,如果在@Consumer中制定了alias,只能通过alias而非属性名进行匹配。 749 750V1实现: 751 752```ts 753@Component 754struct Child { 755 // alias和属性名都为key,alias和属性名都可以匹配 756 @Consume('text') childMessage: string; 757 @Consume message: string; 758 build(){ 759 Column(){ 760 Text(this.childMessage) 761 Text(this.message) // Text是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迁移策略:确保alias一致,没有指定alias的情况下,依赖属性名进行匹配 779 780```ts 781@ComponentV2 782struct Child { 783 // alias是唯一匹配的key,有alias情况下无法通过属性名匹配 784 @Consumer('text') childMessage: string = "default"; 785 @Consumer() message: string = "default"; 786 build(){ 787 Column(){ 788 Text(this.childMessage) 789 Text(this.message) // Text是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**V1的@Consume不支持本地初始化,V2支持** 807 808V1中,@Consume不允许本地初始化变量,必须依赖父组件的@Provide,否则会抛出异常。迁移到V2后,@Consumer允许本地初始化,当找不到对应的@Provider,会使用本地默认值。 809 810V1实现: 811 812```ts 813@Component 814struct Child { 815 // @Consume禁止本地初始化,当找不到对应的@Provide时抛出异常 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迁移策略:@Consumer可以本地初始化 835 836```ts 837@ComponentV2 838struct Child { 839 // @Consumer允许本地初始化,当找不到@Provider的时候使用本地默认值 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**V1的@Provide可以从父组件初始化,V2不支持** 858 859在V1中,@Provide允许从父组件初始化,可以直接通过组件参数传递初始值。在V2中,@Provider禁止从外部初始化。为实现相同功能,可以在子组件中使用@Param @Once接受初始值,然后将其赋值给@Provider变量。 860 861V1实现: 862 863```ts 864@Entry 865@Component 866struct Parent { 867 @State parentValue: number = 42; 868 build() { 869 Column() { 870 // @Provide可以从父组件初始化 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迁移策略:使用@Param接受初始值,再赋值给@Provider 888 889```ts 890@Entry 891@ComponentV2 892struct Parent { 893 @Local parentValue: number = 42; 894 build() { 895 Column() { 896 // @Provider禁止从父组件初始化,替代方案为先用@Param接受,再赋值给@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**V1的@Provide默认不支持重载,V2默认支持** 915 916在V1中,@Provide默认不支持重载,无法覆盖上层组件的同名@Provide。若需支持重载,必须设置allowOverride。在V2中,@Provider默认支持重载,@Consumer会向上查找最近的@Provider,无需额外设置。 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默认不支持重载,支持重载需设置allowOverride函数 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()) // Text显示20 946 } 947} 948``` 949 950V2迁移策略:去掉allowOverride 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默认支持重载,@Consumer向上查找最近的@Provider 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()) // Text显示20 978 } 979} 980``` 981 982### @Watch -> @Monitor 983#### 迁移规则 984在V1中,\@Watch用于监听状态变量的变化,并在变量变化时触发指定回调函数。在V2中,\@Monitor替代了\@Watch,可以更灵活地监听变量的变化,并获取变量变化前后的值。具体的迁移策略如下: 985 986- 单变量监听:对于简单的场景,可以直接用@Monitor替换@Watch,效果一致。 987- 多变量监听:V1的@Watch无法获取变化前的值。在V2中,\@Monitor支持同时监听多个变量,并可以访问变量变化前后的状态。 988#### 示例 989**单变量监听** 990 991对于简单案例,V1的@Watch可以直接替换为替换为V2的@Monitor。 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**多变量监听** 1041 1042在V1中,每个@Watch回调函数只能监听一个变量,且无法获取变化前的值。迁移到V2后,可以使用一个@Monitor同时监听多个变量以及获取监听变量的变化前后的值。 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 回调,只能监听单个变量,不能获取变化前的值 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回调,支持监听多个变量,可以获取变化前的值 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#### 迁移规则 1112V1中并没有提供计算属性的概念,所以对于UI中的冗余计算,并没有办法可以减少重复计算。V2针对该场景,提供了@Computed装饰器,可以帮助开发者减少重复计算。 1113 1114V1: 1115在下面的例子中,每次改变`lastName`都会触发Text组件的刷新,每次Text组件的刷新,都需要重复计算`this.lastName + ' ' + this.firstName`。 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: 1137使用V2中的\@Computed,每次改变`lastName`仅会触发一次计算。 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->全局@ObservedV2/@Trace 1163#### 迁移规则 1164LocalStorage的目的是为了实现页面间的状态变量共享。之所以提供这个能力,是因为V1状态变量和View层耦合,无法由开发者自主地实现页面间状态变量的共享。 1165对于状态管理V2,状态变量的观察能力内嵌到数据本身,不再和View层耦合,所以对于状态管理V2,不再需要类似LocalStorage的能力,可以使用全局@ObservedV2/@Trace,由开发者自己import和export,自己实现状态变量的页面间共享。 1166 1167#### 示例 1168**基本场景** 1169 1170V1: 1171通过windowStage.[loadContent](../reference/apis-arkui/js-apis-window.md#loadcontent9)和[getShared](../reference/apis-arkui/arkui-ts/ts-state-management.md#getshared10)接口实现页面间的状态变量共享。 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``` 1186在下面的示例中,使用\@LocalStorageLink,可以使得开发者本地的修改同步回LocalStorage中。 1187 1188``` 1189// Page1.ets 1190// 通过getShared接口获取stage共享的LocalStorage实例 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// Page2组件获得了父亲Page1组件的LocalStorage实例 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``` 1242使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。 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- 声明\@ObservedV2装饰的MyStorage类,并import需要使用的页面中。 1259- 声明被\@Trace的属性作为页面间共享的可观察的数据。 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``` 1332使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。 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 1348如果开发者需要实现类似于\@LocalStorageProp的效果,希望本地的修改不要同步回LocalStorage中,如以下示例: 1349- 在`Page1`中改变`count`值,因为count是\@LocalStorageProp装饰的,所以其改变只会在本地生效,并不会同步回LocalStorage。 1350- 点击`push to Page2`,跳转到`Page2`中。因为在`Page1`中改变`count`值并不会同步会LocalStorage,所以在`Page2`中Text组件依旧显示原本的值47。 1351- 点击`change Storage Count`,调用LocalStorage的setOrCreate,改变`count`对应的值,并通知所有绑定该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// Page2组件获得了父亲Page1组件的LocalStorage实例 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``` 1418在V2中,可以借助\@Local和\@Monitor实现类似的效果。 1419- \@Local装饰的`count`变量为组件本地的值,其改变不会同步回`storage`。 1420- \@Monitor监听`storage.count`的变化,当`storage.count`改变时,在\@Monitor的回调里改变本地\@Local的值。 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**自定义组件接收LocalStorage实例场景** 1502 1503为了配合Navigation的场景,LocalStorage支持作为自定义组件的入参,传递给以当前自定义组件为根节点的所有子自定义组件。 1504对于该场景,V2可以采用多个全局\@ObservedV2/\@Trace实例来替代。 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 // 传递不同的LocalStorage实例 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' }); //将name指定的NavDestination页面信息入栈 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 // 显示'PropA' 1563 NavigationContentMsgStack() 1564 // 显示'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 // 显示'Hello',当前LocalStorage实例localStorageB没有PropA对应的值,使用本地默认值'Hello' 1591 NavigationContentMsgStack() 1592 // 显示'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 // 显示'Hello',当前LocalStorage实例localStorageC没有PropA对应的值,使用本地默认值'Hello' 1620 NavigationContentMsgStack() 1621 // 显示'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 1655声明\@ObservedV2装饰的class代替LocalStorage。其中LocalStorage的key可以用\@Trace装饰的属性代替。 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 1685在`pageOneStack`、`pageTwoStack`和`pageThreeStack`组件内分别创建`MyStorageA`、`MyStorageB`、`MyStorageC`的实例,并通过\@Param传递给其子组件`NavigationContentMsgStack`,从而实现类似LocalStorage实例在子组件树上共享的能力。 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' }); //将name指定的NavDestination页面信息入栈 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 // 显示'PropA' 1736 NavigationContentMsgStack({storage: this.storageA}) 1737 // 显示'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 // 显示'Hello' 1767 NavigationContentMsgStack({ storage: this.storageB }) 1768 // 显示'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 // 显示'Hello' 1799 NavigationContentMsgStack({ storage: this.storageC }) 1800 // 显示'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 1837上一小节中,对于全局的@ObserveV2/@Trace的改造并不适合跨Ability的数据共享,该场景可以使用AppStorageV2来替换。 1838 1839V1: 1840AppStorage是和应用进程绑定了,可以跨Ability实现数据共享。 1841在下面的示例中,使用\@StorageLink,可以使得开发者本地的修改同步回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', // 替换成AppScope/app.json5里的bundleName 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', // 替换成AppScope/app.json5里的bundleName 1888 abilityName: 'EntryAbility' 1889 }; 1890 this.context.startAbility(wantInfo); 1891 }) 1892 } 1893 } 1894} 1895``` 1896V2: 1897可以使用AppStorageV2实现跨Ability共享。 1898如下面示例: 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', // 替换成AppScope/app.json5里的bundleName 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', // 替换成AppScope/app.json5里的bundleName 1958 abilityName: 'EntryAbility' 1959 }; 1960 this.context.startAbility(wantInfo); 1961 }) 1962 } 1963 } 1964} 1965``` 1966 1967如果开发者需要实现类似于\@StorageProp的效果,希望本地的修改不要同步回AppStorage中,而AppStorage的变化又可以通知给使用\@StorageProp装饰器的组件,可以参考以下示例对比。 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', // 替换成AppScope/app.json5里的bundleName 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', // 替换成AppScope/app.json5里的bundleName 2024 abilityName: 'EntryAbility' 2025 }; 2026 this.context.startAbility(wantInfo); 2027 }) 2028 } 2029 } 2030} 2031``` 2032 2033V2: 2034开发者可以借助\@Monitor和\@Local来实现类似的效果,示例如下。 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', // 替换成AppScope/app.json5里的bundleName 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', // 替换成AppScope/app.json5里的bundleName 2116 abilityName: 'EntryAbility' 2117 }; 2118 this.context.startAbility(wantInfo); 2119 }) 2120 } 2121 } 2122} 2123``` 2124 2125### Environment->调用Ability接口直接获取系统环境变量 2126V1中,开发者可以通过Environment来获取环境变量,但Environment获取的结果无法直接使用,需要配合AppStorage才能得到对应环境变量的值。 2127在切换V2的过程中,开发者无需再通过Environment来获取环境变量,可以直接通过[UIAbilityContext的config属性](../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#属性)获取系统环境变量。 2128V1: 2129以`languageCode`为例。 2130```ts 2131// 将设备languageCode存入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 // 输出当前设备的languageCode 2142 Text(this.languageCode) 2143 } 2144 } 2145 } 2146} 2147``` 2148 2149V2: 2150封装Env类型来传递多个系统环境变量。 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``` 2165在`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``` 2186在页面中获得当前Env的值。 2187``` 2188// Index.ets 2189import { env } from '../pages/Env'; 2190 2191@Entry 2192@ComponentV2 2193struct Index { 2194 build() { 2195 Row() { 2196 Column() { 2197 // 输出当前设备的环境变量 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 2209V1中PersistentStorage提供了持久化UI数据的能力,而V2则提供了更加方便使用的PersistenceV2接口来替代它。 2210- PersistentStorage持久化的触发时机依赖AppStorage的观察能力,且与AppStorage耦合,开发者无法自主选择写入或读取持久化数据的时机。 2211- PersistentStorage使用序列化和反序列化,并没有传入类型,所以在持久化后,会丢失其类型,且对象的属性方法不能持久化。 2212 2213对于PersistenceV2: 2214- 与PersistenceV2关联的\@ObservedV2对象,其\@Trace属性的变化,会触发整个关联对象的自动持久化。 2215- 开发者也可以调用[PersistenceV2.save](./arkts-new-persistencev2.md#save手动持久化数据)和[PersistenceV2.connect](./arkts-new-persistencev2.md#connect创建或获取储存的数据)接口来手动触发持久化写入和读取。 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 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2231 Text(`${this.aProp}`) 2232 .onClick(() => { 2233 this.aProp += 1; 2234 }) 2235 } 2236 } 2237 } 2238} 2239``` 2240 2241V2: 2242 2243下面的案例展示了: 2244- 对标V1的`PersistentStorage`能力:`aProp`的改变自动触发`PersistenceV2`的持久化。 2245- 对比V1的`PersistentStorage`能力增强:`bProp`是非状态变量,其变化不能被观察和监听,但是开发者仍然可以主动调用[PersistenceV2.save](./arkts-new-persistencev2.md#save手动持久化数据)接口,进行持久化。 2246 - 点击`aProp`,UI刷新。 2247 - 点击`bProp`,UI没有刷新。 2248 - 点击`save storage`,触发`PersistentStorage`链接数据的落盘。 2249 - 退出重启应用,Text组件显示的`aProp`和`bProp`为上次改变的值。 2250``` 2251import { PersistenceV2 } from '@kit.ArkUI'; 2252// 数据中心 2253@ObservedV2 2254class Storage { 2255 @Trace aProp: number = 0; 2256 bProp: number = 10; 2257} 2258 2259// 接受序列化失败的回调 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 // 在PersistenceV2中创建一个key为Sample的键值对(如果存在,则返回PersistenceV2中的数据),并且和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 // 页面不刷新,但是bProp的值改变了 2282 this.storage.bProp++; 2283 }) 2284 2285 Button('save storage') 2286 .onClick(() => { 2287 // 和V1不同,PersistenceV2不依赖状态变量的观察能力,开发者可以主动持久化 2288 PersistenceV2.save(Storage); 2289 }) 2290 } 2291 } 2292} 2293``` 2294 2295## 存量迁移场景 2296对于已经使用V1开发的大型应用,一般不太可能做到一次性的从V1迁移到V2,而是分批次和分组件的部分迁移,这就必然会带来V1和V2的混用。 2297 2298这种场景,一般是父组件是状态管理V1,而迁移的子组件为状态管理V2。为了模拟这种场景,我们举出下面的示例: 2299- 父组件是\@Component,数据源是\@LocalStorageLink。 2300- 子组件是\@ComponentV2,使用\@Param接受数据源的数据。 2301 2302这种情况,我们可以通过以下策略进行迁移: 2303- 声明一个\@ObservedV2装饰的class来封装V1的数据。 2304- 在\@Component和\@ComponentV2之间,定义一个桥接的\@Component自定义组件。 2305- 在桥接层: 2306 - V1->V2的数据同步,可通过\@Watch的监听触发\@ObservedV2装饰的class的属性的赋值。 2307 - V2->V1的数据同步,可通过在\@ObservedV2装饰的class里声明Monitor,通过LocalStorage的API反向通知给V1状态变量。 2308 2309具体示例如下: 2310``` 2311let storage: LocalStorage = new LocalStorage(); 2312 2313@ObservedV2 2314class V1StorageData { 2315 @Trace title: string = 'V1OldComponent' 2316 @Monitor('title') 2317 onStrChange(monitor: IMonitor) { 2318 monitor.dirty.forEach((path: string) => { 2319 console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`) 2320 if (path === 'title') { 2321 storage.setOrCreate('title', this.title); 2322 } 2323 }) 2324 } 2325} 2326let v1Data: V1StorageData = new V1StorageData(); 2327 2328@Entry(storage) 2329@Component 2330struct V1OldComponent { 2331 @LocalStorageLink('title') title: string = 'V1OldComponent'; 2332 2333 build() { 2334 Column() { 2335 Text(`V1OldComponent: ${this.title}`) 2336 .fontSize(20) 2337 .onClick(() => { 2338 this.title = 'new value from V1OldComponent'; 2339 }) 2340 Bridge() 2341 } 2342 } 2343} 2344 2345 2346@Component 2347struct Bridge { 2348 @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge'; 2349 titleWatch() { 2350 v1Data.title = this.title; 2351 } 2352 2353 build() { 2354 NewV2Component() 2355 } 2356} 2357@ComponentV2 2358struct NewV2Component { 2359 build() { 2360 Column() { 2361 Text(`NewV2Component: ${v1Data.title}`) 2362 .fontSize(20) 2363 .onClick(() => { 2364 v1Data.title = 'NewV2Component'; 2365 }) 2366 } 2367 } 2368} 2369``` 2370 2371## 其他迁移场景 2372 2373### 滑动组件 2374 2375#### List 2376 2377开发者可以通过[ChildrenMainSize](../reference/apis-arkui/arkui-ts/ts-container-list.md#childrenmainsize12)来设置List的子组件在主轴方向的大小信息。 2378 2379V1: 2380 2381在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 2382 2383具体示例如下: 2384 2385```ts 2386@Entry 2387@Component 2388struct ListExample { 2389 private arr: Array<number> = new Array(10).fill(0); 2390 private scroller: ListScroller = new ListScroller(); 2391 @State listSpace: number = 10; 2392 @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100); 2393 2394 build() { 2395 Column() { 2396 Button('change Default').onClick(() => { 2397 this.listChildrenSize.childDefaultSize += 10; 2398 }) 2399 2400 Button('splice 5').onClick(() => { 2401 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2402 }) 2403 2404 Button('update 5').onClick(() => { 2405 this.listChildrenSize.update(0, 200); 2406 }) 2407 2408 List({ space: this.listSpace, scroller: this.scroller }) { 2409 ForEach(this.arr, (item: number) => { 2410 ListItem() { 2411 Text(`item-` + item) 2412 }.backgroundColor(Color.Pink) 2413 }) 2414 } 2415 .childrenMainSize(this.listChildrenSize) // 10 2416 } 2417 } 2418} 2419``` 2420 2421V2: 2422 2423但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为ChildrenMainSize定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注ChildrenMainSize的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2424 2425具体示例如下: 2426 2427```ts 2428import { UIUtils } from '@kit.ArkUI'; 2429 2430@Entry 2431@ComponentV2 2432struct ListExample { 2433 private arr: Array<number> = new Array(10).fill(0); 2434 private scroller: ListScroller = new ListScroller(); 2435 listSpace: number = 10; 2436 // 使用makeObserved的能力来观测ChildrenMainSize 2437 listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100)); 2438 2439 build() { 2440 Column() { 2441 Button('change Default').onClick(() => { 2442 this.listChildrenSize.childDefaultSize += 10; 2443 }) 2444 2445 Button('splice 5').onClick(() => { 2446 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2447 }) 2448 2449 Button('update 5').onClick(() => { 2450 this.listChildrenSize.update(0, 200); 2451 }) 2452 2453 List({ space: this.listSpace, scroller: this.scroller }) { 2454 ForEach(this.arr, (item: number) => { 2455 ListItem() { 2456 Text(`item-` + item) 2457 }.backgroundColor(Color.Pink) 2458 }) 2459 } 2460 .childrenMainSize(this.listChildrenSize) // 10 2461 } 2462 } 2463} 2464``` 2465 2466#### WaterFlow 2467 2468开发者可以通过[WaterFlowSections](../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12)来设置WaterFlow瀑布流分组信息。 2469 2470需要注意的是,数组arr的长度需要与WaterFlowSections的中所有SectionOptions的itemsCount的总和保持一致,否则WaterFlow无法处理,导致UI不刷新。 2471 2472以下两个示例请按照'push option' -> 'splice option' -> 'update option'的顺序进行点击。 2473 2474V1: 2475 2476在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 2477 2478具体示例如下: 2479 2480```ts 2481@Entry 2482@Component 2483struct WaterFlowSample { 2484 @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2485 @State sections: WaterFlowSections = new WaterFlowSections(); 2486 scroller: Scroller = new Scroller(); 2487 @State private arr: Array<number> = new Array(9).fill(0); 2488 oneColumnSection: SectionOptions = { 2489 itemsCount: 4, 2490 crossCount: 1, 2491 columnsGap: '5vp', 2492 rowsGap: 10, 2493 }; 2494 twoColumnSection: SectionOptions = { 2495 itemsCount: 2, 2496 crossCount: 2, 2497 }; 2498 lastSection: SectionOptions = { 2499 itemsCount: 3, 2500 crossCount: 3, 2501 }; 2502 2503 aboutToAppear(): void { 2504 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2505 this.sections.splice(0, 0, sectionOptions); 2506 } 2507 2508 build() { 2509 Column() { 2510 Text(`${this.arr.length}`) 2511 2512 Button('push option').onClick(() => { 2513 let section: SectionOptions = { 2514 itemsCount: 1, 2515 crossCount: 1, 2516 }; 2517 this.sections.push(section); 2518 this.arr.push(100); 2519 }) 2520 2521 Button('splice option').onClick(() => { 2522 let section: SectionOptions = { 2523 itemsCount: 8, 2524 crossCount: 2, 2525 }; 2526 this.sections.splice(0, this.arr.length, [section]); 2527 this.arr = new Array(8).fill(10); 2528 }) 2529 2530 Button('update option').onClick(() => { 2531 let section: SectionOptions = { 2532 itemsCount: 8, 2533 crossCount: 2, 2534 }; 2535 this.sections.update(1, section); 2536 this.arr = new Array(16).fill(1); 2537 }) 2538 2539 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2540 ForEach(this.arr, (item: number) => { 2541 FlowItem() { 2542 Text(`${item}`) 2543 .border({ width: 1 }) 2544 .backgroundColor(this.colors[item % 6]) 2545 .height(30) 2546 .width(50) 2547 } 2548 }) 2549 } 2550 } 2551 } 2552} 2553``` 2554 2555V2: 2556 2557但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为WaterFlowSections定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注WaterFlowSections的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2558 2559具体示例如下: 2560 2561```ts 2562import { UIUtils } from '@kit.ArkUI'; 2563 2564@Entry 2565@ComponentV2 2566struct WaterFlowSample { 2567 colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2568 // 使用makeObserved的能力来观测WaterFlowSections 2569 sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections()); 2570 scroller: Scroller = new Scroller(); 2571 @Local private arr: Array<number> = new Array(9).fill(0); 2572 oneColumnSection: SectionOptions = { 2573 itemsCount: 4, 2574 crossCount: 1, 2575 columnsGap: '5vp', 2576 rowsGap: 10, 2577 }; 2578 twoColumnSection: SectionOptions = { 2579 itemsCount: 2, 2580 crossCount: 2, 2581 }; 2582 lastSection: SectionOptions = { 2583 itemsCount: 3, 2584 crossCount: 3, 2585 }; 2586 2587 aboutToAppear(): void { 2588 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2589 this.sections.splice(0, 0, sectionOptions); 2590 } 2591 2592 build() { 2593 Column() { 2594 Text(`${this.arr.length}`) 2595 2596 Button('push option').onClick(() => { 2597 let section: SectionOptions = { 2598 itemsCount: 1, 2599 crossCount: 1, 2600 }; 2601 this.sections.push(section); 2602 this.arr.push(100); 2603 }) 2604 2605 Button('splice option').onClick(() => { 2606 let section: SectionOptions = { 2607 itemsCount: 8, 2608 crossCount: 2, 2609 }; 2610 this.sections.splice(0, this.arr.length, [section]); 2611 this.arr = new Array(8).fill(10); 2612 }) 2613 2614 Button('update option').onClick(() => { 2615 let section: SectionOptions = { 2616 itemsCount: 8, 2617 crossCount: 2, 2618 }; 2619 this.sections.update(1, section); 2620 this.arr = new Array(16).fill(1); 2621 }) 2622 2623 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2624 ForEach(this.arr, (item: number) => { 2625 FlowItem() { 2626 Text(`${item}`) 2627 .border({ width: 1 }) 2628 .backgroundColor(this.colors[item % 6]) 2629 .height(30) 2630 .width(50) 2631 } 2632 }) 2633 } 2634 } 2635 } 2636} 2637``` 2638 2639### Modifier 2640 2641#### attributeModifier 2642 2643开发者可以通过[attributeModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier)动态设置组件的属性方法。 2644 2645V1: 2646 2647在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2648 2649具体示例如下: 2650 2651```ts 2652class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2653 isDark: boolean = false; 2654 2655 applyNormalAttribute(instance: ButtonAttribute): void { 2656 if (this.isDark) { 2657 instance.backgroundColor(Color.Black); 2658 } else { 2659 instance.backgroundColor(Color.Red); 2660 } 2661 } 2662} 2663 2664@Entry 2665@Component 2666struct AttributeDemo { 2667 @State modifier: MyButtonModifier = new MyButtonModifier(); 2668 2669 build() { 2670 Row() { 2671 Column() { 2672 Button('Button') 2673 .attributeModifier(this.modifier) 2674 .onClick(() => { 2675 this.modifier.isDark = !this.modifier.isDark; 2676 }) 2677 } 2678 .width('100%') 2679 } 2680 .height('100%') 2681 } 2682} 2683``` 2684 2685V2: 2686 2687但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,如果要观察attributeModifier的属性变化,可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2688 2689具体示例如下: 2690 2691```ts 2692import { UIUtils } from '@kit.ArkUI'; 2693 2694class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2695 isDark: boolean = false; 2696 2697 applyNormalAttribute(instance: ButtonAttribute): void { 2698 if (this.isDark) { 2699 instance.backgroundColor(Color.Black); 2700 } else { 2701 instance.backgroundColor(Color.Red); 2702 } 2703 } 2704} 2705 2706@Entry 2707@ComponentV2 2708struct AttributeDemo { 2709 // 使用makeObserved的能力观测attributeModifier的属性this.modifier 2710 modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier()); 2711 2712 build() { 2713 Row() { 2714 Column() { 2715 Button('Button') 2716 .attributeModifier(this.modifier) 2717 .onClick(() => { 2718 this.modifier.isDark = !this.modifier.isDark; 2719 }) 2720 } 2721 .width('100%') 2722 } 2723 .height('100%') 2724 } 2725} 2726``` 2727 2728#### CommonModifier 2729 2730动态设置组件的属性类。以[CommonModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)为例。 2731 2732V1: 2733 2734在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2735 2736具体实例如下: 2737 2738```ts 2739import { CommonModifier } from '@ohos.arkui.modifier'; 2740 2741class MyModifier extends CommonModifier { 2742 applyNormalAttribute(instance: CommonAttribute): void { 2743 super.applyNormalAttribute?.(instance); 2744 } 2745 2746 public setGroup1(): void { 2747 this.borderStyle(BorderStyle.Dotted); 2748 this.borderWidth(8); 2749 } 2750 2751 public setGroup2(): void { 2752 this.borderStyle(BorderStyle.Dashed); 2753 this.borderWidth(8); 2754 } 2755} 2756 2757@Component 2758struct MyImage1 { 2759 @Link modifier: CommonModifier; 2760 2761 build() { 2762 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 2763 Image($r('app.media.app_icon')) 2764 .attributeModifier(this.modifier as MyModifier) 2765 } 2766} 2767 2768@Entry 2769@Component 2770struct Index { 2771 @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10); 2772 index: number = 0; 2773 2774 build() { 2775 Column() { 2776 Button($r('app.string.EntryAbility_label')) 2777 .margin(10) 2778 .onClick(() => { 2779 console.log('Modifier', 'onClick'); 2780 this.index++; 2781 if (this.index % 2 === 1) { 2782 (this.myModifier as MyModifier).setGroup1(); 2783 console.log('Modifier', 'setGroup1'); 2784 } else { 2785 (this.myModifier as MyModifier).setGroup2(); 2786 console.log('Modifier', 'setGroup2'); 2787 } 2788 }) 2789 2790 MyImage1({ modifier: this.myModifier }) 2791 } 2792 .width('100%') 2793 } 2794} 2795``` 2796 2797V2: 2798 2799但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为[CommonModifier](../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)在框架内是通过其属性触发刷新,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2800 2801具体示例如下: 2802 2803```ts 2804import { UIUtils } from '@kit.ArkUI'; 2805import { CommonModifier } from '@ohos.arkui.modifier'; 2806 2807class MyModifier extends CommonModifier { 2808 applyNormalAttribute(instance: CommonAttribute): void { 2809 super.applyNormalAttribute?.(instance); 2810 } 2811 2812 public setGroup1(): void { 2813 this.borderStyle(BorderStyle.Dotted); 2814 this.borderWidth(8); 2815 } 2816 2817 public setGroup2(): void { 2818 this.borderStyle(BorderStyle.Dashed); 2819 this.borderWidth(8); 2820 } 2821} 2822 2823@ComponentV2 2824struct MyImage1 { 2825 @Param @Require modifier: CommonModifier; 2826 2827 build() { 2828 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 2829 Image($r('app.media.app_icon')) 2830 .attributeModifier(this.modifier as MyModifier) 2831 } 2832} 2833 2834@Entry 2835@ComponentV2 2836struct Index { 2837 // 使用makeObserved的能力来观测CommonModifier 2838 @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 2839 index: number = 0; 2840 2841 build() { 2842 Column() { 2843 Button($r('app.string.EntryAbility_label')) 2844 .margin(10) 2845 .onClick(() => { 2846 console.log('Modifier', 'onClick'); 2847 this.index++; 2848 if (this.index % 2 === 1) { 2849 (this.myModifier as MyModifier).setGroup1(); 2850 console.log('Modifier', 'setGroup1'); 2851 } else { 2852 (this.myModifier as MyModifier).setGroup2(); 2853 console.log('Modifier', 'setGroup2'); 2854 } 2855 }) 2856 2857 MyImage1({ modifier: this.myModifier }) 2858 } 2859 .width('100%') 2860 } 2861} 2862``` 2863 2864#### 组件Modfier 2865 2866动态设置组件的属性类。以Text组件为例。 2867 2868V1: 2869 2870在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2871 2872具体示例如下: 2873 2874```ts 2875import { TextModifier } from '@ohos.arkui.modifier'; 2876 2877class MyModifier extends TextModifier { 2878 applyNormalAttribute(instance: TextModifier): void { 2879 super.applyNormalAttribute?.(instance); 2880 } 2881 2882 public setGroup1(): void { 2883 this.fontSize(50); 2884 this.fontColor(Color.Pink); 2885 } 2886 2887 public setGroup2(): void { 2888 this.fontSize(50); 2889 this.fontColor(Color.Gray); 2890 } 2891} 2892 2893@Component 2894struct MyImage1 { 2895 @Link modifier: TextModifier; 2896 index: number = 0; 2897 2898 build() { 2899 Column() { 2900 Text('Test') 2901 .attributeModifier(this.modifier as MyModifier) 2902 2903 Button($r('app.string.EntryAbility_label')) 2904 .margin(10) 2905 .onClick(() => { 2906 console.log('Modifier', 'onClick'); 2907 this.index++; 2908 if (this.index % 2 === 1) { 2909 (this.modifier as MyModifier).setGroup1(); 2910 console.log('Modifier', 'setGroup1'); 2911 } else { 2912 (this.modifier as MyModifier).setGroup2(); 2913 console.log('Modifier', 'setGroup2'); 2914 } 2915 }) 2916 } 2917 } 2918} 2919 2920@Entry 2921@Component 2922struct Index { 2923 @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10); 2924 index: number = 0; 2925 2926 build() { 2927 Column() { 2928 MyImage1({ modifier: this.myModifier }) 2929 2930 Button('replace whole') 2931 .margin(10) 2932 .onClick(() => { 2933 this.myModifier = new MyModifier().backgroundColor(Color.Orange); 2934 }) 2935 } 2936 .width('100%') 2937 } 2938} 2939``` 2940 2941V2: 2942 2943但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2944 2945具体示例如下: 2946 2947```ts 2948import { UIUtils } from '@kit.ArkUI'; 2949import { TextModifier } from '@ohos.arkui.modifier'; 2950 2951class MyModifier extends TextModifier { 2952 applyNormalAttribute(instance: TextModifier): void { 2953 super.applyNormalAttribute?.(instance); 2954 } 2955 2956 public setGroup1(): void { 2957 this.fontSize(50); 2958 this.fontColor(Color.Pink); 2959 } 2960 2961 public setGroup2(): void { 2962 this.fontSize(50); 2963 this.fontColor(Color.Gray); 2964 } 2965} 2966 2967@ComponentV2 2968struct MyImage1 { 2969 @Param @Require modifier: TextModifier; 2970 index: number = 0; 2971 2972 build() { 2973 Column() { 2974 Text('Test') 2975 .attributeModifier(this.modifier as MyModifier) 2976 2977 Button($r('app.string.EntryAbility_label')) 2978 .margin(10) 2979 .onClick(() => { 2980 console.log('Modifier', 'onClick'); 2981 this.index++; 2982 if (this.index % 2 === 1) { 2983 (this.modifier as MyModifier).setGroup1(); 2984 console.log('Modifier', 'setGroup1'); 2985 } else { 2986 (this.modifier as MyModifier).setGroup2(); 2987 console.log('Modifier', 'setGroup2'); 2988 } 2989 }) 2990 } 2991 } 2992} 2993 2994@Entry 2995@ComponentV2 2996struct Index { 2997 // 使用makeObserved的能力观测TextModifier 2998 @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 2999 index: number = 0; 3000 3001 build() { 3002 Column() { 3003 MyImage1({ modifier: this.myModifier }) 3004 3005 Button('replace whole') 3006 .margin(10) 3007 .onClick(() => { 3008 this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange)); 3009 }) 3010 } 3011 .width('100%') 3012 } 3013} 3014``` 3015