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。 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#getshareddeprecated)接口实现页面间的状态变量共享。 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 = this.getUIContext().getHostContext() as common.UIAbilityContext; 1851 1852 build() { 1853 Column() { 1854 Text(`EntryAbility count: ${this.count}`) 1855 .fontSize(50) 1856 .onClick(() => { 1857 this.count++; 1858 }) 1859 Button('Jump to EntryAbility1').onClick(() => { 1860 let wantInfo: Want = { 1861 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 1862 abilityName: 'EntryAbility1' 1863 }; 1864 this.context.startAbility(wantInfo); 1865 }) 1866 } 1867 } 1868} 1869``` 1870 1871``` 1872// EntryAbility1 Index1.ets 1873import { common, Want } from '@kit.AbilityKit'; 1874@Entry 1875@Component 1876struct Index1 { 1877 @StorageLink('count') count: number = 0; 1878 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 1879 1880 build() { 1881 Column() { 1882 Text(`EntryAbility1 count: ${this.count}`) 1883 .fontSize(50) 1884 .onClick(() => { 1885 this.count++; 1886 }) 1887 Button('Jump to EntryAbility').onClick(() => { 1888 let wantInfo: Want = { 1889 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 1890 abilityName: 'EntryAbility' 1891 }; 1892 this.context.startAbility(wantInfo); 1893 }) 1894 } 1895 } 1896} 1897``` 1898V2: 1899可以使用AppStorageV2实现跨Ability共享。 1900如下面示例: 1901 1902``` 1903import { common, Want } from '@kit.AbilityKit'; 1904import { AppStorageV2 } from '@kit.ArkUI'; 1905 1906@ObservedV2 1907export class MyStorage { 1908 @Trace count: number = 0 1909} 1910 1911@Entry 1912@ComponentV2 1913struct Index { 1914 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 1915 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 1916 1917 build() { 1918 Column() { 1919 Text(`EntryAbility1 count: ${this.storage.count}`) 1920 .fontSize(50) 1921 .onClick(() => { 1922 this.storage.count++; 1923 }) 1924 Button('Jump to EntryAbility1').onClick(() => { 1925 let wantInfo: Want = { 1926 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 1927 abilityName: 'EntryAbility1' 1928 }; 1929 this.context.startAbility(wantInfo); 1930 }) 1931 } 1932 } 1933} 1934 1935``` 1936 1937``` 1938import { common, Want } from '@kit.AbilityKit'; 1939import { AppStorageV2 } from '@kit.ArkUI'; 1940 1941@ObservedV2 1942export class MyStorage { 1943 @Trace count: number = 0 1944} 1945 1946@Entry 1947@ComponentV2 1948struct Index1 { 1949 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 1950 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 1951 1952 build() { 1953 Column() { 1954 Text(`EntryAbility1 count: ${this.storage.count}`) 1955 .fontSize(50) 1956 .onClick(() => { 1957 this.storage.count++; 1958 }) 1959 Button('Jump to EntryAbility').onClick(() => { 1960 let wantInfo: Want = { 1961 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 1962 abilityName: 'EntryAbility' 1963 }; 1964 this.context.startAbility(wantInfo); 1965 }) 1966 } 1967 } 1968} 1969``` 1970 1971如果开发者需要实现类似于\@StorageProp的效果,希望本地的修改不要同步回AppStorage中,而AppStorage的变化又可以通知给使用\@StorageProp装饰器的组件,可以参考以下示例对比。 1972 1973V1: 1974 1975```ts 1976// EntryAbility Index.ets 1977import { common, Want } from '@kit.AbilityKit'; 1978@Entry 1979@Component 1980struct Index { 1981 @StorageProp('count') count: number = 0; 1982 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 1983 1984 build() { 1985 Column() { 1986 Text(`EntryAbility count: ${this.count}`) 1987 .fontSize(25) 1988 .onClick(() => { 1989 this.count++; 1990 }) 1991 Button('change Storage Count') 1992 .onClick(() => { 1993 AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100); 1994 }) 1995 Button('Jump to EntryAbility1').onClick(() => { 1996 let wantInfo: Want = { 1997 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 1998 abilityName: 'EntryAbility1' 1999 }; 2000 this.context.startAbility(wantInfo); 2001 }) 2002 } 2003 } 2004} 2005``` 2006 2007```ts 2008// EntryAbility1 Index1.ets 2009import { common, Want } from '@kit.AbilityKit'; 2010@Entry 2011@Component 2012struct Index1 { 2013 @StorageProp('count') count: number = 0; 2014 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 2015 2016 build() { 2017 Column() { 2018 Text(`EntryAbility1 count: ${this.count}`) 2019 .fontSize(50) 2020 .onClick(() => { 2021 this.count++; 2022 }) 2023 Button('change Storage Count') 2024 .onClick(() => { 2025 AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100); 2026 }) 2027 Button('Jump to EntryAbility').onClick(() => { 2028 let wantInfo: Want = { 2029 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 2030 abilityName: 'EntryAbility' 2031 }; 2032 this.context.startAbility(wantInfo); 2033 }) 2034 } 2035 } 2036} 2037``` 2038 2039V2: 2040开发者可以借助\@Monitor和\@Local来实现类似的效果,示例如下。 2041 2042```ts 2043import { common, Want } from '@kit.AbilityKit'; 2044import { AppStorageV2 } from '@kit.ArkUI'; 2045 2046@ObservedV2 2047export class MyStorage { 2048 @Trace count: number = 0; 2049} 2050 2051@Entry 2052@ComponentV2 2053struct Index { 2054 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 2055 @Local count: number = this.storage.count; 2056 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 2057 2058 @Monitor('storage.count') 2059 onCountChange(mon: IMonitor) { 2060 console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`); 2061 this.count = this.storage.count; 2062 } 2063 build() { 2064 Column() { 2065 Text(`EntryAbility1 count: ${this.count}`) 2066 .fontSize(25) 2067 .onClick(() => { 2068 this.count++; 2069 }) 2070 Button('change Storage Count') 2071 .onClick(() => { 2072 this.storage.count += 100; 2073 }) 2074 Button('Jump to EntryAbility1').onClick(() => { 2075 let wantInfo: Want = { 2076 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 2077 abilityName: 'EntryAbility1' 2078 }; 2079 this.context.startAbility(wantInfo); 2080 }) 2081 } 2082 } 2083} 2084``` 2085 2086```ts 2087import { common, Want } from '@kit.AbilityKit'; 2088import { AppStorageV2 } from '@kit.ArkUI'; 2089 2090@ObservedV2 2091export class MyStorage { 2092 @Trace count: number = 0; 2093} 2094 2095@Entry 2096@ComponentV2 2097struct Index1 { 2098 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 2099 @Local count: number = this.storage.count; 2100 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 2101 2102 @Monitor('storage.count') 2103 onCountChange(mon: IMonitor) { 2104 console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`); 2105 this.count = this.storage.count; 2106 } 2107 2108 build() { 2109 Column() { 2110 Text(`EntryAbility1 count: ${this.count}`) 2111 .fontSize(25) 2112 .onClick(() => { 2113 this.count++; 2114 }) 2115 Button('change Storage Count') 2116 .onClick(() => { 2117 this.storage.count += 100; 2118 }) 2119 Button('Jump to EntryAbility').onClick(() => { 2120 let wantInfo: Want = { 2121 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 2122 abilityName: 'EntryAbility' 2123 }; 2124 this.context.startAbility(wantInfo); 2125 }) 2126 } 2127 } 2128} 2129``` 2130 2131### Environment->调用Ability接口直接获取系统环境变量 2132V1中,开发者可以通过Environment来获取环境变量,但Environment获取的结果无法直接使用,需要配合AppStorage才能得到对应环境变量的值。 2133在切换V2的过程中,开发者无需再通过Environment来获取环境变量,可以直接通过[UIAbilityContext的config属性](../../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#属性)获取系统环境变量。 2134V1: 2135以`languageCode`为例。 2136```ts 2137// 将设备languageCode存入AppStorage中 2138Environment.envProp('languageCode', 'en'); 2139 2140@Entry 2141@Component 2142struct Index { 2143 @StorageProp('languageCode') languageCode: string = 'en'; 2144 build() { 2145 Row() { 2146 Column() { 2147 // 输出当前设备的languageCode 2148 Text(this.languageCode) 2149 } 2150 } 2151 } 2152} 2153``` 2154 2155V2: 2156封装Env类型来传递多个系统环境变量。 2157 2158``` 2159// Env.ts 2160import { ConfigurationConstant } from '@kit.AbilityKit'; 2161 2162export class Env { 2163 language: string | undefined; 2164 colorMode: ConfigurationConstant.ColorMode | undefined; 2165 fontSizeScale: number | undefined; 2166 fontWeightScale: number | undefined; 2167} 2168 2169export let env: Env = new Env(); 2170``` 2171在`onCreate`里获得需要的系统环境变量: 2172``` 2173// EntryAbility.ets 2174import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 2175import { window } from '@kit.ArkUI'; 2176import { env } from '../pages/Env'; 2177 2178export default class EntryAbility extends UIAbility { 2179 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 2180 env.language = this.context.config.language; 2181 env.colorMode = this.context.config.colorMode; 2182 env.fontSizeScale = this.context.config.fontSizeScale; 2183 env.fontWeightScale = this.context.config.fontWeightScale; 2184 } 2185 2186 onWindowStageCreate(windowStage: window.WindowStage): void { 2187 windowStage.loadContent('pages/Index'); 2188 } 2189} 2190 2191``` 2192在页面中获得当前Env的值。 2193``` 2194// Index.ets 2195import { env } from '../pages/Env'; 2196 2197@Entry 2198@ComponentV2 2199struct Index { 2200 build() { 2201 Row() { 2202 Column() { 2203 // 输出当前设备的环境变量 2204 Text(`languageCode: ${env.language}`).fontSize(20) 2205 Text(`colorMode: ${env.colorMode}`).fontSize(20) 2206 Text(`fontSizeScale: ${env.fontSizeScale}`).fontSize(20) 2207 Text(`fontWeightScale: ${env.fontWeightScale}`).fontSize(20) 2208 } 2209 } 2210 } 2211} 2212``` 2213 2214### PersistentStorage->PersistenceV2 2215V1中PersistentStorage提供了持久化UI数据的能力,而V2则提供了更加方便使用的PersistenceV2接口来替代它。 2216- PersistentStorage持久化的触发时机依赖AppStorage的观察能力,且与AppStorage耦合,开发者无法自主选择写入或读取持久化数据的时机。 2217- PersistentStorage使用序列化和反序列化,并没有传入类型,所以在持久化后,会丢失其类型,且对象的属性方法不能持久化。 2218 2219对于PersistenceV2: 2220- 与PersistenceV2关联的\@ObservedV2对象,其\@Trace属性的变化,会触发整个关联对象的自动持久化。 2221- 开发者也可以调用[PersistenceV2.save](./arkts-new-persistencev2.md#save手动持久化数据)和[PersistenceV2.globalConnect](./arkts-new-persistencev2.md#使用globalconnect存储数据)接口来手动触发持久化写入和读取。 2222 2223V1: 2224 2225```ts 2226class data { 2227 name: string = 'ZhangSan'; 2228 id: number = 0; 2229} 2230 2231PersistentStorage.persistProp('numProp', 47); 2232PersistentStorage.persistProp('dataProp', new data()); 2233 2234@Entry 2235@Component 2236struct Index { 2237 @StorageLink('numProp') numProp: number = 48; 2238 @StorageLink('dataProp') dataProp: data = new data(); 2239 2240 build() { 2241 Column() { 2242 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2243 Text(`numProp: ${this.numProp}`) 2244 .onClick(() => { 2245 this.numProp += 1; 2246 }) 2247 .fontSize(30) 2248 2249 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2250 Text(`dataProp.name: ${this.dataProp.name}`) 2251 .onClick(() => { 2252 this.dataProp.name += 'a'; 2253 }) 2254 .fontSize(30) 2255 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2256 Text(`dataProp.id: ${this.dataProp.id}`) 2257 .onClick(() => { 2258 this.dataProp.id += 1; 2259 }) 2260 .fontSize(30) 2261 2262 } 2263 .width('100%') 2264 } 2265} 2266``` 2267 2268V2: 2269 2270下面的案例展示了: 2271- 将`PersistentStorage`的持久化数据迁移到V2的PersistenceV2中去,其中V2对被@Trace标记的数据可以自动持久化,对于非@Trace数据需要开发者自己手动调用save进行持久化。 2272- 示例中的move函数和需要显示的组件放在了一个ets中,开发者可以定义自己的move函数,并放入合适的位置进行统一迁移操作。 2273```ts 2274// 迁移到globalConnect 2275import { PersistenceV2, Type } from '@kit.ArkUI'; 2276 2277// 接受序列化失败的回调 2278PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => { 2279 console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`); 2280}); 2281 2282class Data { 2283 name: string = 'ZhangSan'; 2284 id: number = 0; 2285} 2286 2287@ObservedV2 2288class V2Data { 2289 @Trace name: string = ''; 2290 @Trace Id: number = 1; 2291} 2292 2293@ObservedV2 2294export class Sample { 2295 // 对于复杂对象需要@Type修饰,确保序列化成功 2296 @Type(V2Data) 2297 @Trace num: number = 1; 2298 @Trace V2: V2Data = new V2Data(); 2299} 2300 2301// 用于判断是否完成数据迁移的辅助数据 2302@ObservedV2 2303class StorageState { 2304 @Trace isCompleteMoving: boolean = false; 2305} 2306 2307function move() { 2308 let movingState = PersistenceV2.globalConnect({type: StorageState, defaultCreator: () => new StorageState()})!; 2309 if (!movingState.isCompleteMoving) { 2310 PersistentStorage.persistProp('numProp', 47); 2311 PersistentStorage.persistProp('dataProp', new Data()); 2312 let num = AppStorage.get<number>('numProp')!; 2313 let V1Data = AppStorage.get<Data>('dataProp')!; 2314 PersistentStorage.deleteProp('numProp'); 2315 PersistentStorage.deleteProp('dataProp'); 2316 2317 // V2创建对应数据 2318 let migrate = PersistenceV2.globalConnect({type: Sample, key: 'connect2', defaultCreator: () => new Sample()})!; // 使用默认构造函数也可以 2319 // 赋值数据,@Trace修饰的会自动保存,对于非@Trace对象,也可以调用save保存,如:PersistenceV2.save('connect2'); 2320 migrate.num = num; 2321 migrate.V2.name = V1Data.name; 2322 migrate.V2.Id = V1Data.id; 2323 2324 // 将迁移标志设置为true 2325 movingState.isCompleteMoving = true; 2326 } 2327} 2328 2329move(); 2330 2331@Entry 2332@ComponentV2 2333struct Page1 { 2334 @Local refresh: number = 0; 2335 // 使用key:connect2存入数据 2336 @Local p: Sample = PersistenceV2.globalConnect({type: Sample, key:'connect2', defaultCreator:() => new Sample()})!; 2337 2338 build() { 2339 Column({space: 5}) { 2340 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2341 Text(`numProp: ${this.p.num}`) 2342 .onClick(() => { 2343 this.p.num += 1; 2344 }) 2345 .fontSize(30) 2346 2347 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2348 Text(`dataProp.name: ${this.p.V2.name}`) 2349 .onClick(() => { 2350 this.p.V2.name += 'a'; 2351 }) 2352 .fontSize(30) 2353 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 2354 Text(`dataProp.id: ${this.p.V2.Id}`) 2355 .onClick(() => { 2356 this.p.V2.Id += 1; 2357 }) 2358 .fontSize(30) 2359 } 2360 .width('100%') 2361 } 2362} 2363``` 2364 2365## 存量迁移场景 2366 2367对于已经使用V1开发的大型应用,一般不太可能做到一次性的从V1迁移到V2,而是分批次和分组件的部分迁移,这就必然会带来V1和V2的混用。 2368 2369这种场景,一般是父组件是状态管理V1,而迁移的子组件为状态管理V2。为了模拟这种场景,我们举出下面的示例: 2370- 父组件是\@Component,数据源是\@LocalStorageLink。 2371- 子组件是\@ComponentV2,使用\@Param接受数据源的数据。 2372 2373这种情况,我们可以通过以下策略进行迁移: 2374- 声明一个\@ObservedV2装饰的class来封装V1的数据。 2375- 在\@Component和\@ComponentV2之间,定义一个桥接的\@Component自定义组件。 2376- 在桥接层: 2377 - V1->V2的数据同步,可通过\@Watch的监听触发\@ObservedV2装饰的class的属性的赋值。 2378 - V2->V1的数据同步,可通过在\@ObservedV2装饰的class里声明Monitor,通过LocalStorage的API反向通知给V1状态变量。 2379 2380具体示例如下: 2381``` 2382let storage: LocalStorage = new LocalStorage(); 2383 2384@ObservedV2 2385class V1StorageData { 2386 @Trace title: string = 'V1OldComponent' 2387 @Monitor('title') 2388 onStrChange(monitor: IMonitor) { 2389 monitor.dirty.forEach((path: string) => { 2390 console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`) 2391 if (path === 'title') { 2392 storage.setOrCreate('title', this.title); 2393 } 2394 }) 2395 } 2396} 2397let v1Data: V1StorageData = new V1StorageData(); 2398 2399@Entry(storage) 2400@Component 2401struct V1OldComponent { 2402 @LocalStorageLink('title') title: string = 'V1OldComponent'; 2403 2404 build() { 2405 Column() { 2406 Text(`V1OldComponent: ${this.title}`) 2407 .fontSize(20) 2408 .onClick(() => { 2409 this.title = 'new value from V1OldComponent'; 2410 }) 2411 Bridge() 2412 } 2413 } 2414} 2415 2416 2417@Component 2418struct Bridge { 2419 @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge'; 2420 titleWatch() { 2421 v1Data.title = this.title; 2422 } 2423 2424 build() { 2425 NewV2Component() 2426 } 2427} 2428@ComponentV2 2429struct NewV2Component { 2430 build() { 2431 Column() { 2432 Text(`NewV2Component: ${v1Data.title}`) 2433 .fontSize(20) 2434 .onClick(() => { 2435 v1Data.title = 'NewV2Component'; 2436 }) 2437 } 2438 } 2439} 2440``` 2441 2442## 其他迁移场景 2443 2444### 滑动组件 2445 2446#### List 2447 2448开发者可以通过[ChildrenMainSize](../../reference/apis-arkui/arkui-ts/ts-container-list.md#childrenmainsize12)来设置List的子组件在主轴方向的大小信息。 2449 2450V1: 2451 2452在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 2453 2454具体示例如下: 2455 2456```ts 2457@Entry 2458@Component 2459struct ListExample { 2460 private arr: Array<number> = new Array(10).fill(0); 2461 private scroller: ListScroller = new ListScroller(); 2462 @State listSpace: number = 10; 2463 @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100); 2464 2465 build() { 2466 Column() { 2467 Button('change Default').onClick(() => { 2468 this.listChildrenSize.childDefaultSize += 10; 2469 }) 2470 2471 Button('splice 5').onClick(() => { 2472 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2473 }) 2474 2475 Button('update 5').onClick(() => { 2476 this.listChildrenSize.update(0, 200); 2477 }) 2478 2479 List({ space: this.listSpace, scroller: this.scroller }) { 2480 ForEach(this.arr, (item: number) => { 2481 ListItem() { 2482 Text(`item-` + item) 2483 }.backgroundColor(Color.Pink) 2484 }) 2485 } 2486 .childrenMainSize(this.listChildrenSize) // 10 2487 } 2488 } 2489} 2490``` 2491 2492V2: 2493 2494但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为ChildrenMainSize定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注ChildrenMainSize的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2495 2496具体示例如下: 2497 2498```ts 2499import { UIUtils } from '@kit.ArkUI'; 2500 2501@Entry 2502@ComponentV2 2503struct ListExample { 2504 private arr: Array<number> = new Array(10).fill(0); 2505 private scroller: ListScroller = new ListScroller(); 2506 listSpace: number = 10; 2507 // 使用makeObserved的能力来观测ChildrenMainSize 2508 listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100)); 2509 2510 build() { 2511 Column() { 2512 Button('change Default').onClick(() => { 2513 this.listChildrenSize.childDefaultSize += 10; 2514 }) 2515 2516 Button('splice 5').onClick(() => { 2517 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 2518 }) 2519 2520 Button('update 5').onClick(() => { 2521 this.listChildrenSize.update(0, 200); 2522 }) 2523 2524 List({ space: this.listSpace, scroller: this.scroller }) { 2525 ForEach(this.arr, (item: number) => { 2526 ListItem() { 2527 Text(`item-` + item) 2528 }.backgroundColor(Color.Pink) 2529 }) 2530 } 2531 .childrenMainSize(this.listChildrenSize) // 10 2532 } 2533 } 2534} 2535``` 2536 2537#### WaterFlow 2538 2539开发者可以通过[WaterFlowSections](../../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12)来设置WaterFlow瀑布流分组信息。 2540 2541需要注意的是,数组arr的长度需要与WaterFlowSections的中所有SectionOptions的itemsCount的总和保持一致,否则WaterFlow无法处理,导致UI不刷新。 2542 2543以下两个示例请按照'push option' -> 'splice option' -> 'update option'的顺序进行点击。 2544 2545V1: 2546 2547在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 2548 2549具体示例如下: 2550 2551```ts 2552@Entry 2553@Component 2554struct WaterFlowSample { 2555 @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2556 @State sections: WaterFlowSections = new WaterFlowSections(); 2557 scroller: Scroller = new Scroller(); 2558 @State private arr: Array<number> = new Array(9).fill(0); 2559 oneColumnSection: SectionOptions = { 2560 itemsCount: 4, 2561 crossCount: 1, 2562 columnsGap: '5vp', 2563 rowsGap: 10, 2564 }; 2565 twoColumnSection: SectionOptions = { 2566 itemsCount: 2, 2567 crossCount: 2, 2568 }; 2569 lastSection: SectionOptions = { 2570 itemsCount: 3, 2571 crossCount: 3, 2572 }; 2573 2574 aboutToAppear(): void { 2575 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2576 this.sections.splice(0, 0, sectionOptions); 2577 } 2578 2579 build() { 2580 Column() { 2581 Text(`${this.arr.length}`) 2582 2583 Button('push option').onClick(() => { 2584 let section: SectionOptions = { 2585 itemsCount: 1, 2586 crossCount: 1, 2587 }; 2588 this.sections.push(section); 2589 this.arr.push(100); 2590 }) 2591 2592 Button('splice option').onClick(() => { 2593 let section: SectionOptions = { 2594 itemsCount: 8, 2595 crossCount: 2, 2596 }; 2597 this.sections.splice(0, this.arr.length, [section]); 2598 this.arr = new Array(8).fill(10); 2599 }) 2600 2601 Button('update option').onClick(() => { 2602 let section: SectionOptions = { 2603 itemsCount: 8, 2604 crossCount: 2, 2605 }; 2606 this.sections.update(1, section); 2607 this.arr = new Array(16).fill(1); 2608 }) 2609 2610 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2611 ForEach(this.arr, (item: number) => { 2612 FlowItem() { 2613 Text(`${item}`) 2614 .border({ width: 1 }) 2615 .backgroundColor(this.colors[item % 6]) 2616 .height(30) 2617 .width(50) 2618 } 2619 }) 2620 } 2621 } 2622 } 2623} 2624``` 2625 2626V2: 2627 2628但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为WaterFlowSections定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注WaterFlowSections的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2629 2630具体示例如下: 2631 2632```ts 2633import { UIUtils } from '@kit.ArkUI'; 2634 2635@Entry 2636@ComponentV2 2637struct WaterFlowSample { 2638 colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 2639 // 使用makeObserved的能力来观测WaterFlowSections 2640 sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections()); 2641 scroller: Scroller = new Scroller(); 2642 @Local private arr: Array<number> = new Array(9).fill(0); 2643 oneColumnSection: SectionOptions = { 2644 itemsCount: 4, 2645 crossCount: 1, 2646 columnsGap: '5vp', 2647 rowsGap: 10, 2648 }; 2649 twoColumnSection: SectionOptions = { 2650 itemsCount: 2, 2651 crossCount: 2, 2652 }; 2653 lastSection: SectionOptions = { 2654 itemsCount: 3, 2655 crossCount: 3, 2656 }; 2657 2658 aboutToAppear(): void { 2659 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 2660 this.sections.splice(0, 0, sectionOptions); 2661 } 2662 2663 build() { 2664 Column() { 2665 Text(`${this.arr.length}`) 2666 2667 Button('push option').onClick(() => { 2668 let section: SectionOptions = { 2669 itemsCount: 1, 2670 crossCount: 1, 2671 }; 2672 this.sections.push(section); 2673 this.arr.push(100); 2674 }) 2675 2676 Button('splice option').onClick(() => { 2677 let section: SectionOptions = { 2678 itemsCount: 8, 2679 crossCount: 2, 2680 }; 2681 this.sections.splice(0, this.arr.length, [section]); 2682 this.arr = new Array(8).fill(10); 2683 }) 2684 2685 Button('update option').onClick(() => { 2686 let section: SectionOptions = { 2687 itemsCount: 8, 2688 crossCount: 2, 2689 }; 2690 this.sections.update(1, section); 2691 this.arr = new Array(16).fill(1); 2692 }) 2693 2694 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 2695 ForEach(this.arr, (item: number) => { 2696 FlowItem() { 2697 Text(`${item}`) 2698 .border({ width: 1 }) 2699 .backgroundColor(this.colors[item % 6]) 2700 .height(30) 2701 .width(50) 2702 } 2703 }) 2704 } 2705 } 2706 } 2707} 2708``` 2709 2710### Modifier 2711 2712#### attributeModifier 2713 2714开发者可以通过[attributeModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier)动态设置组件的属性方法。 2715 2716V1: 2717 2718在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2719 2720具体示例如下: 2721 2722```ts 2723class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2724 isDark: boolean = false; 2725 2726 applyNormalAttribute(instance: ButtonAttribute): void { 2727 if (this.isDark) { 2728 instance.backgroundColor(Color.Black); 2729 } else { 2730 instance.backgroundColor(Color.Red); 2731 } 2732 } 2733} 2734 2735@Entry 2736@Component 2737struct AttributeDemo { 2738 @State modifier: MyButtonModifier = new MyButtonModifier(); 2739 2740 build() { 2741 Row() { 2742 Column() { 2743 Button('Button') 2744 .attributeModifier(this.modifier) 2745 .onClick(() => { 2746 this.modifier.isDark = !this.modifier.isDark; 2747 }) 2748 } 2749 .width('100%') 2750 } 2751 .height('100%') 2752 } 2753} 2754``` 2755 2756V2: 2757 2758但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,如果要观察attributeModifier的属性变化,可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2759 2760具体示例如下: 2761 2762```ts 2763import { UIUtils } from '@kit.ArkUI'; 2764 2765class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 2766 isDark: boolean = false; 2767 2768 applyNormalAttribute(instance: ButtonAttribute): void { 2769 if (this.isDark) { 2770 instance.backgroundColor(Color.Black); 2771 } else { 2772 instance.backgroundColor(Color.Red); 2773 } 2774 } 2775} 2776 2777@Entry 2778@ComponentV2 2779struct AttributeDemo { 2780 // 使用makeObserved的能力观测attributeModifier的属性this.modifier 2781 modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier()); 2782 2783 build() { 2784 Row() { 2785 Column() { 2786 Button('Button') 2787 .attributeModifier(this.modifier) 2788 .onClick(() => { 2789 this.modifier.isDark = !this.modifier.isDark; 2790 }) 2791 } 2792 .width('100%') 2793 } 2794 .height('100%') 2795 } 2796} 2797``` 2798 2799#### CommonModifier 2800 2801动态设置组件的属性类。以[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)为例。 2802 2803V1: 2804 2805在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2806 2807具体实例如下: 2808 2809```ts 2810import { CommonModifier } from '@ohos.arkui.modifier'; 2811 2812class MyModifier extends CommonModifier { 2813 applyNormalAttribute(instance: CommonAttribute): void { 2814 super.applyNormalAttribute?.(instance); 2815 } 2816 2817 public setGroup1(): void { 2818 this.borderStyle(BorderStyle.Dotted); 2819 this.borderWidth(8); 2820 } 2821 2822 public setGroup2(): void { 2823 this.borderStyle(BorderStyle.Dashed); 2824 this.borderWidth(8); 2825 } 2826} 2827 2828@Component 2829struct MyImage1 { 2830 @Link modifier: CommonModifier; 2831 2832 build() { 2833 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 2834 Image($r('app.media.app_icon')) 2835 .attributeModifier(this.modifier as MyModifier) 2836 } 2837} 2838 2839@Entry 2840@Component 2841struct Index { 2842 @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10); 2843 index: number = 0; 2844 2845 build() { 2846 Column() { 2847 Button($r('app.string.EntryAbility_label')) 2848 .margin(10) 2849 .onClick(() => { 2850 console.log('Modifier', 'onClick'); 2851 this.index++; 2852 if (this.index % 2 === 1) { 2853 (this.myModifier as MyModifier).setGroup1(); 2854 console.log('Modifier', 'setGroup1'); 2855 } else { 2856 (this.myModifier as MyModifier).setGroup2(); 2857 console.log('Modifier', 'setGroup2'); 2858 } 2859 }) 2860 2861 MyImage1({ modifier: this.myModifier }) 2862 } 2863 .width('100%') 2864 } 2865} 2866``` 2867 2868V2: 2869 2870但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)在框架内是通过其属性触发刷新,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 2871 2872具体示例如下: 2873 2874```ts 2875import { UIUtils } from '@kit.ArkUI'; 2876import { CommonModifier } from '@ohos.arkui.modifier'; 2877 2878class MyModifier extends CommonModifier { 2879 applyNormalAttribute(instance: CommonAttribute): void { 2880 super.applyNormalAttribute?.(instance); 2881 } 2882 2883 public setGroup1(): void { 2884 this.borderStyle(BorderStyle.Dotted); 2885 this.borderWidth(8); 2886 } 2887 2888 public setGroup2(): void { 2889 this.borderStyle(BorderStyle.Dashed); 2890 this.borderWidth(8); 2891 } 2892} 2893 2894@ComponentV2 2895struct MyImage1 { 2896 @Param @Require modifier: CommonModifier; 2897 2898 build() { 2899 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 2900 Image($r('app.media.app_icon')) 2901 .attributeModifier(this.modifier as MyModifier) 2902 } 2903} 2904 2905@Entry 2906@ComponentV2 2907struct Index { 2908 // 使用makeObserved的能力来观测CommonModifier 2909 @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 2910 index: number = 0; 2911 2912 build() { 2913 Column() { 2914 Button($r('app.string.EntryAbility_label')) 2915 .margin(10) 2916 .onClick(() => { 2917 console.log('Modifier', 'onClick'); 2918 this.index++; 2919 if (this.index % 2 === 1) { 2920 (this.myModifier as MyModifier).setGroup1(); 2921 console.log('Modifier', 'setGroup1'); 2922 } else { 2923 (this.myModifier as MyModifier).setGroup2(); 2924 console.log('Modifier', 'setGroup2'); 2925 } 2926 }) 2927 2928 MyImage1({ modifier: this.myModifier }) 2929 } 2930 .width('100%') 2931 } 2932} 2933``` 2934 2935#### 组件Modfier 2936 2937动态设置组件的属性类。以Text组件为例。 2938 2939V1: 2940 2941在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 2942 2943具体示例如下: 2944 2945```ts 2946import { TextModifier } from '@ohos.arkui.modifier'; 2947 2948class MyModifier extends TextModifier { 2949 applyNormalAttribute(instance: TextModifier): void { 2950 super.applyNormalAttribute?.(instance); 2951 } 2952 2953 public setGroup1(): void { 2954 this.fontSize(50); 2955 this.fontColor(Color.Pink); 2956 } 2957 2958 public setGroup2(): void { 2959 this.fontSize(50); 2960 this.fontColor(Color.Gray); 2961 } 2962} 2963 2964@Component 2965struct MyImage1 { 2966 @Link modifier: TextModifier; 2967 index: number = 0; 2968 2969 build() { 2970 Column() { 2971 Text('Test') 2972 .attributeModifier(this.modifier as MyModifier) 2973 2974 Button($r('app.string.EntryAbility_label')) 2975 .margin(10) 2976 .onClick(() => { 2977 console.log('Modifier', 'onClick'); 2978 this.index++; 2979 if (this.index % 2 === 1) { 2980 (this.modifier as MyModifier).setGroup1(); 2981 console.log('Modifier', 'setGroup1'); 2982 } else { 2983 (this.modifier as MyModifier).setGroup2(); 2984 console.log('Modifier', 'setGroup2'); 2985 } 2986 }) 2987 } 2988 } 2989} 2990 2991@Entry 2992@Component 2993struct Index { 2994 @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10); 2995 index: number = 0; 2996 2997 build() { 2998 Column() { 2999 MyImage1({ modifier: this.myModifier }) 3000 3001 Button('replace whole') 3002 .margin(10) 3003 .onClick(() => { 3004 this.myModifier = new MyModifier().backgroundColor(Color.Orange); 3005 }) 3006 } 3007 .width('100%') 3008 } 3009} 3010``` 3011 3012V2: 3013 3014但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 3015 3016具体示例如下: 3017 3018```ts 3019import { UIUtils } from '@kit.ArkUI'; 3020import { TextModifier } from '@ohos.arkui.modifier'; 3021 3022class MyModifier extends TextModifier { 3023 applyNormalAttribute(instance: TextModifier): void { 3024 super.applyNormalAttribute?.(instance); 3025 } 3026 3027 public setGroup1(): void { 3028 this.fontSize(50); 3029 this.fontColor(Color.Pink); 3030 } 3031 3032 public setGroup2(): void { 3033 this.fontSize(50); 3034 this.fontColor(Color.Gray); 3035 } 3036} 3037 3038@ComponentV2 3039struct MyImage1 { 3040 @Param @Require modifier: TextModifier; 3041 index: number = 0; 3042 3043 build() { 3044 Column() { 3045 Text('Test') 3046 .attributeModifier(this.modifier as MyModifier) 3047 3048 Button($r('app.string.EntryAbility_label')) 3049 .margin(10) 3050 .onClick(() => { 3051 console.log('Modifier', 'onClick'); 3052 this.index++; 3053 if (this.index % 2 === 1) { 3054 (this.modifier as MyModifier).setGroup1(); 3055 console.log('Modifier', 'setGroup1'); 3056 } else { 3057 (this.modifier as MyModifier).setGroup2(); 3058 console.log('Modifier', 'setGroup2'); 3059 } 3060 }) 3061 } 3062 } 3063} 3064 3065@Entry 3066@ComponentV2 3067struct Index { 3068 // 使用makeObserved的能力观测TextModifier 3069 @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 3070 index: number = 0; 3071 3072 build() { 3073 Column() { 3074 MyImage1({ modifier: this.myModifier }) 3075 3076 Button('replace whole') 3077 .margin(10) 3078 .onClick(() => { 3079 this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange)); 3080 }) 3081 } 3082 .width('100%') 3083 } 3084} 3085``` 3086