• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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