1# 应用内状态变量和其他场景迁移指导 2本文档主要介绍应用内状态变量和其他场景迁移场景,包含以下场景。 3 4| V1装饰器名/场景 | V2装饰器名 | 5|------------------------|--------------------------| 6| [LocalStorage](./arkts-localstorage.md) | [\@ObservedV2](./arkts-new-observedV2-and-trace.md)[\@Trace](./arkts-new-observedV2-and-trace.md) | 7| [AppStorage](./arkts-appstorage.md) | [AppStorageV2](./arkts-new-appstoragev2.md) | 8| [Environment](./arkts-environment.md) | 调用Ability接口获取系统环境变量 | 9| [PersistentStorage](./arkts-persiststorage.md) | [PersistenceV2](./arkts-new-persistencev2.md) | 10| 存量迁移场景 | \@ObservedV2、\@Trace、[\@Monitor](./arkts-new-monitor.md) | 11| 滑动组件场景 | [makeObserved](./arkts-new-makeObserved.md)| 12| [Modifier](../arkts-user-defined-modifier.md) |[makeObserved](./arkts-new-makeObserved.md)、\@ObservedV2、\@Trace| 13 14 15## 各装饰器迁移示例 16 17### LocalStorage->\@ObservedV2/\@Trace 18**迁移规则** 19 20LocalStorage的目的是实现页面间的状态变量共享。由于V1状态变量和View层耦合,开发者难以自主实现页面间状态变量的共享,因此框架提供了该能力。 21状态管理V2将状态变量的观察能力内嵌到数据本身,不再和View层耦合。因此,不再需要类似LocalStorage的能力,可以使用创建\@ObservedV2和\@Trace装饰类的实例,开发者需自行import和export,实现状态变量的页面间共享。 22 23**示例** 24 25**基本场景** 26 27V1: 28 29通过windowStage.[loadContent](../../reference/apis-arkui/arkts-apis-window-Window.md#loadcontent9)和this.getUIContext().[getSharedLocalStorage](../../reference/apis-arkui/arkts-apis-uicontext-uicontext.md#getsharedlocalstorage12)接口实现页面间的状态变量共享。 30```ts 31// EntryAbility.ets 32import { UIAbility } from '@kit.AbilityKit'; 33import { window } from '@kit.ArkUI'; 34 35export default class EntryAbility extends UIAbility { 36 para:Record<string, number> = { 'count': 47 }; 37 storage: LocalStorage = new LocalStorage(this.para); 38 39 onWindowStageCreate(windowStage: window.WindowStage): void { 40 windowStage.loadContent('pages/Page1', this.storage); 41 } 42} 43``` 44在下面的示例中,使用\@LocalStorageLink,可以将开发者本地的修改同步回LocalStorage中。 45 46```ts 47// Page1.ets 48// 预览器上不支持获取页面共享的LocalStorage实例。 49@Entry({ useSharedStorage: true }) 50@Component 51struct Page1 { 52 @LocalStorageLink('count') count: number = 0; 53 pageStack: NavPathStack = new NavPathStack(); 54 build() { 55 Navigation(this.pageStack) { 56 Column() { 57 Text(`${this.count}`) 58 .fontSize(50) 59 .onClick(() => { 60 this.count++; 61 }) 62 Button('push to Page2') 63 .onClick(() => { 64 this.pageStack.pushPathByName('Page2', null); 65 }) 66 } 67 } 68 } 69} 70``` 71 72```ts 73// Page2.ets 74@Builder 75export function Page2Builder() { 76 Page2() 77} 78 79// Page2组件获得了父亲Page1组件的LocalStorage实例 80@Component 81struct Page2 { 82 @LocalStorageLink('count') count: number = 0; 83 pathStack: NavPathStack = new NavPathStack(); 84 85 build() { 86 NavDestination() { 87 Column() { 88 Text(`${this.count}`) 89 .fontSize(50) 90 .onClick(() => { 91 this.count++; 92 }) 93 Button('change') 94 .fontSize(50) 95 .onClick(() => { 96 const storage = this.getUIContext().getSharedLocalStorage(); 97 if (storage) { 98 storage.set('count', 20); 99 } 100 }) 101 } 102 } 103 .onReady((context: NavDestinationContext) => { 104 this.pathStack = context.pathStack; 105 }) 106 } 107} 108``` 109使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。 110```json 111{ 112 "routerMap": [ 113 { 114 "name": "Page2", 115 "pageSourceFile": "src/main/ets/pages/Page2.ets", 116 "buildFunction": "Page2Builder", 117 "data": { 118 "description": "LocalStorage example" 119 } 120 } 121 ] 122} 123``` 124V2: 125 126- 声明\@ObservedV2装饰的MyStorage类,并import到需要使用的页面中。 127- 声明被\@Trace的属性作为页面间共享的可观察的数据。 128 129```ts 130// storage.ets 131@ObservedV2 132export class MyStorage { 133 static singleton_: MyStorage; 134 static instance() { 135 if(!MyStorage.singleton_) { 136 MyStorage.singleton_ = new MyStorage(); 137 }; 138 return MyStorage.singleton_; 139 } 140 @Trace count: number = 47; 141} 142``` 143 144```ts 145// Page1.ets 146import { MyStorage } from './storage'; 147 148@Entry 149@ComponentV2 150struct Page1 { 151 storage: MyStorage = MyStorage.instance(); 152 pageStack: NavPathStack = new NavPathStack(); 153 build() { 154 Navigation(this.pageStack) { 155 Column() { 156 Text(`${this.storage.count}`) 157 .fontSize(50) 158 .onClick(() => { 159 this.storage.count++; 160 }) 161 Button('push to Page2') 162 .onClick(() => { 163 this.pageStack.pushPathByName('Page2', null); 164 }) 165 } 166 } 167 } 168} 169``` 170 171```ts 172// Page2.ets 173import { MyStorage } from './storage'; 174 175@Builder 176export function Page2Builder() { 177 Page2() 178} 179 180@ComponentV2 181struct Page2 { 182 storage: MyStorage = MyStorage.instance(); 183 pathStack: NavPathStack = new NavPathStack(); 184 build() { 185 NavDestination() { 186 Column() { 187 Text(`${this.storage.count}`) 188 .fontSize(50) 189 .onClick(() => { 190 this.storage.count++; 191 }) 192 } 193 } 194 .onReady((context: NavDestinationContext) => { 195 this.pathStack = context.pathStack; 196 }) 197 } 198} 199``` 200使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。 201```json 202{ 203 "routerMap": [ 204 { 205 "name": "Page2", 206 "pageSourceFile": "src/main/ets/pages/Page2.ets", 207 "buildFunction": "Page2Builder", 208 "data": { 209 "description" : "LocalStorage example" 210 } 211 } 212 ] 213} 214``` 215 216如果开发者需要实现类似于\@LocalStorageProp的效果,但希望本地的修改不同步回LocalStorage中,可参考以下示例: 217- 在`Page1`中改变`count`值,由于count被\@LocalStorageProp装饰的,因此其更改仅在本地生效,不会同步到LocalStorage。 218- 点击`push to Page2`,跳转到`Page2`。由于在`Page1`中改变`count`值不会同步到LocalStorage,因此`Page2`中的Text组件仍显示初始值47。 219- 点击`change Storage Count`,调用LocalStorage的setOrCreate,改变`count`对应的值,并通知所有绑定该key的变量。 220 221```ts 222// Page1.ets 223export let storage: LocalStorage = new LocalStorage(); 224storage.setOrCreate('count', 47); 225 226@Entry(storage) 227@Component 228struct Page1 { 229 @LocalStorageProp('count') count: number = 0; 230 pageStack: NavPathStack = new NavPathStack(); 231 build() { 232 Navigation(this.pageStack) { 233 Column() { 234 Text(`${this.count}`) 235 .fontSize(50) 236 .onClick(() => { 237 this.count++; 238 }) 239 Button('change Storage Count') 240 .onClick(() => { 241 storage.setOrCreate('count', storage.get<number>('count') as number + 100); 242 }) 243 Button('push to Page2') 244 .onClick(() => { 245 this.pageStack.pushPathByName('Page2', null); 246 }) 247 } 248 } 249 } 250} 251``` 252 253```ts 254// Page2.ets 255import { storage } from './Page1' 256@Builder 257export function Page2Builder() { 258 Page2() 259} 260 261// Page2组件获得了父亲Page1组件的LocalStorage实例 262@Component 263struct Page2 { 264 @LocalStorageProp('count') count: number = 0; 265 pathStack: NavPathStack = new NavPathStack(); 266 build() { 267 NavDestination() { 268 Column() { 269 Text(`${this.count}`) 270 .fontSize(50) 271 .onClick(() => { 272 this.count++; 273 }) 274 Button('change Storage Count') 275 .onClick(() => { 276 storage.setOrCreate('count', storage.get<number>('count') as number + 100); 277 }) 278 } 279 } 280 .onReady((context: NavDestinationContext) => { 281 this.pathStack = context.pathStack; 282 }) 283 } 284} 285``` 286在V2中,可以借助\@Local和\@Monitor实现类似的效果。 287- \@Local装饰的`count`变量为组件本地的值,其改变不会同步回`storage`。 288- \@Monitor监听`storage.count`的变化,当`storage.count`改变时,在\@Monitor的回调里改变本地\@Local的值。 289 290```ts 291// Page1.ets 292import { MyStorage } from './storage'; 293 294@Entry 295@ComponentV2 296struct Page1 { 297 storage: MyStorage = MyStorage.instance(); 298 pageStack: NavPathStack = new NavPathStack(); 299 @Local count: number = this.storage.count; 300 301 @Monitor('storage.count') 302 onCountChange(mon: IMonitor) { 303 console.log(`Page1 ${mon.value()?.before} to ${mon.value()?.now}`); 304 this.count = this.storage.count; 305 } 306 build() { 307 Navigation(this.pageStack) { 308 Column() { 309 Text(`${this.count}`) 310 .fontSize(50) 311 .onClick(() => { 312 this.count++; 313 }) 314 Button('change Storage Count') 315 .onClick(() => { 316 this.storage.count += 100; 317 }) 318 Button('push to Page2') 319 .onClick(() => { 320 this.pageStack.pushPathByName('Page2', null); 321 }) 322 } 323 } 324 } 325} 326``` 327 328```ts 329// Page2.ets 330import { MyStorage } from './storage'; 331 332@Builder 333export function Page2Builder() { 334 Page2() 335} 336 337@ComponentV2 338struct Page2 { 339 storage: MyStorage = MyStorage.instance(); 340 pathStack: NavPathStack = new NavPathStack(); 341 @Local count: number = this.storage.count; 342 343 @Monitor('storage.count') 344 onCountChange(mon: IMonitor) { 345 console.log(`Page2 ${mon.value()?.before} to ${mon.value()?.now}`); 346 this.count = this.storage.count; 347 } 348 build() { 349 NavDestination() { 350 Column() { 351 Text(`${this.count}`) 352 .fontSize(50) 353 .onClick(() => { 354 this.count++; 355 }) 356 Button('change Storage Count') 357 .onClick(() => { 358 this.storage.count += 100; 359 }) 360 } 361 } 362 .onReady((context: NavDestinationContext) => { 363 this.pathStack = context.pathStack; 364 }) 365 } 366} 367``` 368 369**自定义组件接收LocalStorage实例场景** 370 371为了配合Navigation的场景,LocalStorage支持作为自定义组件的入参,传递给以当前自定义组件为根节点的所有子自定义组件。 372对于该场景,V2可以使用创建多个全局\@ObservedV2和\@Trace装饰类的实例进行替代。 373 374V1: 375 376```ts 377let localStorageA: LocalStorage = new LocalStorage(); 378localStorageA.setOrCreate('PropA', 'PropA'); 379 380let localStorageB: LocalStorage = new LocalStorage(); 381localStorageB.setOrCreate('PropB', 'PropB'); 382 383let localStorageC: LocalStorage = new LocalStorage(); 384localStorageC.setOrCreate('PropC', 'PropC'); 385 386@Entry 387@Component 388struct MyNavigationTestStack { 389 @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack(); 390 391 @Builder 392 PageMap(name: string) { 393 if (name === 'pageOne') { 394 // 传递不同的LocalStorage实例 395 PageOneStack({}, localStorageA) 396 } else if (name === 'pageTwo') { 397 PageTwoStack({}, localStorageB) 398 } else if (name === 'pageThree') { 399 PageThreeStack({}, localStorageC) 400 } 401 } 402 403 build() { 404 Column({ space: 5 }) { 405 Navigation(this.pageInfo) { 406 Column() { 407 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 408 .width('80%') 409 .height(40) 410 .margin(20) 411 .onClick(() => { 412 this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈 413 }) 414 } 415 }.title('NavIndex') 416 .navDestination(this.PageMap) 417 .mode(NavigationMode.Stack) 418 .borderWidth(1) 419 } 420 } 421} 422 423@Component 424struct PageOneStack { 425 @Consume('pageInfo') pageInfo: NavPathStack; 426 @LocalStorageLink('PropA') PropA: string = 'Hello World'; 427 428 build() { 429 NavDestination() { 430 Column() { 431 // 显示'PropA' 432 NavigationContentMsgStack() 433 // 显示'PropA' 434 Text(`${this.PropA}`) 435 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 436 .width('80%') 437 .height(40) 438 .margin(20) 439 .onClick(() => { 440 this.pageInfo.pushPathByName('pageTwo', null); 441 }) 442 }.width('100%').height('100%') 443 }.title('pageOne') 444 .onBackPressed(() => { 445 this.pageInfo.pop(); 446 return true; 447 }) 448 } 449} 450 451@Component 452struct PageTwoStack { 453 @Consume('pageInfo') pageInfo: NavPathStack; 454 @LocalStorageLink('PropB') PropB: string = 'Hello World'; 455 456 build() { 457 NavDestination() { 458 Column() { 459 // 显示'Hello',当前LocalStorage实例localStorageB没有PropA对应的值,使用本地默认值'Hello' 460 NavigationContentMsgStack() 461 // 显示'PropB' 462 Text(`${this.PropB}`) 463 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 464 .width('80%') 465 .height(40) 466 .margin(20) 467 .onClick(() => { 468 this.pageInfo.pushPathByName('pageThree', null); 469 }) 470 471 }.width('100%').height('100%') 472 }.title('pageTwo') 473 .onBackPressed(() => { 474 this.pageInfo.pop(); 475 return true; 476 }) 477 } 478} 479 480@Component 481struct PageThreeStack { 482 @Consume('pageInfo') pageInfo: NavPathStack; 483 @LocalStorageLink('PropC') PropC: string = 'pageThreeStack'; 484 485 build() { 486 NavDestination() { 487 Column() { 488 // 显示'Hello',当前LocalStorage实例localStorageC没有PropA对应的值,使用本地默认值'Hello' 489 NavigationContentMsgStack() 490 // 显示'PropC' 491 Text(`${this.PropC}`) 492 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 493 .width('80%') 494 .height(40) 495 .margin(20) 496 .onClick(() => { 497 this.pageInfo.pushPathByName('pageOne', null); 498 }) 499 500 }.width('100%').height('100%') 501 }.title('pageThree') 502 .onBackPressed(() => { 503 this.pageInfo.pop(); 504 return true; 505 }) 506 } 507} 508 509@Component 510struct NavigationContentMsgStack { 511 @LocalStorageLink('PropA') PropA: string = 'Hello'; 512 513 build() { 514 Column() { 515 Text(`${this.PropA}`) 516 .fontSize(30) 517 .fontWeight(FontWeight.Bold) 518 } 519 } 520} 521``` 522V2: 523 524声明\@ObservedV2装饰的class代替LocalStorage。其中LocalStorage的key可以用\@Trace装饰的属性代替。 525```ts 526// storage.ets 527@ObservedV2 528export class MyStorageA { 529 @Trace propA: string = 'Hello'; 530 531 constructor(propA?: string) { 532 this.propA = propA ? propA : this.propA; 533 } 534} 535 536@ObservedV2 537export class MyStorageB extends MyStorageA { 538 @Trace propB: string = 'Hello'; 539 540 constructor(propB: string) { 541 super(); 542 this.propB = propB; 543 } 544} 545 546@ObservedV2 547export class MyStorageC extends MyStorageA { 548 @Trace propC: string = 'Hello'; 549 550 constructor(propC: string) { 551 super(); 552 this.propC = propC; 553 } 554} 555``` 556 557在`pageOneStack`、`pageTwoStack`和`pageThreeStack`组件内分别创建`MyStorageA`、`MyStorageB`、`MyStorageC`的实例,并通过\@Param传递给其子组件`NavigationContentMsgStack`,从而实现类似LocalStorage实例在子组件树上共享的能力。 558 559```ts 560// Index.ets 561import { MyStorageA, MyStorageB, MyStorageC } from './storage'; 562 563@Entry 564@ComponentV2 565struct MyNavigationTestStack { 566 pageInfo: NavPathStack = new NavPathStack(); 567 568 @Builder 569 PageMap(name: string) { 570 if (name === 'pageOne') { 571 pageOneStack() 572 } else if (name === 'pageTwo') { 573 pageTwoStack() 574 } else if (name === 'pageThree') { 575 pageThreeStack() 576 } 577 } 578 579 build() { 580 Column({ space: 5 }) { 581 Navigation(this.pageInfo) { 582 Column() { 583 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 584 .width('80%') 585 .height(40) 586 .margin(20) 587 .onClick(() => { 588 this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈 589 }) 590 } 591 }.title('NavIndex') 592 .navDestination(this.PageMap) 593 .mode(NavigationMode.Stack) 594 .borderWidth(1) 595 } 596 } 597} 598 599@ComponentV2 600struct pageOneStack { 601 pageInfo: NavPathStack = new NavPathStack(); 602 @Local storageA: MyStorageA = new MyStorageA('PropA'); 603 604 build() { 605 NavDestination() { 606 Column() { 607 // 显示'PropA' 608 NavigationContentMsgStack({storage: this.storageA}) 609 // 显示'PropA' 610 Text(`${this.storageA.propA}`) 611 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 612 .width('80%') 613 .height(40) 614 .margin(20) 615 .onClick(() => { 616 this.pageInfo.pushPathByName('pageTwo', null); 617 }) 618 }.width('100%').height('100%') 619 }.title('pageOne') 620 .onBackPressed(() => { 621 this.pageInfo.pop(); 622 return true; 623 }) 624 .onReady((context: NavDestinationContext) => { 625 this.pageInfo = context.pathStack; 626 }) 627 } 628} 629 630@ComponentV2 631struct pageTwoStack { 632 pageInfo: NavPathStack = new NavPathStack(); 633 @Local storageB: MyStorageB = new MyStorageB('PropB'); 634 635 build() { 636 NavDestination() { 637 Column() { 638 // 显示'Hello' 639 NavigationContentMsgStack({ storage: this.storageB }) 640 // 显示'PropB' 641 Text(`${this.storageB.propB}`) 642 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 643 .width('80%') 644 .height(40) 645 .margin(20) 646 .onClick(() => { 647 this.pageInfo.pushPathByName('pageThree', null); 648 }) 649 650 }.width('100%').height('100%') 651 }.title('pageTwo') 652 .onBackPressed(() => { 653 this.pageInfo.pop(); 654 return true; 655 }) 656 .onReady((context: NavDestinationContext) => { 657 this.pageInfo = context.pathStack; 658 }) 659 } 660} 661 662@ComponentV2 663struct pageThreeStack { 664 pageInfo: NavPathStack = new NavPathStack(); 665 @Local storageC: MyStorageC = new MyStorageC("PropC"); 666 667 build() { 668 NavDestination() { 669 Column() { 670 // 显示'Hello' 671 NavigationContentMsgStack({ storage: this.storageC }) 672 // 显示'PropC' 673 Text(`${this.storageC.propC}`) 674 Button('Next Page', { stateEffect: true, type: ButtonType.Capsule }) 675 .width('80%') 676 .height(40) 677 .margin(20) 678 .onClick(() => { 679 this.pageInfo.pushPathByName('pageOne', null); 680 }) 681 682 }.width('100%').height('100%') 683 }.title('pageThree') 684 .onBackPressed(() => { 685 this.pageInfo.pop(); 686 return true; 687 }) 688 .onReady((context: NavDestinationContext) => { 689 this.pageInfo = context.pathStack; 690 }) 691 } 692} 693 694@ComponentV2 695struct NavigationContentMsgStack { 696 @Require@Param storage: MyStorageA; 697 698 build() { 699 Column() { 700 Text(`${this.storage.propA}`) 701 .fontSize(30) 702 .fontWeight(FontWeight.Bold) 703 } 704 } 705} 706``` 707 708### AppStorage->AppStorageV2 709上一小节中,对于创建全局\@ObserveV2和\@Trace装饰实例的改造不适用于跨Ability的数据共享,可以使用AppStorageV2替代。 710 711V1: 712 713AppStorage与应用进程绑定,支持跨Ability数据共享。 714在下面的示例中,使用\@StorageLink,可以使得开发者本地的修改同步回AppStorage中。 715 716```ts 717// EntryAbility Index.ets 718import { common, Want } from '@kit.AbilityKit'; 719@Entry 720@Component 721struct Index { 722 @StorageLink('count') count: number = 0; 723 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 724 725 build() { 726 Column() { 727 Text(`EntryAbility count: ${this.count}`) 728 .fontSize(50) 729 .onClick(() => { 730 this.count++; 731 }) 732 Button('Jump to EntryAbility1').onClick(() => { 733 let wantInfo: Want = { 734 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 735 abilityName: 'EntryAbility1' 736 }; 737 this.context.startAbility(wantInfo); 738 }) 739 } 740 } 741} 742``` 743 744``` 745// EntryAbility1 Index1.ets 746import { common, Want } from '@kit.AbilityKit'; 747@Entry 748@Component 749struct Index1 { 750 @StorageLink('count') count: number = 0; 751 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 752 753 build() { 754 Column() { 755 Text(`EntryAbility1 count: ${this.count}`) 756 .fontSize(50) 757 .onClick(() => { 758 this.count++; 759 }) 760 Button('Jump to EntryAbility').onClick(() => { 761 let wantInfo: Want = { 762 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 763 abilityName: 'EntryAbility' 764 }; 765 this.context.startAbility(wantInfo); 766 }) 767 } 768 } 769} 770``` 771V2: 772 773可以使用AppStorageV2实现跨Ability共享。 774如下面示例: 775 776``` 777import { common, Want } from '@kit.AbilityKit'; 778import { AppStorageV2 } from '@kit.ArkUI'; 779 780@ObservedV2 781export class MyStorage { 782 @Trace count: number = 0 783} 784 785@Entry 786@ComponentV2 787struct Index { 788 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 789 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 790 791 build() { 792 Column() { 793 Text(`EntryAbility1 count: ${this.storage.count}`) 794 .fontSize(50) 795 .onClick(() => { 796 this.storage.count++; 797 }) 798 Button('Jump to EntryAbility1').onClick(() => { 799 let wantInfo: Want = { 800 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 801 abilityName: 'EntryAbility1' 802 }; 803 this.context.startAbility(wantInfo); 804 }) 805 } 806 } 807} 808 809``` 810 811``` 812import { common, Want } from '@kit.AbilityKit'; 813import { AppStorageV2 } from '@kit.ArkUI'; 814 815@ObservedV2 816export class MyStorage { 817 @Trace count: number = 0 818} 819 820@Entry 821@ComponentV2 822struct Index1 { 823 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 824 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 825 826 build() { 827 Column() { 828 Text(`EntryAbility1 count: ${this.storage.count}`) 829 .fontSize(50) 830 .onClick(() => { 831 this.storage.count++; 832 }) 833 Button('Jump to EntryAbility').onClick(() => { 834 let wantInfo: Want = { 835 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 836 abilityName: 'EntryAbility' 837 }; 838 this.context.startAbility(wantInfo); 839 }) 840 } 841 } 842} 843``` 844 845如果开发者需要实现类似于\@StorageProp的效果,希望本地的修改不同步回AppStorage,而AppStorage的变化能够通知到使用\@StorageProp装饰器的组件,可以参考以下示例对比。 846 847V1: 848 849```ts 850// EntryAbility Index.ets 851import { common, Want } from '@kit.AbilityKit'; 852@Entry 853@Component 854struct Index { 855 @StorageProp('count') count: number = 0; 856 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 857 858 build() { 859 Column() { 860 Text(`EntryAbility count: ${this.count}`) 861 .fontSize(25) 862 .onClick(() => { 863 this.count++; 864 }) 865 Button('change Storage Count') 866 .onClick(() => { 867 AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100); 868 }) 869 Button('Jump to EntryAbility1').onClick(() => { 870 let wantInfo: Want = { 871 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 872 abilityName: 'EntryAbility1' 873 }; 874 this.context.startAbility(wantInfo); 875 }) 876 } 877 } 878} 879``` 880 881```ts 882// EntryAbility1 Index1.ets 883import { common, Want } from '@kit.AbilityKit'; 884@Entry 885@Component 886struct Index1 { 887 @StorageProp('count') count: number = 0; 888 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 889 890 build() { 891 Column() { 892 Text(`EntryAbility1 count: ${this.count}`) 893 .fontSize(50) 894 .onClick(() => { 895 this.count++; 896 }) 897 Button('change Storage Count') 898 .onClick(() => { 899 AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100); 900 }) 901 Button('Jump to EntryAbility').onClick(() => { 902 let wantInfo: Want = { 903 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 904 abilityName: 'EntryAbility' 905 }; 906 this.context.startAbility(wantInfo); 907 }) 908 } 909 } 910} 911``` 912 913V2: 914 915开发者可以使用\@Monitor和\@Local实现类似效果,示例如下。 916 917```ts 918import { common, Want } from '@kit.AbilityKit'; 919import { AppStorageV2 } from '@kit.ArkUI'; 920 921@ObservedV2 922export class MyStorage { 923 @Trace count: number = 0; 924} 925 926@Entry 927@ComponentV2 928struct Index { 929 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 930 @Local count: number = this.storage.count; 931 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 932 933 @Monitor('storage.count') 934 onCountChange(mon: IMonitor) { 935 console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`); 936 this.count = this.storage.count; 937 } 938 build() { 939 Column() { 940 Text(`EntryAbility1 count: ${this.count}`) 941 .fontSize(25) 942 .onClick(() => { 943 this.count++; 944 }) 945 Button('change Storage Count') 946 .onClick(() => { 947 this.storage.count += 100; 948 }) 949 Button('Jump to EntryAbility1').onClick(() => { 950 let wantInfo: Want = { 951 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 952 abilityName: 'EntryAbility1' 953 }; 954 this.context.startAbility(wantInfo); 955 }) 956 } 957 } 958} 959``` 960 961```ts 962import { common, Want } from '@kit.AbilityKit'; 963import { AppStorageV2 } from '@kit.ArkUI'; 964 965@ObservedV2 966export class MyStorage { 967 @Trace count: number = 0; 968} 969 970@Entry 971@ComponentV2 972struct Index1 { 973 @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!; 974 @Local count: number = this.storage.count; 975 private context = this.getUIContext().getHostContext() as common.UIAbilityContext; 976 977 @Monitor('storage.count') 978 onCountChange(mon: IMonitor) { 979 console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`); 980 this.count = this.storage.count; 981 } 982 983 build() { 984 Column() { 985 Text(`EntryAbility1 count: ${this.count}`) 986 .fontSize(25) 987 .onClick(() => { 988 this.count++; 989 }) 990 Button('change Storage Count') 991 .onClick(() => { 992 this.storage.count += 100; 993 }) 994 Button('Jump to EntryAbility').onClick(() => { 995 let wantInfo: Want = { 996 bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName 997 abilityName: 'EntryAbility' 998 }; 999 this.context.startAbility(wantInfo); 1000 }) 1001 } 1002 } 1003} 1004``` 1005 1006### Environment->调用Ability接口直接获取系统环境变量 1007V1中,开发者可以通过Environment来获取环境变量,但Environment获取的结果无法直接使用,需要配合AppStorage才能得到对应环境变量的值。 1008在切换V2的过程中,开发者无需再通过Environment来获取环境变量,可以直接通过[UIAbilityContext的config属性](../../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#uiabilitycontext-1)获取系统环境变量。 1009 1010V1: 1011 1012以`languageCode`为例。 1013```ts 1014// 将设备languageCode存入AppStorage中 1015Environment.envProp('languageCode', 'en'); 1016 1017@Entry 1018@Component 1019struct Index { 1020 @StorageProp('languageCode') languageCode: string = 'en'; 1021 build() { 1022 Row() { 1023 Column() { 1024 // 输出当前设备的languageCode 1025 Text(this.languageCode) 1026 } 1027 } 1028 } 1029} 1030``` 1031 1032V2: 1033 1034封装Env类型来传递多个系统环境变量。 1035 1036``` 1037// Env.ets 1038import { ConfigurationConstant } from '@kit.AbilityKit'; 1039 1040export class Env { 1041 language: string | undefined; 1042 colorMode: ConfigurationConstant.ColorMode | undefined; 1043 fontSizeScale: number | undefined; 1044 fontWeightScale: number | undefined; 1045} 1046 1047export let env: Env = new Env(); 1048``` 1049在`onCreate`里获取需要的系统环境变量: 1050``` 1051// EntryAbility.ets 1052import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit'; 1053import { window } from '@kit.ArkUI'; 1054import { env } from '../pages/Env'; 1055 1056export default class EntryAbility extends UIAbility { 1057 onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { 1058 env.language = this.context.config.language; 1059 env.colorMode = this.context.config.colorMode; 1060 env.fontSizeScale = this.context.config.fontSizeScale; 1061 env.fontWeightScale = this.context.config.fontWeightScale; 1062 } 1063 1064 onWindowStageCreate(windowStage: window.WindowStage): void { 1065 windowStage.loadContent('pages/Index'); 1066 } 1067} 1068 1069``` 1070在页面中获取当前Env的值。 1071``` 1072// Index.ets 1073import { env } from '../pages/Env'; 1074 1075@Entry 1076@ComponentV2 1077struct Index { 1078 build() { 1079 Row() { 1080 Column() { 1081 // 输出当前设备的环境变量 1082 Text(`languageCode: ${env.language}`).fontSize(20) 1083 Text(`colorMode: ${env.colorMode}`).fontSize(20) 1084 Text(`fontSizeScale: ${env.fontSizeScale}`).fontSize(20) 1085 Text(`fontWeightScale: ${env.fontWeightScale}`).fontSize(20) 1086 } 1087 } 1088 } 1089} 1090``` 1091 1092### PersistentStorage->PersistenceV2 1093V1中PersistentStorage提供了持久化UI数据的能力,而V2则提供了更加方便使用的PersistenceV2接口来替代它。 1094- PersistentStorage持久化的触发时机依赖AppStorage的观察能力,且与AppStorage耦合,开发者无法自主选择写入或读取持久化数据的时机。 1095- PersistentStorage使用序列化和反序列化,并没有传入类型,所以在持久化后,会丢失其类型,且对象的属性方法不能持久化。 1096 1097对于PersistenceV2: 1098- 与PersistenceV2关联的\@ObservedV2对象,其\@Trace属性的变化,会触发整个关联对象的自动持久化。 1099- 开发者也可以调用[PersistenceV2.save](../../reference/apis-arkui/js-apis-StateManagement.md#save)和[PersistenceV2.globalConnect](./arkts-new-persistencev2.md#使用globalconnect存储数据)接口来手动触发持久化写入和读取。 1100 1101V1: 1102 1103```ts 1104class data { 1105 name: string = 'ZhangSan'; 1106 id: number = 0; 1107} 1108 1109PersistentStorage.persistProp('numProp', 47); 1110PersistentStorage.persistProp('dataProp', new data()); 1111 1112@Entry 1113@Component 1114struct Index { 1115 @StorageLink('numProp') numProp: number = 48; 1116 @StorageLink('dataProp') dataProp: data = new data(); 1117 1118 build() { 1119 Column() { 1120 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1121 Text(`numProp: ${this.numProp}`) 1122 .onClick(() => { 1123 this.numProp += 1; 1124 }) 1125 .fontSize(30) 1126 1127 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1128 Text(`dataProp.name: ${this.dataProp.name}`) 1129 .onClick(() => { 1130 this.dataProp.name += 'a'; 1131 }) 1132 .fontSize(30) 1133 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1134 Text(`dataProp.id: ${this.dataProp.id}`) 1135 .onClick(() => { 1136 this.dataProp.id += 1; 1137 }) 1138 .fontSize(30) 1139 1140 } 1141 .width('100%') 1142 } 1143} 1144``` 1145 1146V2: 1147 1148下面的案例展示了: 1149- 将`PersistentStorage`的持久化数据迁移到V2的PersistenceV2中。V2对被\@Trace标记的数据可以自动持久化,对于非\@Trace数据,需要手动调用save进行持久化。 1150- 示例中的move函数和需要显示的组件放在了一个ets中,开发者可以定义自己的move函数,并放入合适的位置进行统一迁移操作。 1151```ts 1152// 迁移到globalConnect 1153import { PersistenceV2, Type } from '@kit.ArkUI'; 1154 1155// 接受序列化失败的回调 1156PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => { 1157 console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`); 1158}); 1159 1160class Data { 1161 name: string = 'ZhangSan'; 1162 id: number = 0; 1163} 1164 1165@ObservedV2 1166class V2Data { 1167 @Trace name: string = ''; 1168 @Trace Id: number = 1; 1169} 1170 1171@ObservedV2 1172export class Sample { 1173 // 对于复杂对象需要@Type修饰,确保序列化成功 1174 @Type(V2Data) 1175 @Trace num: number = 1; 1176 @Trace V2: V2Data = new V2Data(); 1177} 1178 1179// 用于判断是否完成数据迁移的辅助数据 1180@ObservedV2 1181class StorageState { 1182 @Trace isCompleteMoving: boolean = false; 1183} 1184 1185function move() { 1186 let movingState = PersistenceV2.globalConnect({type: StorageState, defaultCreator: () => new StorageState()})!; 1187 if (!movingState.isCompleteMoving) { 1188 PersistentStorage.persistProp('numProp', 47); 1189 PersistentStorage.persistProp('dataProp', new Data()); 1190 let num = AppStorage.get<number>('numProp')!; 1191 let V1Data = AppStorage.get<Data>('dataProp')!; 1192 PersistentStorage.deleteProp('numProp'); 1193 PersistentStorage.deleteProp('dataProp'); 1194 1195 // V2创建对应数据 1196 let migrate = PersistenceV2.globalConnect({type: Sample, key: 'connect2', defaultCreator: () => new Sample()})!; // 使用默认构造函数也可以 1197 // 赋值数据,@Trace修饰的会自动保存,对于非@Trace对象,也可以调用save保存,如:PersistenceV2.save('connect2'); 1198 migrate.num = num; 1199 migrate.V2.name = V1Data.name; 1200 migrate.V2.Id = V1Data.id; 1201 1202 // 将迁移标志设置为true 1203 movingState.isCompleteMoving = true; 1204 } 1205} 1206 1207move(); 1208 1209@Entry 1210@ComponentV2 1211struct Page1 { 1212 @Local refresh: number = 0; 1213 // 使用key:connect2存入数据 1214 @Local p: Sample = PersistenceV2.globalConnect({type: Sample, key:'connect2', defaultCreator:() => new Sample()})!; 1215 1216 build() { 1217 Column({space: 5}) { 1218 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1219 Text(`numProp: ${this.p.num}`) 1220 .onClick(() => { 1221 this.p.num += 1; 1222 }) 1223 .fontSize(30) 1224 1225 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1226 Text(`dataProp.name: ${this.p.V2.name}`) 1227 .onClick(() => { 1228 this.p.V2.name += 'a'; 1229 }) 1230 .fontSize(30) 1231 // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果 1232 Text(`dataProp.id: ${this.p.V2.Id}`) 1233 .onClick(() => { 1234 this.p.V2.Id += 1; 1235 }) 1236 .fontSize(30) 1237 } 1238 .width('100%') 1239 } 1240} 1241``` 1242 1243## V1现有功能向V2的逐步迁移场景 1244 1245对于已经使用V1开发的大型应用,通常难以一次性从V1迁移到V2,而是需要分批次、分组件地逐步迁移,这就必然会带来V1和V2的混用。 1246 1247这种场景,通常父组件使用状态管理V1,而迁移的子组件使用状态管理V2。为了模拟这种场景,我们举出以下示例: 1248- 父组件是\@Component,数据源是\@LocalStorageLink。 1249- 子组件是\@ComponentV2,使用\@Param接受数据源的数据。 1250 1251可以通过以下策略进行迁移: 1252- 声明一个\@ObservedV2装饰的class来封装V1的数据。 1253- 在\@Component和\@ComponentV2之间,定义一个桥接的\@Component自定义组件。 1254- 在桥接层: 1255 - V1->V2的数据同步,可通过\@Watch的监听触发\@ObservedV2装饰的class的属性的赋值。 1256 - V2->V1的数据同步,可通过在\@ObservedV2装饰的class里声明Monitor,通过LocalStorage的API反向通知给V1状态变量。 1257 1258具体示例如下: 1259```ts 1260let storage: LocalStorage = new LocalStorage(); 1261 1262@ObservedV2 1263class V1StorageData { 1264 @Trace title: string = 'V1OldComponent' 1265 @Monitor('title') 1266 onStrChange(monitor: IMonitor) { 1267 monitor.dirty.forEach((path: string) => { 1268 console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`) 1269 if (path === 'title') { 1270 storage.setOrCreate('title', this.title); 1271 } 1272 }) 1273 } 1274} 1275let v1Data: V1StorageData = new V1StorageData(); 1276 1277@Entry(storage) 1278@Component 1279struct V1OldComponent { 1280 @LocalStorageLink('title') title: string = 'V1OldComponent'; 1281 1282 build() { 1283 Column() { 1284 Text(`V1OldComponent: ${this.title}`) 1285 .fontSize(20) 1286 .onClick(() => { 1287 this.title = 'new value from V1OldComponent'; 1288 }) 1289 // 定义一个桥接的\@Component自定义组件,用于V1和V2的变量相互同步 1290 Bridge() 1291 } 1292 } 1293} 1294 1295 1296@Component 1297struct Bridge { 1298 @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge'; 1299 titleWatch() { 1300 v1Data.title = this.title; 1301 } 1302 1303 build() { 1304 NewV2Component() 1305 } 1306} 1307@ComponentV2 1308struct NewV2Component { 1309 build() { 1310 Column() { 1311 Text(`NewV2Component: ${v1Data.title}`) 1312 .fontSize(20) 1313 .onClick(() => { 1314 v1Data.title = 'NewV2Component'; 1315 }) 1316 } 1317 } 1318} 1319``` 1320 1321## 其他迁移场景 1322 1323### 滑动组件 1324 1325**List** 1326 1327开发者可以通过[ChildrenMainSize](../../reference/apis-arkui/arkui-ts/ts-container-list.md#childrenmainsize12)来设置List的子组件在主轴方向的大小信息。 1328 1329V1: 1330 1331在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 1332 1333具体示例如下: 1334 1335```ts 1336@Entry 1337@Component 1338struct ListExample { 1339 private arr: Array<number> = new Array(10).fill(0); 1340 private scroller: ListScroller = new ListScroller(); 1341 @State listSpace: number = 10; 1342 @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100); 1343 1344 build() { 1345 Column() { 1346 Button('change Default').onClick(() => { 1347 this.listChildrenSize.childDefaultSize += 10; 1348 }) 1349 1350 Button('splice 5').onClick(() => { 1351 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 1352 }) 1353 1354 Button('update 5').onClick(() => { 1355 this.listChildrenSize.update(0, 200); 1356 }) 1357 1358 List({ space: this.listSpace, scroller: this.scroller }) { 1359 ForEach(this.arr, (item: number) => { 1360 ListItem() { 1361 Text(`item-` + item) 1362 }.backgroundColor(Color.Pink) 1363 }) 1364 } 1365 .childrenMainSize(this.listChildrenSize) // 10 1366 } 1367 } 1368} 1369``` 1370 1371V2: 1372 1373在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,而由于ChildrenMainSize定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注ChildrenMainSize的属性。可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 1374 1375具体示例如下: 1376 1377```ts 1378import { UIUtils } from '@kit.ArkUI'; 1379 1380@Entry 1381@ComponentV2 1382struct ListExample { 1383 private arr: Array<number> = new Array(10).fill(0); 1384 private scroller: ListScroller = new ListScroller(); 1385 listSpace: number = 10; 1386 // 使用makeObserved的能力来观测ChildrenMainSize 1387 listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100)); 1388 1389 build() { 1390 Column() { 1391 Button('change Default').onClick(() => { 1392 this.listChildrenSize.childDefaultSize += 10; 1393 }) 1394 1395 Button('splice 5').onClick(() => { 1396 this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]); 1397 }) 1398 1399 Button('update 5').onClick(() => { 1400 this.listChildrenSize.update(0, 200); 1401 }) 1402 1403 List({ space: this.listSpace, scroller: this.scroller }) { 1404 ForEach(this.arr, (item: number) => { 1405 ListItem() { 1406 Text(`item-` + item) 1407 }.backgroundColor(Color.Pink) 1408 }) 1409 } 1410 .childrenMainSize(this.listChildrenSize) // 10 1411 } 1412 } 1413} 1414``` 1415 1416**WaterFlow** 1417 1418开发者可以通过[WaterFlowSections](../../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12)来设置WaterFlow瀑布流分组信息。 1419 1420需要注意的是,数组arr的长度需要与WaterFlowSections的所有SectionOptions的itemsCount总和一致,否则WaterFlow无法处理,导致UI不刷新。 1421 1422以下两个示例请按照'push option' -> 'splice option' -> 'update option'的顺序进行点击。 1423 1424V1: 1425 1426在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。 1427 1428具体示例如下: 1429 1430```ts 1431@Entry 1432@Component 1433struct WaterFlowSample { 1434 @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 1435 @State sections: WaterFlowSections = new WaterFlowSections(); 1436 scroller: Scroller = new Scroller(); 1437 @State private arr: Array<number> = new Array(9).fill(0); 1438 oneColumnSection: SectionOptions = { 1439 itemsCount: 4, 1440 crossCount: 1, 1441 columnsGap: '5vp', 1442 rowsGap: 10, 1443 }; 1444 twoColumnSection: SectionOptions = { 1445 itemsCount: 2, 1446 crossCount: 2, 1447 }; 1448 lastSection: SectionOptions = { 1449 itemsCount: 3, 1450 crossCount: 3, 1451 }; 1452 1453 aboutToAppear(): void { 1454 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 1455 this.sections.splice(0, 0, sectionOptions); 1456 } 1457 1458 build() { 1459 Column() { 1460 Text(`${this.arr.length}`) 1461 1462 Button('push option').onClick(() => { 1463 let section: SectionOptions = { 1464 itemsCount: 1, 1465 crossCount: 1, 1466 }; 1467 this.sections.push(section); 1468 this.arr.push(100); 1469 }) 1470 1471 Button('splice option').onClick(() => { 1472 let section: SectionOptions = { 1473 itemsCount: 8, 1474 crossCount: 2, 1475 }; 1476 this.sections.splice(0, this.arr.length, [section]); 1477 this.arr = new Array(8).fill(10); 1478 }) 1479 1480 Button('update option').onClick(() => { 1481 let section: SectionOptions = { 1482 itemsCount: 8, 1483 crossCount: 2, 1484 }; 1485 this.sections.update(1, section); 1486 this.arr = new Array(16).fill(1); 1487 }) 1488 1489 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 1490 ForEach(this.arr, (item: number) => { 1491 FlowItem() { 1492 Text(`${item}`) 1493 .border({ width: 1 }) 1494 .backgroundColor(this.colors[item % 6]) 1495 .height(30) 1496 .width(50) 1497 } 1498 }) 1499 } 1500 } 1501 } 1502} 1503``` 1504 1505V2: 1506 1507在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,由于WaterFlowSections定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)标注其属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 1508 1509具体示例如下: 1510 1511```ts 1512import { UIUtils } from '@kit.ArkUI'; 1513 1514@Entry 1515@ComponentV2 1516struct WaterFlowSample { 1517 colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink]; 1518 // 使用makeObserved的能力来观测WaterFlowSections 1519 sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections()); 1520 scroller: Scroller = new Scroller(); 1521 @Local private arr: Array<number> = new Array(9).fill(0); 1522 oneColumnSection: SectionOptions = { 1523 itemsCount: 4, 1524 crossCount: 1, 1525 columnsGap: '5vp', 1526 rowsGap: 10, 1527 }; 1528 twoColumnSection: SectionOptions = { 1529 itemsCount: 2, 1530 crossCount: 2, 1531 }; 1532 lastSection: SectionOptions = { 1533 itemsCount: 3, 1534 crossCount: 3, 1535 }; 1536 1537 aboutToAppear(): void { 1538 let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection]; 1539 this.sections.splice(0, 0, sectionOptions); 1540 } 1541 1542 build() { 1543 Column() { 1544 Text(`${this.arr.length}`) 1545 1546 Button('push option').onClick(() => { 1547 let section: SectionOptions = { 1548 itemsCount: 1, 1549 crossCount: 1, 1550 }; 1551 this.sections.push(section); 1552 this.arr.push(100); 1553 }) 1554 1555 Button('splice option').onClick(() => { 1556 let section: SectionOptions = { 1557 itemsCount: 8, 1558 crossCount: 2, 1559 }; 1560 this.sections.splice(0, this.arr.length, [section]); 1561 this.arr = new Array(8).fill(10); 1562 }) 1563 1564 Button('update option').onClick(() => { 1565 let section: SectionOptions = { 1566 itemsCount: 8, 1567 crossCount: 2, 1568 }; 1569 this.sections.update(1, section); 1570 this.arr = new Array(16).fill(1); 1571 }) 1572 1573 WaterFlow({ scroller: this.scroller, sections: this.sections }) { 1574 ForEach(this.arr, (item: number) => { 1575 FlowItem() { 1576 Text(`${item}`) 1577 .border({ width: 1 }) 1578 .backgroundColor(this.colors[item % 6]) 1579 .height(30) 1580 .width(50) 1581 } 1582 }) 1583 } 1584 } 1585 } 1586} 1587``` 1588 1589### Modifier 1590 1591**attributeModifier** 1592 1593开发者可以通过[attributeModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier)动态设置组件的属性方法。 1594 1595V1: 1596 1597在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 1598 1599具体示例如下: 1600 1601```ts 1602class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 1603 isDark: boolean = false; 1604 1605 applyNormalAttribute(instance: ButtonAttribute): void { 1606 if (this.isDark) { 1607 instance.backgroundColor(Color.Black); 1608 } else { 1609 instance.backgroundColor(Color.Red); 1610 } 1611 } 1612} 1613 1614@Entry 1615@Component 1616struct AttributeDemo { 1617 @State modifier: MyButtonModifier = new MyButtonModifier(); 1618 1619 build() { 1620 Row() { 1621 Column() { 1622 Button('Button') 1623 .attributeModifier(this.modifier) 1624 .onClick(() => { 1625 this.modifier.isDark = !this.modifier.isDark; 1626 }) 1627 } 1628 .width('100%') 1629 } 1630 .height('100%') 1631 } 1632} 1633``` 1634 1635V2: 1636 1637在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,如果要观察attributeModifier的属性变化,可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 1638 1639具体示例如下: 1640 1641```ts 1642import { UIUtils } from '@kit.ArkUI'; 1643 1644class MyButtonModifier implements AttributeModifier<ButtonAttribute> { 1645 isDark: boolean = false; 1646 1647 applyNormalAttribute(instance: ButtonAttribute): void { 1648 if (this.isDark) { 1649 instance.backgroundColor(Color.Black); 1650 } else { 1651 instance.backgroundColor(Color.Red); 1652 } 1653 } 1654} 1655 1656@Entry 1657@ComponentV2 1658struct AttributeDemo { 1659 // 使用makeObserved的能力观测attributeModifier的属性this.modifier 1660 modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier()); 1661 1662 build() { 1663 Row() { 1664 Column() { 1665 Button('Button') 1666 .attributeModifier(this.modifier) 1667 .onClick(() => { 1668 this.modifier.isDark = !this.modifier.isDark; 1669 }) 1670 } 1671 .width('100%') 1672 } 1673 .height('100%') 1674 } 1675} 1676``` 1677 1678**CommonModifier** 1679 1680动态设置组件的属性类。以[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)为例。 1681 1682V1: 1683 1684在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 1685 1686具体实例如下: 1687 1688```ts 1689import { CommonModifier } from '@ohos.arkui.modifier'; 1690 1691class MyModifier extends CommonModifier { 1692 applyNormalAttribute(instance: CommonAttribute): void { 1693 super.applyNormalAttribute?.(instance); 1694 } 1695 1696 public setGroup1(): void { 1697 this.borderStyle(BorderStyle.Dotted); 1698 this.borderWidth(8); 1699 } 1700 1701 public setGroup2(): void { 1702 this.borderStyle(BorderStyle.Dashed); 1703 this.borderWidth(8); 1704 } 1705} 1706 1707@Component 1708struct MyImage1 { 1709 @Link modifier: CommonModifier; 1710 1711 build() { 1712 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 1713 Image($r('app.media.app_icon')) 1714 .attributeModifier(this.modifier as MyModifier) 1715 } 1716} 1717 1718@Entry 1719@Component 1720struct Index { 1721 @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10); 1722 index: number = 0; 1723 1724 build() { 1725 Column() { 1726 Button($r('app.string.EntryAbility_label')) 1727 .margin(10) 1728 .onClick(() => { 1729 console.log('Modifier', 'onClick'); 1730 this.index++; 1731 if (this.index % 2 === 1) { 1732 (this.myModifier as MyModifier).setGroup1(); 1733 console.log('Modifier', 'setGroup1'); 1734 } else { 1735 (this.myModifier as MyModifier).setGroup2(); 1736 console.log('Modifier', 'setGroup2'); 1737 } 1738 }) 1739 1740 MyImage1({ modifier: this.myModifier }) 1741 } 1742 .width('100%') 1743 } 1744} 1745``` 1746 1747V2: 1748 1749在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)在框架内是通过其属性触发刷新,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 1750 1751具体示例如下: 1752 1753```ts 1754import { UIUtils } from '@kit.ArkUI'; 1755import { CommonModifier } from '@ohos.arkui.modifier'; 1756 1757class MyModifier extends CommonModifier { 1758 applyNormalAttribute(instance: CommonAttribute): void { 1759 super.applyNormalAttribute?.(instance); 1760 } 1761 1762 public setGroup1(): void { 1763 this.borderStyle(BorderStyle.Dotted); 1764 this.borderWidth(8); 1765 } 1766 1767 public setGroup2(): void { 1768 this.borderStyle(BorderStyle.Dashed); 1769 this.borderWidth(8); 1770 } 1771} 1772 1773@ComponentV2 1774struct MyImage1 { 1775 @Param @Require modifier: CommonModifier; 1776 1777 build() { 1778 // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。 1779 Image($r('app.media.app_icon')) 1780 .attributeModifier(this.modifier as MyModifier) 1781 } 1782} 1783 1784@Entry 1785@ComponentV2 1786struct Index { 1787 // 使用makeObserved的能力来观测CommonModifier 1788 @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 1789 index: number = 0; 1790 1791 build() { 1792 Column() { 1793 Button($r('app.string.EntryAbility_label')) 1794 .margin(10) 1795 .onClick(() => { 1796 console.log('Modifier', 'onClick'); 1797 this.index++; 1798 if (this.index % 2 === 1) { 1799 (this.myModifier as MyModifier).setGroup1(); 1800 console.log('Modifier', 'setGroup1'); 1801 } else { 1802 (this.myModifier as MyModifier).setGroup2(); 1803 console.log('Modifier', 'setGroup2'); 1804 } 1805 }) 1806 1807 MyImage1({ modifier: this.myModifier }) 1808 } 1809 .width('100%') 1810 } 1811} 1812``` 1813 1814**组件Modifier** 1815 1816动态设置组件的属性类。以Text组件为例。 1817 1818V1: 1819 1820在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。 1821 1822具体示例如下: 1823 1824```ts 1825import { TextModifier } from '@ohos.arkui.modifier'; 1826 1827class MyModifier extends TextModifier { 1828 applyNormalAttribute(instance: TextModifier): void { 1829 super.applyNormalAttribute?.(instance); 1830 } 1831 1832 public setGroup1(): void { 1833 this.fontSize(50); 1834 this.fontColor(Color.Pink); 1835 } 1836 1837 public setGroup2(): void { 1838 this.fontSize(50); 1839 this.fontColor(Color.Gray); 1840 } 1841} 1842 1843@Component 1844struct MyImage1 { 1845 @Link modifier: TextModifier; 1846 index: number = 0; 1847 1848 build() { 1849 Column() { 1850 Text('Test') 1851 .attributeModifier(this.modifier as MyModifier) 1852 1853 Button($r('app.string.EntryAbility_label')) 1854 .margin(10) 1855 .onClick(() => { 1856 console.log('Modifier', 'onClick'); 1857 this.index++; 1858 if (this.index % 2 === 1) { 1859 (this.modifier as MyModifier).setGroup1(); 1860 console.log('Modifier', 'setGroup1'); 1861 } else { 1862 (this.modifier as MyModifier).setGroup2(); 1863 console.log('Modifier', 'setGroup2'); 1864 } 1865 }) 1866 } 1867 } 1868} 1869 1870@Entry 1871@Component 1872struct Index { 1873 @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10); 1874 index: number = 0; 1875 1876 build() { 1877 Column() { 1878 MyImage1({ modifier: this.myModifier }) 1879 1880 Button('replace whole') 1881 .margin(10) 1882 .onClick(() => { 1883 this.myModifier = new MyModifier().backgroundColor(Color.Orange); 1884 }) 1885 } 1886 .width('100%') 1887 } 1888} 1889``` 1890 1891V2: 1892 1893但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。 1894 1895具体示例如下: 1896 1897```ts 1898import { UIUtils } from '@kit.ArkUI'; 1899import { TextModifier } from '@ohos.arkui.modifier'; 1900 1901class MyModifier extends TextModifier { 1902 applyNormalAttribute(instance: TextModifier): void { 1903 super.applyNormalAttribute?.(instance); 1904 } 1905 1906 public setGroup1(): void { 1907 this.fontSize(50); 1908 this.fontColor(Color.Pink); 1909 } 1910 1911 public setGroup2(): void { 1912 this.fontSize(50); 1913 this.fontColor(Color.Gray); 1914 } 1915} 1916 1917@ComponentV2 1918struct MyImage1 { 1919 @Param @Require modifier: TextModifier; 1920 index: number = 0; 1921 1922 build() { 1923 Column() { 1924 Text('Test') 1925 .attributeModifier(this.modifier as MyModifier) 1926 1927 Button($r('app.string.EntryAbility_label')) 1928 .margin(10) 1929 .onClick(() => { 1930 console.log('Modifier', 'onClick'); 1931 this.index++; 1932 if (this.index % 2 === 1) { 1933 (this.modifier as MyModifier).setGroup1(); 1934 console.log('Modifier', 'setGroup1'); 1935 } else { 1936 (this.modifier as MyModifier).setGroup2(); 1937 console.log('Modifier', 'setGroup2'); 1938 } 1939 }) 1940 } 1941 } 1942} 1943 1944@Entry 1945@ComponentV2 1946struct Index { 1947 // 使用makeObserved的能力观测TextModifier 1948 @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10)); 1949 index: number = 0; 1950 1951 build() { 1952 Column() { 1953 MyImage1({ modifier: this.myModifier }) 1954 1955 Button('replace whole') 1956 .margin(10) 1957 .onClick(() => { 1958 this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange)); 1959 }) 1960 } 1961 .width('100%') 1962 } 1963} 1964``` 1965**AttributeUpdater** 1966 1967[AttributeUpdater](../arkts-user-defined-extension-attributeUpdater.md)可以将属性直接设置给组件,无需标记为状态变量即可直接触发UI更新。 1968 1969V1: 1970 1971在状态管理V1中,开发者希望通过修改`MyButtonModifier`的`flag`来改变绑定在Button上的属性。由于状态管理V1的\@State装饰器支持自身及第一层对象属性的观察能力,因此只需用\@State装饰`AttributeUpdater`,即可监听其变化并触发属性更新。 1972 1973```ts 1974// xxx.ets 1975import { AttributeUpdater } from '@kit.ArkUI'; 1976 1977class MyButtonModifier extends AttributeUpdater<ButtonAttribute> { 1978 flag: boolean = false; 1979 1980 initializeModifier(instance: ButtonAttribute): void { 1981 instance.backgroundColor('#ff2787d9') 1982 .width('50%') 1983 .height(30) 1984 } 1985 1986 applyNormalAttribute(instance: ButtonAttribute): void { 1987 if (this.flag) { 1988 instance.borderWidth(2); 1989 } else { 1990 instance.borderWidth(10); 1991 } 1992 } 1993} 1994 1995@Entry 1996@Component 1997struct Index { 1998 @State modifier: MyButtonModifier = new MyButtonModifier(); 1999 2000 build() { 2001 Row() { 2002 Column() { 2003 Button('Button') 2004 .attributeModifier(this.modifier) 2005 Button('Update') 2006 .onClick(() => { 2007 this.modifier.flag = !this.modifier.flag; 2008 }) 2009 } 2010 .width('100%') 2011 } 2012 .height('100%') 2013 } 2014} 2015``` 2016 2017V2: 2018 2019与状态管理V1不同,状态管理V2的\@Local仅观察自身变化,因此`MyButtonModifier`需添加\@ObservedV2装饰器,`flag`需要被\@Trace装饰,并且需要在组件创建过程中读取`flag`以建立其与Button组件的联系。在`AttributeUpdater`场景中,需在`initializeModifier`中读取`flag`(如示例所示),否则无法建立关联。 2020 2021```ts 2022// xxx.ets 2023import { AttributeUpdater } from '@kit.ArkUI'; 2024 2025@ObservedV2 2026class MyButtonModifier extends AttributeUpdater<ButtonAttribute> { 2027 @Trace flag: boolean = false; 2028 2029 initializeModifier(instance: ButtonAttribute): void { 2030 // initializeModifier会在组件初始化阶段回调,需要在这个地方触发下flag的读,使其建立Button组件的关联。 2031 this.flag; 2032 instance.backgroundColor('#ff2787d9') 2033 .width('50%') 2034 .height(30) 2035 } 2036 2037 applyNormalAttribute(instance: ButtonAttribute): void { 2038 if (this.flag) { 2039 instance.borderWidth(2); 2040 } else { 2041 instance.borderWidth(10); 2042 } 2043 } 2044} 2045 2046@Entry 2047@ComponentV2 2048struct Index { 2049 // 状态管理V2装饰器仅观察本层,即当前可以观察到modifier整体赋值的变化。 2050 @Local modifier: MyButtonModifier = new MyButtonModifier(); 2051 2052 build() { 2053 Row() { 2054 Column() { 2055 Button('Button') 2056 .attributeModifier(this.modifier) 2057 Button('Update') 2058 .onClick(() => { 2059 this.modifier.flag = !this.modifier.flag; 2060 }) 2061 } 2062 .width('100%') 2063 } 2064 .height('100%') 2065 } 2066} 2067``` 2068