• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# V1->V2迁移指导
2
3## 概述
4ArkUI状态管理的主要职责是:负责将可观察数据的变化自动同步到UI界面,实现数据驱动的UI刷新,使开发者能更加够专注于UI界面的实现和设计。
5
6在状态管理框架的演进过程中,先后推出了状态管理V1和V2两个版本。V1强调组件层级的状态管理,而V2则增强了对数据对象的深度观察与管理能力,不再局限于组件层级。通过V2,开发者能够更灵活地控制数据和状态,实现更高效的UI刷新。具体V1和V2的区别可以参见[状态管理概述](./arkts-state-management-overview.md)。
7
8## V1V2使用指引
91. V2是V1的增强版本,正在持续迭代优化来为开发者提供更多功能和灵活性。
102. 对于新开发的应用,建议直接使用V2版本范式来进行开发。
113. 对于已经使用V1的应用,如果V1的功能和性能已能满足需求,则不必立即切换到V2。
124. 对于需要在现阶段混用V1和V2的场景,请参阅[混用文档](./arkts-custom-component-mixed-scenarios.md)。编译器、工具链、IDE对某些不推荐的误用和混用场景会进行校验,虽然开发者可能可以通过特殊手段绕过这些校验,但还是强烈建议开发者遵循[混用文档](./arkts-custom-component-mixed-scenarios.md)的指导,避免因双重代理等问题给应用带来不确定性。
13
14## 迁移指南的目的
151. 对希望将现有V1应用迁移到V2的开发者,提供系统化的模板和指导,帮助完成V1到V2的迁移。
162. 对希望逐步将V1应用过渡到V2的开发者,提供参考,结合本迁移文档与[混用文档](./arkts-custom-component-mixed-scenarios.md),可以帮助开发者实现逐步改造。
173. 尚未开始开发应用但已熟悉V1状态管理规则的开发者,可以参考本迁移文档及V2各个装饰器和接口的文档,开始使用V2进行应用开发。
18
19## V1V2能力对比及迁移简表
20| V1装饰器名                | V2装饰器名                  | 说明 |
21|------------------------|--------------------------|--------------------------|
22| \@Observed              | \@ObservedV2              | 表明当前对象为可观察对象。但两者能力并不相同。 <br/>\@Observed可观察第一层的属性,需要搭配\@ObjectLink使用才能生效。 <br/>\@ObservedV2本身无观察能力,仅代表当前class可被观察,如果要观察其属性,需要搭配\@Trace使用。  |
23| \@Track                 | \@Trace                   | V1装饰器\@Track为精确观察,不使用则无法做到类属性的精准观察。 <br/>V2\@Trace装饰的属性可以被精确跟踪观察。|
24| \@Component             | \@ComponentV2             | \@Component为搭配V1状态变量使用的自定义组件装饰器。<br/>@ComponentV2为搭配V2状态变量使用的自定义组件装饰器。 |
25|\@State                 | 无外部初始化:@Local<br/>外部初始化一次:\@Param\@Once | \@State和\@Local类似都是数据源的概念,在不需要外部传入初始化时,可直接迁移。如果需要外部传入初始化,则可以迁移为\@Param\@Once,详情见[@State->@Local](#state-local)。 |
26| \@Prop                  | \@Param                   | \@Prop和\@Param类似都是自定义组件参数的概念。当输入参数为复杂类型时,\@Prop为深拷贝,\@Param为引用。 |
27| \@Link                  | \@Param\@Event    | \@Link是框架自己封装实现的双向同步,对于V2开发者可以通过@Param@Event自己实现双向同步。 |
28| \@ObjectLink            | \@Param                   | 直接兼容,\@ObjectLink需要被@Observed装饰的class的实例初始化,\@Param没有此限制。 |
29| \@Provide               | \@Provider                | 兼容。 |
30| \@Consume               | \@Consumer                | 兼容。 |
31| \@Watch               | \@Monitor                | \@Watch用于监听V1状态变量的变化,具有监听状态变量本身和其第一层属性变化的能力。状态变量可观察到的变化会触发其\@Watch监听事件。<br/>\@Monitor用于监听V2状态变量的变化,搭配\@Trace使用,可有深层监听的能力。状态变量在一次事件中多次变化时,仅会以最终的结果判断是否触发\@Monitor监听事件。 |
32| LocalStorage               | 全局\@ObservedV2\@Trace   | 兼容。 |
33| AppStorage               | AppStorageV2   | 兼容。 |
34| Environment       | 调用Ability接口获取系统环境变量   | Environment获取环境变量能力和AppStorage耦合。在V2中可直接调用Ability接口获取系统环境变量。 |
35| PersistentStorage     | PersistenceV2   | PersistentStorage持久化能力和AppStorage耦合,PersistenceV2持久化能力可独立使用。 |
36
37## 各装饰器迁移示例
38
39### @State->@Local
40
41#### 迁移规则
42在V1中,\@State装饰器用于装饰组件内部的状态变量,在V2中提供了\@Local作为其替代能力,但两者在观察能力和初始化规则上存在明显差异。针对不同的使用场景,迁移策略如下:
43
44- 简单类型:对于简单类型的变量,可以直接将\@State替换为\@Local。
45- 复杂类型:V1中的@State可以观察复杂对象的第一层属性变化,而V2中的\@Local只能观察对象自身的变化。如果需要追踪对象内部的属性变化,可以结合使用\@ObservedV2和\@Trace。
46- 外部初始化:V1中,\@State支持从外部传递初始值,但在V2中,\@Local禁止外部初始化。若需要从外部传递初始值,可以使用\@Param和\@Once装饰器来实现类似的效果。
47
48#### 示例
49
50**简单类型**
51
52对于简单类型变量,V1的@State可以直接替换为V2的@Local。
53
54V1:
55
56```ts
57@Entry
58@Component
59struct Child {
60  @State val: number = 10;
61  build(){
62    Text(this.val.toString())
63  }
64}
65```
66
67V2迁移策略:直接替换。
68
69```ts
70@Entry
71@ComponentV2
72struct Child {
73  @Local val: number = 10;
74  build(){
75    Text(this.val.toString())
76  }
77}
78```
79
80**复杂类型**
81
82V1的@State能够观察复杂对象的第一层属性变化,但V2的@Local无法观察对象内部变化。为了解决这个问题,需要在类上添加@ObservedV2,并在需要观察的属性上添加@Trace。这样,框架就能追踪对象内部的属性变化。
83
84V1:
85
86```ts
87class Child {
88  value: number = 10;
89}
90
91@Component
92@Entry
93struct example {
94  @State child: Child = new Child();
95  build(){
96    Column() {
97      Text(this.child.value.toString())
98      // @State可以观察第一层变化
99      Button('value+1')
100        .onClick(() => {
101          this.child.value++;
102        })
103    }
104  }
105}
106```
107
108V2迁移策略:使用@ObservedV2和@Trace。
109
110```ts
111@ObservedV2
112class Child {
113  @Trace public value: number = 10;
114}
115
116@ComponentV2
117@Entry
118struct example {
119  @Local child: Child = new Child();
120  build(){
121    Column() {
122      Text(this.child.value.toString())
123      // @Local只能观察自身,需要给Child加上@ObservedV2和@Trace
124      Button('value+1')
125        .onClick(() => {
126          this.child.value++;
127        })
128    }
129  }
130}
131```
132
133**外部初始化状态变量**
134
135V1的@State变量可以从外部初始化,V2的@Local禁止外部初始化。为实现类似功能,需要用@Param和@Once代替@State,允许外部传入初始值,并确保该值只初始化时同步一次。
136
137V1实现:
138
139```ts
140@Component
141struct Child {
142  @State value: number = 0;
143  build() {
144    Text(this.value.toString())
145  }
146}
147
148@Entry
149@Component
150struct Parent {
151  build() {
152    Column(){
153      // @State可以从外部初始化
154      Child({ value: 30 })
155    }
156  }
157}
158```
159
160V2迁移策略:使用@Param和@Once。
161
162```ts
163@ComponentV2
164struct Child {
165  @Param @Once value: number = 0;
166  build() {
167    Text(this.value.toString())
168  }
169}
170
171@Entry
172@ComponentV2
173struct Parent {
174  build() {
175    Column(){
176      // @Local禁止从外部初始化,可以用@Param和@Once替代实现
177      Child({ value: 30 })
178    }
179  }
180}
181```
182
183### @Link -> @Param/@Event
184
185#### 迁移规则
186在V1中,@Link允许父组件和子组件之间进行双向数据绑定。迁移到V2时,可以用@Param和@Event模拟双向同步。@Param实现父到子的单向传递,子组件再通过@Event回调函数触发父组件的状态更新。
187
188#### 示例
189
190V1实现:
191
192```ts
193@Component
194struct Child {
195  // @Link可以双向同步数据
196  @Link val: number;
197  build() {
198    Column(){
199      Text("child: " + this.val.toString())
200      Button("+1")
201        .onClick(() => {
202          this.val++;
203        })
204    }
205  }
206}
207
208@Entry
209@Component
210struct Parent {
211  @State myVal: number = 10;
212  build() {
213    Column(){
214      Text("parent: " + this.myVal.toString())
215      Child({val: this.myVal})
216    }
217  }
218}
219```
220
221V2迁移策略:使用@Param和@Event
222
223```ts
224@ComponentV2
225struct Child {
226  // @Param搭配@Event回调实现数据双向同步
227  @Param val: number  = 0;
228  @Event addOne: () => void;
229  build() {
230    Column(){
231      Text("child: " + this.val.toString())
232      Button("+1")
233        .onClick(()=> {
234          this.addOne();
235        })
236    }
237  }
238}
239
240@Entry
241@ComponentV2
242struct Parent {
243  @Local myVal: number = 10
244  build() {
245    Column() {
246      Text("parent: " + this.myVal.toString())
247      Child({ val: this.myVal, addOne: () => this.myVal++})
248    }
249  }
250}
251```
252
253### @Prop -> @Param
254
255#### 迁移规则
256在V1中,@Prop装饰器用于从父组件传递参数给子组件,这些参数在子组件中可以被直接修改。在V2中,@Param取代了@Prop的作用,但@Param是只读的,子组件不能直接修改参数的值。因此,根据场景的不同,有几种迁移策略:
257
258- 简单类型:对于简单类型的参数,可以直接将@Prop替换@Param。
259- 复杂类型:如果传递的是复杂对象且需要严格的单向数据绑定,可以对对象进行深拷贝,防止子组件修改父组件的数据。
260- 子组件修改变量:如果子组件需要修改传入的参数,可以使用@Once来允许子组件对在本地修改该变量。但需要注意,如果使用了\@Once,则代表当前子组件只会被初始化一次,后续并没有父组件到子组件的同步能力。
261
262#### 示例
263
264**简单类型**
265
266对于简单类型变量,V1的@Prop可以直接替换为V2的@Param。
267
268V1实现:
269
270```ts
271@Component
272struct Child {
273  @Prop value: number;
274  build() {
275    Text(this.value.toString())
276  }
277}
278
279@Entry
280@Component
281struct Parent {
282  build() {
283    Column(){
284      Child({ value: 30 })
285    }
286  }
287}
288```
289
290V2迁移策略:直接替换
291
292```ts
293@ComponentV2
294struct Child {
295  @Param value: number = 0;
296  build() {
297    Text(this.value.toString())
298  }
299}
300
301@Entry
302@ComponentV2
303struct Parent {
304  build() {
305    Column(){
306      Child({ value: 30 })
307    }
308  }
309}
310```
311**复杂类型的单向数据传递**
312
313在V2中,传递复杂类型时,如果希望实现严格的单向数据绑定,防止子组件修改父组件的数据,需要在使用@Param传递复杂对象时进行深拷贝以避免传递对象的引用。
314
315V1实现:
316
317```ts
318class Fruit {
319  apple: number = 5;
320  orange: number = 10;
321}
322
323@Component
324struct Child {
325  // @Prop传递Fruit类,当子类修改属性,父类不受影响
326  @Prop fruit: Fruit;
327  build() {
328    Column() {
329      Text("child apple: "+ this.fruit.apple.toString())
330      Text("child orange: "+ this.fruit.orange.toString())
331      Button("apple+1")
332        .onClick(() => {
333          this.fruit.apple++;
334        })
335      Button("orange+1")
336        .onClick(() => {
337          this.fruit.orange++;
338        })
339    }
340  }
341}
342
343@Entry
344@Component
345struct Parent {
346  @State parentFruit: Fruit = new Fruit();
347  build() {
348    Column(){
349      Text("parent apple: "+this.parentFruit.apple.toString())
350      Text("parent orange: "+this.parentFruit.orange.toString())
351      Child({ fruit: this.parentFruit })
352    }
353  }
354}
355```
356
357V2迁移策略:使用深拷贝
358
359```ts
360@ObservedV2
361class Fruit{
362  @Trace apple: number = 5;
363  @Trace orange: number = 10;
364  // 实现深拷贝,子组件不会修改父组件的数据
365  clone(): Fruit {
366    let newFruit: Fruit = new Fruit();
367    newFruit.apple = this.apple;
368    newFruit.orange = this.orange;
369    return newFruit;
370  }
371}
372
373@ComponentV2
374struct Child {
375  @Param fruit: Fruit = new Fruit();
376  build() {
377    Column() {
378      Text("child")
379      Text(this.fruit.apple.toString())
380      Text(this.fruit.orange.toString())
381      Button("apple+1")
382        .onClick( ()=> {
383          this.fruit.apple++;
384        })
385      Button("orange+1")
386        .onClick(() => {
387          this.fruit.orange++;
388        })
389    }
390  }
391}
392
393@Entry
394@ComponentV2
395struct Parent {
396  @Local parentFruit: Fruit = new Fruit();
397  build() {
398    Column(){
399      Text("parent")
400      Text(this.parentFruit.apple.toString())
401      Text(this.parentFruit.orange.toString())
402      Child({ fruit: this.parentFruit.clone()})
403    }
404  }
405}
406```
407
408**子组件修改变量**
409
410在V1中,子组件可以修改@Prop的变量,然而在V2中,@Param是只读的。如果子组件需要修改传入的值,可以使用@Param和@Once允许子组件在本地修改。
411
412V1实现:
413
414```ts
415@Component
416struct Child {
417  // @Prop可以直接修改变量值
418  @Prop value: number;
419  build() {
420    Column(){
421      Text(this.value.toString())
422      Button("+1")
423        .onClick(()=> {
424          this.value++;
425        })
426    }
427  }
428}
429
430@Entry
431@Component
432struct Parent {
433  build() {
434    Column(){
435      Child({ value: 30 })
436    }
437  }
438}
439```
440
441V2迁移策略:使用@Param和@Once
442
443```ts
444@ComponentV2
445struct Child {
446  // @Param搭配@Once使用,可以在本地修改@Param变量
447  @Param @Once value: number = 0;
448  build() {
449    Column(){
450      Text(this.value.toString())
451      Button("+1")
452        .onClick(() => {
453          this.value++;
454        })
455    }
456  }
457}
458
459@Entry
460@ComponentV2
461struct Parent {
462  build() {
463    Column(){
464      Child({ value: 30 })
465    }
466  }
467}
468```
469
470在V1中,子组件可以修改\@Prop的变量,且只会在本地更新,不会同步回父组件。父组件数据源更新时,会通知子组件更新,并覆写子组件本地\@Prop的值。
471
472V1:
473- 改变子组件`Child`的`localValue`,不会同步回父组件`Parent`。
474- 父组件更新`value`,通知子组件`Child`更新,并覆写本地子组件`localValue`的值。
475
476```ts
477@Component
478struct Child {
479  @Prop localValue: number = 0;
480
481  build() {
482    Column() {
483      Text(`${this.localValue}`).fontSize(25)
484      Button('Child +100')
485        .onClick(() => {
486          // 改变localValue不会传递给父组件Parent
487          this.localValue += 100;
488        })
489    }
490  }
491}
492
493@Entry
494@Component
495struct Parent {
496  @State value: number = 10;
497  build() {
498    Column() {
499      Button('Parent +1')
500        .onClick(() => {
501          // 改变value的值,通知子组件Child value更新
502          this.value += 1;
503        })
504      Child({ localValue: this.value })
505    }
506  }
507}
508```
509V2中,\@Param本地不可写,和\@Once搭配使用只会同步一次。如果要实现子组件本地可写,且父组件后续更新还是能通知子组件,可以借助\@Monitor来实现这一效果。
510
511V2实现:
512- 父组件`Parent`更新通知子组件`value`的刷新,并回调\@Monitor修饰的`onValueChange`回调方法,`onValueChange`将更新后的值赋值给`localValue`。
513- 子组件`Child`改变`localValue`的值,不会同步给父组件`Parent`。
514- 父组件`Parent`中再次改变`value`,将会继续通知给子组件,并覆写子组件本地`localValue`的值。
515
516```ts
517@ComponentV2
518struct Child {
519  @Local localValue: number = 0;
520  @Param value: number = 0;
521  @Monitor('value')
522  onValueChange(mon: IMonitor) {
523    console.info(`value has been changed from ${mon.value()?.before} to ${mon.value()?.now}`);
524    // 父组件value变化时,通知子组件value更新,回调Monitor函数,将更新的值覆写给本地的localValue
525    this.localValue = this.value;
526  }
527
528  build() {
529    Column() {
530      Text(`${this.localValue}`).fontSize(25)
531      Button('Child +100')
532        .onClick(() => {
533          // 改变localValue不会传递给父组件Parent
534          this.localValue += 100;
535        })
536    }
537  }
538}
539
540@Entry
541@ComponentV2
542struct Parent {
543  @Local value: number = 10;
544  build() {
545    Column() {
546      Button('Parent +1')
547        .onClick(() => {
548          // 改变value的值,通知子组件Child value更新
549          this.value += 1;
550        })
551      Child({ value: this.value })
552    }
553  }
554}
555```
556
557### @ObjectLink/@Observed/@Track -> @ObservedV2/@Trace
558#### 迁移规则
559在V1中,@Observed与@ObjectLink装饰器用于观察类对象及其嵌套属性的变化,但V1只能直接观察对象的第一层属性。对于嵌套对象的属性,必须通过自定义组件和@ObjectLink实现观察。此外,V1中提供了@Track装饰器来实现对属性级别变化的精确控制。
560
561在V2中,@ObservedV2与@Trace结合使用,可以高效地实现类对象及其嵌套属性的深度观察,省去了对自定义组件的依赖,简化了开发流程。同时,@Trace装饰器还具备精确更新的能力,替代了V1中的@Track,从而实现更高效的UI刷新控制。根据不同的场景,有以下迁移策略:
562
563- 嵌套对象的属性观察:V1中需要通过自定义组件和@ObjectLink观察嵌套属性,V2中则可以使用@ObservedV2和@Trace直接观察嵌套对象,简化了代码结构。
564- 类属性的精确更新:V1中的@Track可以用V2中的@Trace取代,@Trace可以同时观察和精确更新属性变化,使代码更简洁高效。
565
566#### 示例
567**嵌套对象属性观察方法**
568
569在V1中,无法直接观察嵌套对象的属性变化,只能观察到第一层属性的变化。必须通过创建自定义组件并使用@ObjectLink来实现对嵌套属性的观察。V2中使用@ObservedV2和@Trace,可以直接对嵌套对象的属性进行深度观察,减少复杂度。
570
571V1实现:
572
573```ts
574@Observed
575class Address {
576  city: string;
577
578  constructor(city: string) {
579    this.city = city;
580  }
581}
582
583@Observed
584class User {
585  name: string;
586  address: Address;
587
588  constructor(name: string, address: Address) {
589    this.name = name;
590    this.address = address;
591  }
592}
593
594@Component
595struct AddressView {
596  // 子组件中@ObjectLink装饰的address从父组件初始化,接收被@Observed装饰的Address实例
597  @ObjectLink address: Address;
598
599  build() {
600    Column() {
601      Text(`City: ${this.address.city}`)
602      Button("city +a")
603        .onClick(() => {
604          this.address.city += "a";
605        })
606    }
607  }
608}
609
610@Entry
611@Component
612struct UserProfile {
613  @State user: User = new User("Alice", new Address("New York"));
614
615  build() {
616    Column() {
617      Text(`Name: ${this.user.name}`)
618      // 无法直接观察嵌套对象的属性变化,例如this.user.address.city
619      // 只能观察到对象第一层属性变化,所以需要将嵌套的对象Address抽取到自定义组件AddressView
620      AddressView({ address: this.user.address })
621    }
622  }
623}
624```
625
626V2迁移策略:使用@ObservedV2和@Trace
627
628```ts
629@ObservedV2
630class Address {
631  @Trace city: string;
632
633  constructor(city: string) {
634    this.city = city;
635  }
636}
637
638@ObservedV2
639class User {
640  @Trace name: string;
641  @Trace address: Address;
642
643  constructor(name: string, address: Address) {
644    this.name = name;
645    this.address = address;
646  }
647}
648
649@Entry
650@ComponentV2
651struct UserProfile {
652  @Local user: User = new User("Alice", new Address("New York"));
653
654  build() {
655    Column() {
656      Text(`Name: ${this.user.name}`)
657      // 通过@ObservedV2和@Trace可以直接观察嵌套属性
658      Text(`City: ${this.user.address.city}`)
659      Button("city +a")
660        .onClick(() => {
661          this.user.address.city += "a";
662        })
663    }
664  }
665}
666```
667**类属性变化观测**
668
669在V1中,@Observed用于观察类实例及其属性的变化,@Track则用于对属性级别的变化优化,使得只有被@Track装饰的属性触发UI更新。在V2中,@Trace结合了观察和更新属性级别变化的能力,搭配@ObservedV2实现高效的UI更新。
670
671V1实现:
672
673```ts
674@Observed
675class User {
676  @Track name: string;
677  @Track age: number;
678
679  constructor(name: string, age: number) {
680    this.name = name;
681    this.age = age;
682  }
683}
684
685@Entry
686@Component
687struct UserProfile {
688  @State user: User = new User('Alice', 30);
689
690  build() {
691    Column() {
692      Text(`Name: ${this.user.name}`)
693      Text(`Age: ${this.user.age}`)
694      Button("increase age")
695        .onClick(() => {
696          this.user.age++;
697        })
698    }
699  }
700}
701```
702
703V2迁移策略:使用@ObservedV2和@Trace
704
705```ts
706@ObservedV2
707class User {
708  @Trace name: string;
709  @Trace age: number;
710
711  constructor(name: string, age: number) {
712    this.name = name;
713    this.age = age;
714  }
715}
716
717@Entry
718@ComponentV2
719struct UserProfile {
720  @Local user: User = new User('Alice', 30);
721
722  build() {
723    Column() {
724      Text(`Name: ${this.user.name}`)
725      Text(`Age: ${this.user.age}`)
726      Button("Increase age")
727        .onClick(() => {
728          this.user.age++;
729        })
730    }
731  }
732}
733```
734
735### @Provide/@Consume -> @Provider/@Consumer
736#### 迁移规则
737V1的@Provide/@Consume和V2@Provider/@Consumer定位和作用大体类似,基本可以实现丝滑替换,但是有以下细微差距,开发者可根据自己代码实现来参考是否需要调整:
738在V1中,@Provide和@Consume用于父子组件之间的数据共享,可以通过alias(别名)或属性名匹配,同时@Consume必须依赖父组件的@Provide,不允许本地初始化。而V2中,@Provider和@Consumer增强了这些特性,使数据共享更加灵活。根据不同的场景,有以下迁移策略:
739
740- V1中\@Provide/\@Consume在没有指定alias的情况下,可以直接使用。V2中\@Provider/\@Consumer是标准装饰器,且参数可选,所以不管有无指定alias后面需要必须跟随“()”。
741- alias和属性名匹配规则:V1中,@Provide和@Consume可以通过alias或属性名匹配;V2中,alias是唯一的匹配key,指定alias后只能通过alias匹配。
742- 本地初始化支持:V1中,@Consume不允许本地初始化,必须依赖父组件;V2中,@Consumer支持本地初始化,当找不到对应的@Provider时使用本地默认值。
743- 从父组件初始化:V1中,@Provide可以直接从父组件初始化;V2中,@Provider不支持外部初始化,需用@Param和@Once接受初始值并赋给 @Provider。
744- 重载支持:V1中,@Provide默认不支持重载,需设置 allowOverride;V2中,@Provider默认支持重载,@Consumer会向上查找最近的@Provider。
745#### 示例
746**alias和属性名匹配规则**
747
748在V1中,@Provide和@Consume的匹配既可以通过alias,也可以通过属性名。在V2中,alias成为唯一的key,如果在@Consumer中制定了alias,只能通过alias而非属性名进行匹配。
749
750V1实现:
751
752```ts
753@Component
754struct Child {
755  // alias和属性名都为key,alias和属性名都可以匹配
756  @Consume('text') childMessage: string;
757  @Consume message: string;
758  build(){
759    Column(){
760      Text(this.childMessage)
761      Text(this.message) // Text是Hello World
762    }
763  }
764}
765
766@Entry
767@Component
768struct Parent {
769  @Provide('text') message: string = "Hello World";
770  build(){
771    Column(){
772      Child()
773    }
774  }
775}
776```
777
778V2迁移策略:确保alias一致,没有指定alias的情况下,依赖属性名进行匹配
779
780```ts
781@ComponentV2
782struct Child {
783  // alias是唯一匹配的key,有alias情况下无法通过属性名匹配
784  @Consumer('text') childMessage: string = "default";
785  @Consumer() message: string = "default";
786  build(){
787    Column(){
788      Text(this.childMessage)
789      Text(this.message) // Text是default
790    }
791  }
792}
793
794@Entry
795@ComponentV2
796struct Parent {
797  @Provider('text') message: string = "Hello World";
798  build(){
799    Column(){
800      Child()
801    }
802  }
803}
804```
805
806**V1的@Consume不支持本地初始化,V2支持**
807
808V1中,@Consume不允许本地初始化变量,必须依赖父组件的@Provide,否则会抛出异常。迁移到V2后,@Consumer允许本地初始化,当找不到对应的@Provider,会使用本地默认值。
809
810V1实现:
811
812```ts
813@Component
814struct Child {
815  // @Consume禁止本地初始化,当找不到对应的@Provide时抛出异常
816  @Consume message: string;
817  build(){
818    Text(this.message)
819  }
820}
821
822@Entry
823@Component
824struct Parent {
825  @Provide message: string = "Hello World";
826  build(){
827    Column(){
828      Child()
829    }
830  }
831}
832```
833
834V2迁移策略:@Consumer可以本地初始化
835
836```ts
837@ComponentV2
838struct Child {
839  // @Consumer允许本地初始化,当找不到@Provider的时候使用本地默认值
840  @Consumer() message: string = "Hello World";
841  build(){
842    Text(this.message)
843  }
844}
845
846@Entry
847@ComponentV2
848struct Parent {
849  build(){
850    Column(){
851      Child()
852    }
853  }
854}
855```
856
857**V1的@Provide可以从父组件初始化,V2不支持**
858
859在V1中,@Provide允许从父组件初始化,可以直接通过组件参数传递初始值。在V2中,@Provider禁止从外部初始化。为实现相同功能,可以在子组件中使用@Param @Once接受初始值,然后将其赋值给@Provider变量。
860
861V1实现:
862
863```ts
864@Entry
865@Component
866struct Parent {
867  @State parentValue: number = 42;
868  build() {
869    Column() {
870      // @Provide可以从父组件初始化
871      Child({ childValue: this.parentValue })
872    }
873  }
874}
875
876@Component
877struct Child {
878  @Provide childValue: number = 0;
879  build(){
880    Column(){
881      Text(this.childValue.toString())
882    }
883  }
884}
885```
886
887V2迁移策略:使用@Param接受初始值,再赋值给@Provider
888
889```ts
890@Entry
891@ComponentV2
892struct Parent {
893  @Local parentValue: number = 42;
894  build() {
895    Column() {
896      // @Provider禁止从父组件初始化,替代方案为先用@Param接受,再赋值给@Provider
897      Child({ initialValue: this.parentValue })
898    }
899  }
900}
901
902@ComponentV2
903struct Child {
904  @Param @Once initialValue: number = 0;
905  @Provider() childValue: number = this.initialValue;
906  build() {
907    Column(){
908      Text(this.childValue.toString())
909    }
910  }
911}
912```
913
914**V1的@Provide默认不支持重载,V2默认支持**
915
916在V1中,@Provide默认不支持重载,无法覆盖上层组件的同名@Provide。若需支持重载,必须设置allowOverride。在V2中,@Provider默认支持重载,@Consumer会向上查找最近的@Provider,无需额外设置。
917
918V1实现:
919
920```ts
921@Entry
922@Component
923struct GrandParent {
924  @Provide("reviewVotes") reviewVotes: number = 40;
925  build() {
926    Column(){
927      Parent()
928    }
929  }
930}
931
932@Component
933struct Parent {
934  // @Provide默认不支持重载,支持重载需设置allowOverride函数
935  @Provide({ allowOverride: "reviewVotes" }) reviewVotes: number = 20;
936  build() {
937    Child()
938  }
939}
940
941@Component
942struct Child {
943  @Consume("reviewVotes") reviewVotes: number;
944  build() {
945    Text(this.reviewVotes.toString()) // Text显示20
946  }
947}
948```
949
950V2迁移策略:去掉allowOverride
951
952```ts
953@Entry
954@ComponentV2
955struct GrandParent {
956  @Provider("reviewVotes") reviewVotes: number = 40;
957  build() {
958    Column(){
959      Parent()
960    }
961  }
962}
963
964@ComponentV2
965struct Parent {
966  // @Provider默认支持重载,@Consumer向上查找最近的@Provider
967  @Provider() reviewVotes: number = 20;
968  build() {
969    Child()
970  }
971}
972
973@ComponentV2
974struct Child {
975  @Consumer() reviewVotes: number = 0;
976  build() {
977    Text(this.reviewVotes.toString()) // Text显示20
978  }
979}
980```
981
982### @Watch -> @Monitor
983#### 迁移规则
984在V1中,\@Watch用于监听状态变量的变化,并在变量变化时触发指定回调函数。在V2中,\@Monitor替代了\@Watch,可以更灵活地监听变量的变化,并获取变量变化前后的值。具体的迁移策略如下:
985
986- 单变量监听:对于简单的场景,可以直接用@Monitor替换@Watch,效果一致。
987- 多变量监听:V1的@Watch无法获取变化前的值。在V2中,\@Monitor支持同时监听多个变量,并可以访问变量变化前后的状态。
988#### 示例
989**单变量监听**
990
991对于简单案例,V1的@Watch可以直接替换为替换为V2的@Monitor。
992
993V1实现:
994
995```ts
996@Entry
997@Component
998struct watchExample {
999  @State @Watch('onAppleChange') apple: number = 0;
1000  onAppleChange(): void {
1001    console.log("apple count changed to "+this.apple);
1002  }
1003
1004  build() {
1005    Column(){
1006      Text(`apple count: ${this.apple}`)
1007      Button("add apple")
1008        .onClick(() => {
1009          this.apple++;
1010        })
1011    }
1012  }
1013}
1014```
1015
1016V2迁移策略:直接替换
1017
1018```ts
1019@Entry
1020@ComponentV2
1021struct monitorExample {
1022  @Local apple: number = 0;
1023  @Monitor('apple')
1024  onFruitChange(monitor: IMonitor) {
1025    console.log(`apple changed from ${monitor.value()?.before} to ${monitor.value()?.now}`);
1026  }
1027
1028  build() {
1029    Column(){
1030      Text(`apple count: ${this.apple}`)
1031      Button("add apple")
1032        .onClick(()=> {
1033          this.apple++;
1034        })
1035    }
1036  }
1037}
1038```
1039
1040**多变量监听**
1041
1042在V1中,每个@Watch回调函数只能监听一个变量,且无法获取变化前的值。迁移到V2后,可以使用一个@Monitor同时监听多个变量以及获取监听变量的变化前后的值。
1043
1044V1实现:
1045
1046```ts
1047@Entry
1048@Component
1049struct watchExample {
1050  @State @Watch('onAppleChange') apple: number = 0;
1051  @State @Watch('onOrangeChange') orange: number = 0;
1052  // @Watch 回调,只能监听单个变量,不能获取变化前的值
1053  onAppleChange(): void {
1054    console.log("apple count changed to "+this.apple);
1055  }
1056  onOrangeChange(): void {
1057    console.log("orange count changed to "+this.orange);
1058  }
1059
1060  build() {
1061    Column(){
1062      Text(`apple count: ${this.apple}`)
1063      Text(`orange count: ${this.orange}`)
1064      Button("add apple")
1065        .onClick(() => {
1066          this.apple++;
1067        })
1068      Button("add orange")
1069        .onClick(() => {
1070          this.orange++;
1071        })
1072    }
1073  }
1074}
1075```
1076
1077V2迁移策略:同时监听多个变量,以及获取变化前的值
1078
1079```ts
1080@Entry
1081@ComponentV2
1082struct monitorExample {
1083  @Local apple: number = 0;
1084  @Local orange: number = 0;
1085
1086  // @Monitor回调,支持监听多个变量,可以获取变化前的值
1087  @Monitor('apple','orange')
1088  onFruitChange(monitor: IMonitor) {
1089    monitor.dirty.forEach((name: string) => {
1090      console.log(`${name} changed from ${monitor.value(name)?.before} to ${monitor.value(name)?.now}`);
1091    });
1092  }
1093
1094  build() {
1095    Column() {
1096      Text(`apple count: ${this.apple}`)
1097      Text(`orange count: ${this.orange}`)
1098      Button("add apple")
1099        .onClick(() => {
1100          this.apple++;
1101        })
1102      Button("add orange")
1103        .onClick(() => {
1104          this.orange++;
1105        })
1106    }
1107  }
1108}
1109```
1110### @Computed
1111#### 迁移规则
1112V1中并没有提供计算属性的概念,所以对于UI中的冗余计算,并没有办法可以减少重复计算。V2针对该场景,提供了@Computed装饰器,可以帮助开发者减少重复计算。
1113
1114V1:
1115在下面的例子中,每次改变`lastName`都会触发Text组件的刷新,每次Text组件的刷新,都需要重复计算`this.lastName + ' ' + this.firstName`。
1116```
1117@Entry
1118@Component
1119struct Index {
1120  @State firstName: string = 'Li';
1121  @State lastName: string = 'Hua';
1122
1123  build() {
1124    Column() {
1125      Text(this.lastName + ' ' + this.firstName)
1126      Text(this.lastName + ' ' + this.firstName)
1127      Button('changed lastName').onClick(() => {
1128        this.lastName += 'a';
1129      })
1130
1131    }
1132  }
1133}
1134```
1135
1136V2:
1137使用V2中的\@Computed,每次改变`lastName`仅会触发一次计算。
1138
1139```
1140@Entry
1141@ComponentV2
1142struct Index {
1143  @Local firstName: string = 'Li';
1144  @Local lastName: string = 'Hua';
1145
1146  @Computed
1147  get fullName() {
1148    return this.firstName + ' ' + this.lastName;
1149  }
1150
1151  build() {
1152    Column() {
1153      Text(this.fullName)
1154      Text(this.fullName)
1155      Button('changed lastName').onClick(() => {
1156        this.lastName += 'a';
1157      })
1158    }
1159  }
1160}
1161```
1162### LocalStorage->全局@ObservedV2/@Trace
1163#### 迁移规则
1164LocalStorage的目的是为了实现页面间的状态变量共享。之所以提供这个能力,是因为V1状态变量和View层耦合,无法由开发者自主地实现页面间状态变量的共享。
1165对于状态管理V2,状态变量的观察能力内嵌到数据本身,不再和View层耦合,所以对于状态管理V2,不再需要类似LocalStorage的能力,可以使用全局@ObservedV2/@Trace,由开发者自己import和export,自己实现状态变量的页面间共享。
1166
1167#### 示例
1168**基本场景**
1169
1170V1:
1171通过windowStage.[loadContent](../../reference/apis-arkui/js-apis-window.md#loadcontent9)和[getShared](../../reference/apis-arkui/arkui-ts/ts-state-management.md#getshareddeprecated)接口实现页面间的状态变量共享。
1172```
1173// EntryAbility.ets
1174import { UIAbility } from '@kit.AbilityKit';
1175import { window } from '@kit.ArkUI';
1176
1177export default class EntryAbility extends UIAbility {
1178  para:Record<string, number> = { 'count': 47 };
1179  storage: LocalStorage = new LocalStorage(this.para);
1180
1181  onWindowStageCreate(windowStage: window.WindowStage): void {
1182    windowStage.loadContent('pages/Page1', this.storage);
1183  }
1184}
1185```
1186在下面的示例中,使用\@LocalStorageLink,可以使得开发者本地的修改同步回LocalStorage中。
1187
1188```
1189// Page1.ets
1190// 通过getShared接口获取stage共享的LocalStorage实例
1191@Entry(LocalStorage.getShared())
1192@Component
1193struct Page1 {
1194  @LocalStorageLink('count') count: number = 0;
1195  pageStack: NavPathStack = new NavPathStack();
1196  build() {
1197    Navigation(this.pageStack) {
1198      Column() {
1199        Text(`${this.count}`)
1200          .fontSize(50)
1201          .onClick(() => {
1202            this.count++;
1203          })
1204        Button('push to Page2')
1205          .onClick(() => {
1206            this.pageStack.pushPathByName('Page2', null);
1207          })
1208      }
1209    }
1210  }
1211}
1212```
1213
1214```
1215// Page2.ets
1216@Builder
1217export function Page2Builder() {
1218  Page2()
1219}
1220
1221// Page2组件获得了父亲Page1组件的LocalStorage实例
1222@Component
1223struct Page2 {
1224  @LocalStorageLink('count') count: number = 0;
1225  pathStack: NavPathStack = new NavPathStack();
1226  build() {
1227    NavDestination() {
1228      Column() {
1229        Text(`${this.count}`)
1230          .fontSize(50)
1231          .onClick(() => {
1232            this.count++;
1233          })
1234      }
1235    }
1236    .onReady((context: NavDestinationContext) => {
1237      this.pathStack = context.pathStack;
1238    })
1239  }
1240}
1241```
1242使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。
1243```json
1244{
1245  "routerMap": [
1246    {
1247      "name": "Page2",
1248      "pageSourceFile": "src/main/ets/pages/Page2.ets",
1249      "buildFunction": "Page2Builder",
1250      "data": {
1251        "description" : "LocalStorage example"
1252      }
1253    }
1254  ]
1255}
1256```
1257V2:
1258- 声明\@ObservedV2装饰的MyStorage类,并import需要使用的页面中。
1259- 声明被\@Trace的属性作为页面间共享的可观察的数据。
1260
1261```
1262// storage.ets
1263@ObservedV2
1264export class MyStorage {
1265  static singleton_: MyStorage;
1266  static instance() {
1267    if(!MyStorage.singleton_) {
1268      MyStorage.singleton_ = new MyStorage();
1269    };
1270    return MyStorage.singleton_;
1271  }
1272  @Trace count: number = 47;
1273}
1274```
1275
1276```
1277// Page1.ets
1278import { MyStorage } from './storage';
1279
1280@Entry
1281@ComponentV2
1282struct Page1 {
1283  storage: MyStorage = MyStorage.instance();
1284  pageStack: NavPathStack = new NavPathStack();
1285  build() {
1286    Navigation(this.pageStack) {
1287      Column() {
1288        Text(`${this.storage.count}`)
1289          .fontSize(50)
1290          .onClick(() => {
1291            this.storage.count++;
1292          })
1293        Button('push to Page2')
1294          .onClick(() => {
1295            this.pageStack.pushPathByName('Page2', null);
1296          })
1297      }
1298    }
1299  }
1300}
1301```
1302
1303```
1304// Page2.ets
1305import { MyStorage } from './storage';
1306
1307@Builder
1308export function Page2Builder() {
1309  Page2()
1310}
1311
1312@ComponentV2
1313struct Page2 {
1314  storage: MyStorage = MyStorage.instance();
1315  pathStack: NavPathStack = new NavPathStack();
1316  build() {
1317    NavDestination() {
1318      Column() {
1319        Text(`${this.storage.count}`)
1320          .fontSize(50)
1321          .onClick(() => {
1322            this.storage.count++;
1323          })
1324      }
1325    }
1326    .onReady((context: NavDestinationContext) => {
1327      this.pathStack = context.pathStack;
1328    })
1329  }
1330}
1331```
1332使用Navigation时,需要添加配置系统路由表文件src/main/resources/base/profile/route_map.json,并替换pageSourceFile为Page2页面的路径,并且在module.json5中添加:"routerMap": "$profile:route_map"。
1333```json
1334{
1335  "routerMap": [
1336    {
1337      "name": "Page2",
1338      "pageSourceFile": "src/main/ets/pages/Page2.ets",
1339      "buildFunction": "Page2Builder",
1340      "data": {
1341        "description" : "LocalStorage example"
1342      }
1343    }
1344  ]
1345}
1346```
1347
1348如果开发者需要实现类似于\@LocalStorageProp的效果,希望本地的修改不要同步回LocalStorage中,如以下示例:
1349- 在`Page1`中改变`count`值,因为count是\@LocalStorageProp装饰的,所以其改变只会在本地生效,并不会同步回LocalStorage。
1350- 点击`push to Page2`,跳转到`Page2`中。因为在`Page1`中改变`count`值并不会同步会LocalStorage,所以在`Page2`中Text组件依旧显示原本的值47。
1351- 点击`change Storage Count`,调用LocalStorage的setOrCreate,改变`count`对应的值,并通知所有绑定该key的变量。
1352
1353```ts
1354// Page1.ets
1355export let storage: LocalStorage = new LocalStorage();
1356storage.setOrCreate('count', 47);
1357
1358@Entry(storage)
1359@Component
1360struct Page1 {
1361  @LocalStorageProp('count') count: number = 0;
1362  pageStack: NavPathStack = new NavPathStack();
1363  build() {
1364    Navigation(this.pageStack) {
1365      Column() {
1366        Text(`${this.count}`)
1367          .fontSize(50)
1368          .onClick(() => {
1369            this.count++;
1370          })
1371        Button('change Storage Count')
1372          .onClick(() => {
1373            storage.setOrCreate('count', storage.get<number>('count') as number + 100);
1374          })
1375        Button('push to Page2')
1376          .onClick(() => {
1377            this.pageStack.pushPathByName('Page2', null);
1378          })
1379      }
1380    }
1381  }
1382}
1383```
1384
1385```ts
1386// Page2.ets
1387import { storage } from './Page1'
1388@Builder
1389export function Page2Builder() {
1390  Page2()
1391}
1392
1393// Page2组件获得了父亲Page1组件的LocalStorage实例
1394@Component
1395struct Page2 {
1396  @LocalStorageProp('count') count: number = 0;
1397  pathStack: NavPathStack = new NavPathStack();
1398  build() {
1399    NavDestination() {
1400      Column() {
1401        Text(`${this.count}`)
1402          .fontSize(50)
1403          .onClick(() => {
1404            this.count++;
1405          })
1406        Button('change Storage Count')
1407          .onClick(() => {
1408            storage.setOrCreate('count', storage.get<number>('count') as number + 100);
1409          })
1410      }
1411    }
1412    .onReady((context: NavDestinationContext) => {
1413      this.pathStack = context.pathStack;
1414    })
1415  }
1416}
1417```
1418在V2中,可以借助\@Local和\@Monitor实现类似的效果。
1419- \@Local装饰的`count`变量为组件本地的值,其改变不会同步回`storage`。
1420- \@Monitor监听`storage.count`的变化,当`storage.count`改变时,在\@Monitor的回调里改变本地\@Local的值。
1421
1422```ts
1423// Page1.ets
1424import { MyStorage } from './storage';
1425
1426@Entry
1427@ComponentV2
1428struct Page1 {
1429  storage: MyStorage = MyStorage.instance();
1430  pageStack: NavPathStack = new NavPathStack();
1431  @Local count: number = this.storage.count;
1432
1433  @Monitor('storage.count')
1434  onCountChange(mon: IMonitor) {
1435    console.log(`Page1 ${mon.value()?.before} to ${mon.value()?.now}`);
1436    this.count = this.storage.count;
1437  }
1438  build() {
1439    Navigation(this.pageStack) {
1440      Column() {
1441        Text(`${this.count}`)
1442          .fontSize(50)
1443          .onClick(() => {
1444            this.count++;
1445          })
1446        Button('change Storage Count')
1447          .onClick(() => {
1448            this.storage.count += 100;
1449          })
1450        Button('push to Page2')
1451          .onClick(() => {
1452            this.pageStack.pushPathByName('Page2', null);
1453          })
1454      }
1455    }
1456  }
1457}
1458```
1459
1460```ts
1461// Page2.ets
1462import { MyStorage } from './storage';
1463
1464@Builder
1465export function Page2Builder() {
1466  Page2()
1467}
1468
1469@ComponentV2
1470struct Page2 {
1471  storage: MyStorage = MyStorage.instance();
1472  pathStack: NavPathStack = new NavPathStack();
1473  @Local count: number = this.storage.count;
1474
1475  @Monitor('storage.count')
1476  onCountChange(mon: IMonitor) {
1477    console.log(`Page2 ${mon.value()?.before} to ${mon.value()?.now}`);
1478    this.count = this.storage.count;
1479  }
1480  build() {
1481    NavDestination() {
1482      Column() {
1483        Text(`${this.count}`)
1484          .fontSize(50)
1485          .onClick(() => {
1486            this.count++;
1487          })
1488        Button('change Storage Count')
1489          .onClick(() => {
1490            this.storage.count += 100;
1491          })
1492      }
1493    }
1494    .onReady((context: NavDestinationContext) => {
1495      this.pathStack = context.pathStack;
1496    })
1497  }
1498}
1499```
1500
1501**自定义组件接收LocalStorage实例场景**
1502
1503为了配合Navigation的场景,LocalStorage支持作为自定义组件的入参,传递给以当前自定义组件为根节点的所有子自定义组件。
1504对于该场景,V2可以采用多个全局\@ObservedV2/\@Trace实例来替代。
1505
1506V1:
1507```ts
1508let localStorageA: LocalStorage = new LocalStorage();
1509localStorageA.setOrCreate('PropA', 'PropA');
1510
1511let localStorageB: LocalStorage = new LocalStorage();
1512localStorageB.setOrCreate('PropB', 'PropB');
1513
1514let localStorageC: LocalStorage = new LocalStorage();
1515localStorageC.setOrCreate('PropC', 'PropC');
1516
1517@Entry
1518@Component
1519struct MyNavigationTestStack {
1520  @Provide('pageInfo') pageInfo: NavPathStack = new NavPathStack();
1521
1522  @Builder
1523  PageMap(name: string) {
1524    if (name === 'pageOne') {
1525      // 传递不同的LocalStorage实例
1526      pageOneStack({}, localStorageA)
1527    } else if (name === 'pageTwo') {
1528      pageTwoStack({}, localStorageB)
1529    } else if (name === 'pageThree') {
1530      pageThreeStack({}, localStorageC)
1531    }
1532  }
1533
1534  build() {
1535    Column({ space: 5 }) {
1536      Navigation(this.pageInfo) {
1537        Column() {
1538          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1539            .width('80%')
1540            .height(40)
1541            .margin(20)
1542            .onClick(() => {
1543              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
1544            })
1545        }
1546      }.title('NavIndex')
1547      .navDestination(this.PageMap)
1548      .mode(NavigationMode.Stack)
1549      .borderWidth(1)
1550    }
1551  }
1552}
1553
1554@Component
1555struct pageOneStack {
1556  @Consume('pageInfo') pageInfo: NavPathStack;
1557  @LocalStorageLink('PropA') PropA: string = 'Hello World';
1558
1559  build() {
1560    NavDestination() {
1561      Column() {
1562        // 显示'PropA'
1563        NavigationContentMsgStack()
1564        // 显示'PropA'
1565        Text(`${this.PropA}`)
1566        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1567          .width('80%')
1568          .height(40)
1569          .margin(20)
1570          .onClick(() => {
1571            this.pageInfo.pushPathByName('pageTwo', null);
1572          })
1573      }.width('100%').height('100%')
1574    }.title('pageOne')
1575    .onBackPressed(() => {
1576      this.pageInfo.pop();
1577      return true;
1578    })
1579  }
1580}
1581
1582@Component
1583struct pageTwoStack {
1584  @Consume('pageInfo') pageInfo: NavPathStack;
1585  @LocalStorageLink('PropB') PropB: string = 'Hello World';
1586
1587  build() {
1588    NavDestination() {
1589      Column() {
1590        // 显示'Hello',当前LocalStorage实例localStorageB没有PropA对应的值,使用本地默认值'Hello'
1591        NavigationContentMsgStack()
1592        // 显示'PropB'
1593        Text(`${this.PropB}`)
1594        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1595          .width('80%')
1596          .height(40)
1597          .margin(20)
1598          .onClick(() => {
1599            this.pageInfo.pushPathByName('pageThree', null);
1600          })
1601
1602      }.width('100%').height('100%')
1603    }.title('pageTwo')
1604    .onBackPressed(() => {
1605      this.pageInfo.pop();
1606      return true;
1607    })
1608  }
1609}
1610
1611@Component
1612struct pageThreeStack {
1613  @Consume('pageInfo') pageInfo: NavPathStack;
1614  @LocalStorageLink('PropC') PropC: string = 'pageThreeStack';
1615
1616  build() {
1617    NavDestination() {
1618      Column() {
1619        // 显示'Hello',当前LocalStorage实例localStorageC没有PropA对应的值,使用本地默认值'Hello'
1620        NavigationContentMsgStack()
1621        // 显示'PropC'
1622        Text(`${this.PropC}`)
1623        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1624          .width('80%')
1625          .height(40)
1626          .margin(20)
1627          .onClick(() => {
1628            this.pageInfo.pushPathByName('pageOne', null);
1629          })
1630
1631      }.width('100%').height('100%')
1632    }.title('pageThree')
1633    .onBackPressed(() => {
1634      this.pageInfo.pop();
1635      return true;
1636    })
1637  }
1638}
1639
1640@Component
1641struct NavigationContentMsgStack {
1642  @LocalStorageLink('PropA') PropA: string = 'Hello';
1643
1644  build() {
1645    Column() {
1646      Text(`${this.PropA}`)
1647        .fontSize(30)
1648        .fontWeight(FontWeight.Bold)
1649    }
1650  }
1651}
1652```
1653V2:
1654
1655声明\@ObservedV2装饰的class代替LocalStorage。其中LocalStorage的key可以用\@Trace装饰的属性代替。
1656```ts
1657// storage.ets
1658@ObservedV2
1659export class MyStorageA {
1660  @Trace propA: string = 'Hello';
1661  constructor(propA?: string) {
1662      this.propA = propA? propA : this.propA;
1663  }
1664}
1665
1666@ObservedV2
1667export class MyStorageB extends MyStorageA {
1668  @Trace propB: string = 'Hello';
1669  constructor(propB: string) {
1670    super();
1671    this.propB = propB;
1672  }
1673}
1674
1675@ObservedV2
1676export class MyStorageC extends MyStorageA {
1677  @Trace propC: string = 'Hello';
1678  constructor(propC: string) {
1679    super();
1680    this.propC = propC;
1681  }
1682}
1683```
1684
1685在`pageOneStack`、`pageTwoStack`和`pageThreeStack`组件内分别创建`MyStorageA`、`MyStorageB`、`MyStorageC`的实例,并通过\@Param传递给其子组件`NavigationContentMsgStack`,从而实现类似LocalStorage实例在子组件树上共享的能力。
1686
1687```ts
1688// Index.ets
1689import { MyStorageA, MyStorageB, MyStorageC } from './storage';
1690
1691@Entry
1692@ComponentV2
1693struct MyNavigationTestStack {
1694   pageInfo: NavPathStack = new NavPathStack();
1695
1696  @Builder
1697  PageMap(name: string) {
1698    if (name === 'pageOne') {
1699      pageOneStack()
1700    } else if (name === 'pageTwo') {
1701      pageTwoStack()
1702    } else if (name === 'pageThree') {
1703      pageThreeStack()
1704    }
1705  }
1706
1707  build() {
1708    Column({ space: 5 }) {
1709      Navigation(this.pageInfo) {
1710        Column() {
1711          Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1712            .width('80%')
1713            .height(40)
1714            .margin(20)
1715            .onClick(() => {
1716              this.pageInfo.pushPath({ name: 'pageOne' }); //将name指定的NavDestination页面信息入栈
1717            })
1718        }
1719      }.title('NavIndex')
1720      .navDestination(this.PageMap)
1721      .mode(NavigationMode.Stack)
1722      .borderWidth(1)
1723    }
1724  }
1725}
1726
1727@ComponentV2
1728struct pageOneStack {
1729  pageInfo: NavPathStack = new NavPathStack();
1730  @Local storageA: MyStorageA = new MyStorageA('PropA');
1731
1732  build() {
1733    NavDestination() {
1734      Column() {
1735        // 显示'PropA'
1736        NavigationContentMsgStack({storage: this.storageA})
1737        // 显示'PropA'
1738        Text(`${this.storageA.propA}`)
1739        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1740          .width('80%')
1741          .height(40)
1742          .margin(20)
1743          .onClick(() => {
1744            this.pageInfo.pushPathByName('pageTwo', null);
1745          })
1746      }.width('100%').height('100%')
1747    }.title('pageOne')
1748    .onBackPressed(() => {
1749      this.pageInfo.pop();
1750      return true;
1751    })
1752    .onReady((context: NavDestinationContext) => {
1753      this.pageInfo = context.pathStack;
1754    })
1755  }
1756}
1757
1758@ComponentV2
1759struct pageTwoStack {
1760  pageInfo: NavPathStack = new NavPathStack();
1761  @Local storageB: MyStorageB = new MyStorageB('PropB');
1762
1763  build() {
1764    NavDestination() {
1765      Column() {
1766        // 显示'Hello'
1767        NavigationContentMsgStack({ storage: this.storageB })
1768        // 显示'PropB'
1769        Text(`${this.storageB.propB}`)
1770        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1771          .width('80%')
1772          .height(40)
1773          .margin(20)
1774          .onClick(() => {
1775            this.pageInfo.pushPathByName('pageThree', null);
1776          })
1777
1778      }.width('100%').height('100%')
1779    }.title('pageTwo')
1780    .onBackPressed(() => {
1781      this.pageInfo.pop();
1782      return true;
1783    })
1784    .onReady((context: NavDestinationContext) => {
1785      this.pageInfo = context.pathStack;
1786    })
1787  }
1788}
1789
1790@ComponentV2
1791struct pageThreeStack {
1792  pageInfo: NavPathStack = new NavPathStack();
1793  @Local storageC: MyStorageC = new MyStorageC("PropC");
1794
1795  build() {
1796    NavDestination() {
1797      Column() {
1798        // 显示'Hello'
1799        NavigationContentMsgStack({ storage: this.storageC })
1800        // 显示'PropC'
1801        Text(`${this.storageC.propC}`)
1802        Button('Next Page', { stateEffect: true, type: ButtonType.Capsule })
1803          .width('80%')
1804          .height(40)
1805          .margin(20)
1806          .onClick(() => {
1807            this.pageInfo.pushPathByName('pageOne', null);
1808          })
1809
1810      }.width('100%').height('100%')
1811    }.title('pageThree')
1812    .onBackPressed(() => {
1813      this.pageInfo.pop();
1814      return true;
1815    })
1816    .onReady((context: NavDestinationContext) => {
1817      this.pageInfo = context.pathStack;
1818    })
1819  }
1820}
1821
1822@ComponentV2
1823struct NavigationContentMsgStack {
1824  @Require@Param storage: MyStorageA;
1825
1826  build() {
1827    Column() {
1828      Text(`${this.storage.propA}`)
1829        .fontSize(30)
1830        .fontWeight(FontWeight.Bold)
1831    }
1832  }
1833}
1834```
1835
1836### AppStorage->AppStorageV2
1837上一小节中,对于全局的@ObserveV2/@Trace的改造并不适合跨Ability的数据共享,该场景可以使用AppStorageV2来替换。
1838
1839V1:
1840AppStorage是和应用进程绑定了,可以跨Ability实现数据共享。
1841在下面的示例中,使用\@StorageLink,可以使得开发者本地的修改同步回AppStorage中。
1842
1843```
1844// EntryAbility Index.ets
1845import { common, Want } from '@kit.AbilityKit';
1846@Entry
1847@Component
1848struct Index {
1849  @StorageLink('count') count: number = 0;
1850  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1851
1852  build() {
1853    Column() {
1854      Text(`EntryAbility count: ${this.count}`)
1855        .fontSize(50)
1856        .onClick(() => {
1857          this.count++;
1858        })
1859      Button('Jump to EntryAbility1').onClick(() => {
1860        let wantInfo: Want = {
1861          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
1862          abilityName: 'EntryAbility1'
1863        };
1864        this.context.startAbility(wantInfo);
1865      })
1866    }
1867  }
1868}
1869```
1870
1871```
1872// EntryAbility1 Index1.ets
1873import { common, Want } from '@kit.AbilityKit';
1874@Entry
1875@Component
1876struct Index1 {
1877  @StorageLink('count') count: number = 0;
1878  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1879
1880  build() {
1881    Column() {
1882      Text(`EntryAbility1 count: ${this.count}`)
1883        .fontSize(50)
1884        .onClick(() => {
1885          this.count++;
1886        })
1887      Button('Jump to EntryAbility').onClick(() => {
1888        let wantInfo: Want = {
1889          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
1890          abilityName: 'EntryAbility'
1891        };
1892        this.context.startAbility(wantInfo);
1893      })
1894    }
1895  }
1896}
1897```
1898V2:
1899可以使用AppStorageV2实现跨Ability共享。
1900如下面示例:
1901
1902```
1903import { common, Want } from '@kit.AbilityKit';
1904import { AppStorageV2 } from '@kit.ArkUI';
1905
1906@ObservedV2
1907export class MyStorage {
1908  @Trace count: number = 0
1909}
1910
1911@Entry
1912@ComponentV2
1913struct Index {
1914  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
1915  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1916
1917  build() {
1918    Column() {
1919      Text(`EntryAbility1 count: ${this.storage.count}`)
1920        .fontSize(50)
1921        .onClick(() => {
1922          this.storage.count++;
1923        })
1924      Button('Jump to EntryAbility1').onClick(() => {
1925        let wantInfo: Want = {
1926          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
1927          abilityName: 'EntryAbility1'
1928        };
1929        this.context.startAbility(wantInfo);
1930      })
1931    }
1932  }
1933}
1934
1935```
1936
1937```
1938import { common, Want } from '@kit.AbilityKit';
1939import { AppStorageV2 } from '@kit.ArkUI';
1940
1941@ObservedV2
1942export class MyStorage {
1943  @Trace count: number = 0
1944}
1945
1946@Entry
1947@ComponentV2
1948struct Index1 {
1949  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
1950  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1951
1952    build() {
1953      Column() {
1954        Text(`EntryAbility1 count: ${this.storage.count}`)
1955          .fontSize(50)
1956          .onClick(() => {
1957            this.storage.count++;
1958          })
1959        Button('Jump to EntryAbility').onClick(() => {
1960          let wantInfo: Want = {
1961            bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
1962            abilityName: 'EntryAbility'
1963          };
1964          this.context.startAbility(wantInfo);
1965        })
1966      }
1967    }
1968}
1969```
1970
1971如果开发者需要实现类似于\@StorageProp的效果,希望本地的修改不要同步回AppStorage中,而AppStorage的变化又可以通知给使用\@StorageProp装饰器的组件,可以参考以下示例对比。
1972
1973V1:
1974
1975```ts
1976// EntryAbility Index.ets
1977import { common, Want } from '@kit.AbilityKit';
1978@Entry
1979@Component
1980struct Index {
1981  @StorageProp('count') count: number = 0;
1982  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1983
1984  build() {
1985    Column() {
1986      Text(`EntryAbility count: ${this.count}`)
1987        .fontSize(25)
1988        .onClick(() => {
1989          this.count++;
1990        })
1991      Button('change Storage Count')
1992        .onClick(() => {
1993          AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100);
1994        })
1995      Button('Jump to EntryAbility1').onClick(() => {
1996        let wantInfo: Want = {
1997          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
1998          abilityName: 'EntryAbility1'
1999        };
2000        this.context.startAbility(wantInfo);
2001      })
2002    }
2003  }
2004}
2005```
2006
2007```ts
2008// EntryAbility1 Index1.ets
2009import { common, Want } from '@kit.AbilityKit';
2010@Entry
2011@Component
2012struct Index1 {
2013  @StorageProp('count') count: number = 0;
2014  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
2015
2016  build() {
2017    Column() {
2018      Text(`EntryAbility1 count: ${this.count}`)
2019        .fontSize(50)
2020        .onClick(() => {
2021          this.count++;
2022        })
2023      Button('change Storage Count')
2024        .onClick(() => {
2025          AppStorage.setOrCreate('count', AppStorage.get<number>('count') as number + 100);
2026        })
2027      Button('Jump to EntryAbility').onClick(() => {
2028        let wantInfo: Want = {
2029          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
2030          abilityName: 'EntryAbility'
2031        };
2032        this.context.startAbility(wantInfo);
2033      })
2034    }
2035  }
2036}
2037```
2038
2039V2:
2040开发者可以借助\@Monitor和\@Local来实现类似的效果,示例如下。
2041
2042```ts
2043import { common, Want } from '@kit.AbilityKit';
2044import { AppStorageV2 } from '@kit.ArkUI';
2045
2046@ObservedV2
2047export class MyStorage {
2048  @Trace count: number = 0;
2049}
2050
2051@Entry
2052@ComponentV2
2053struct Index {
2054  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
2055  @Local count: number = this.storage.count;
2056  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
2057
2058  @Monitor('storage.count')
2059  onCountChange(mon: IMonitor) {
2060    console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`);
2061    this.count = this.storage.count;
2062  }
2063  build() {
2064    Column() {
2065      Text(`EntryAbility1 count: ${this.count}`)
2066        .fontSize(25)
2067        .onClick(() => {
2068          this.count++;
2069        })
2070      Button('change Storage Count')
2071        .onClick(() => {
2072          this.storage.count += 100;
2073        })
2074      Button('Jump to EntryAbility1').onClick(() => {
2075        let wantInfo: Want = {
2076          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
2077          abilityName: 'EntryAbility1'
2078        };
2079        this.context.startAbility(wantInfo);
2080      })
2081    }
2082  }
2083}
2084```
2085
2086```ts
2087import { common, Want } from '@kit.AbilityKit';
2088import { AppStorageV2 } from '@kit.ArkUI';
2089
2090@ObservedV2
2091export class MyStorage {
2092  @Trace count: number = 0;
2093}
2094
2095@Entry
2096@ComponentV2
2097struct Index1 {
2098  @Local storage: MyStorage = AppStorageV2.connect(MyStorage, 'storage', () => new MyStorage())!;
2099  @Local count: number = this.storage.count;
2100  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
2101
2102  @Monitor('storage.count')
2103  onCountChange(mon: IMonitor) {
2104    console.log(`Index1 ${mon.value()?.before} to ${mon.value()?.now}`);
2105    this.count = this.storage.count;
2106  }
2107
2108  build() {
2109    Column() {
2110      Text(`EntryAbility1 count: ${this.count}`)
2111        .fontSize(25)
2112        .onClick(() => {
2113          this.count++;
2114        })
2115      Button('change Storage Count')
2116        .onClick(() => {
2117          this.storage.count += 100;
2118        })
2119      Button('Jump to EntryAbility').onClick(() => {
2120        let wantInfo: Want = {
2121          bundleName: 'com.example.myapplication', // 替换成AppScope/app.json5里的bundleName
2122          abilityName: 'EntryAbility'
2123        };
2124        this.context.startAbility(wantInfo);
2125      })
2126    }
2127  }
2128}
2129```
2130
2131### Environment->调用Ability接口直接获取系统环境变量
2132V1中,开发者可以通过Environment来获取环境变量,但Environment获取的结果无法直接使用,需要配合AppStorage才能得到对应环境变量的值。
2133在切换V2的过程中,开发者无需再通过Environment来获取环境变量,可以直接通过[UIAbilityContext的config属性](../../reference/apis-ability-kit/js-apis-inner-application-uiAbilityContext.md#属性)获取系统环境变量。
2134V1:
2135以`languageCode`为例。
2136```ts
2137// 将设备languageCode存入AppStorage中
2138Environment.envProp('languageCode', 'en');
2139
2140@Entry
2141@Component
2142struct Index {
2143  @StorageProp('languageCode') languageCode: string = 'en';
2144  build() {
2145    Row() {
2146      Column() {
2147        // 输出当前设备的languageCode
2148        Text(this.languageCode)
2149      }
2150    }
2151  }
2152}
2153```
2154
2155V2:
2156封装Env类型来传递多个系统环境变量。
2157
2158```
2159// Env.ts
2160import { ConfigurationConstant } from '@kit.AbilityKit';
2161
2162export class Env {
2163  language: string | undefined;
2164  colorMode: ConfigurationConstant.ColorMode | undefined;
2165  fontSizeScale: number | undefined;
2166  fontWeightScale: number | undefined;
2167}
2168
2169export let env: Env = new Env();
2170```
2171在`onCreate`里获得需要的系统环境变量:
2172```
2173// EntryAbility.ets
2174import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
2175import { window } from '@kit.ArkUI';
2176import { env } from '../pages/Env';
2177
2178export default class EntryAbility extends UIAbility {
2179  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
2180    env.language = this.context.config.language;
2181    env.colorMode = this.context.config.colorMode;
2182    env.fontSizeScale = this.context.config.fontSizeScale;
2183    env.fontWeightScale = this.context.config.fontWeightScale;
2184  }
2185
2186  onWindowStageCreate(windowStage: window.WindowStage): void {
2187    windowStage.loadContent('pages/Index');
2188  }
2189}
2190
2191```
2192在页面中获得当前Env的值。
2193```
2194// Index.ets
2195import { env } from '../pages/Env';
2196
2197@Entry
2198@ComponentV2
2199struct Index {
2200  build() {
2201    Row() {
2202      Column() {
2203        // 输出当前设备的环境变量
2204        Text(`languageCode: ${env.language}`).fontSize(20)
2205        Text(`colorMode: ${env.colorMode}`).fontSize(20)
2206        Text(`fontSizeScale: ${env.fontSizeScale}`).fontSize(20)
2207        Text(`fontWeightScale: ${env.fontWeightScale}`).fontSize(20)
2208      }
2209    }
2210  }
2211}
2212```
2213
2214### PersistentStorage->PersistenceV2
2215V1中PersistentStorage提供了持久化UI数据的能力,而V2则提供了更加方便使用的PersistenceV2接口来替代它。
2216- PersistentStorage持久化的触发时机依赖AppStorage的观察能力,且与AppStorage耦合,开发者无法自主选择写入或读取持久化数据的时机。
2217- PersistentStorage使用序列化和反序列化,并没有传入类型,所以在持久化后,会丢失其类型,且对象的属性方法不能持久化。
2218
2219对于PersistenceV2:
2220- 与PersistenceV2关联的\@ObservedV2对象,其\@Trace属性的变化,会触发整个关联对象的自动持久化。
2221- 开发者也可以调用[PersistenceV2.save](./arkts-new-persistencev2.md#save手动持久化数据)和[PersistenceV2.globalConnect](./arkts-new-persistencev2.md#使用globalconnect存储数据)接口来手动触发持久化写入和读取。
2222
2223V1:
2224
2225```ts
2226class data {
2227  name: string = 'ZhangSan';
2228  id: number = 0;
2229}
2230
2231PersistentStorage.persistProp('numProp', 47);
2232PersistentStorage.persistProp('dataProp', new data());
2233
2234@Entry
2235@Component
2236struct Index {
2237  @StorageLink('numProp') numProp: number = 48;
2238  @StorageLink('dataProp') dataProp: data = new data();
2239
2240  build() {
2241    Column() {
2242      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2243      Text(`numProp: ${this.numProp}`)
2244        .onClick(() => {
2245          this.numProp += 1;
2246        })
2247        .fontSize(30)
2248
2249      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2250      Text(`dataProp.name: ${this.dataProp.name}`)
2251        .onClick(() => {
2252          this.dataProp.name += 'a';
2253        })
2254        .fontSize(30)
2255      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2256      Text(`dataProp.id: ${this.dataProp.id}`)
2257        .onClick(() => {
2258          this.dataProp.id += 1;
2259        })
2260        .fontSize(30)
2261
2262    }
2263    .width('100%')
2264  }
2265}
2266```
2267
2268V2:
2269
2270下面的案例展示了:
2271- 将`PersistentStorage`的持久化数据迁移到V2的PersistenceV2中去,其中V2对被@Trace标记的数据可以自动持久化,对于非@Trace数据需要开发者自己手动调用save进行持久化。
2272- 示例中的move函数和需要显示的组件放在了一个ets中,开发者可以定义自己的move函数,并放入合适的位置进行统一迁移操作。
2273```ts
2274// 迁移到globalConnect
2275import { PersistenceV2, Type } from '@kit.ArkUI';
2276
2277// 接受序列化失败的回调
2278PersistenceV2.notifyOnError((key: string, reason: string, msg: string) => {
2279  console.error(`error key: ${key}, reason: ${reason}, message: ${msg}`);
2280});
2281
2282class Data {
2283  name: string = 'ZhangSan';
2284  id: number = 0;
2285}
2286
2287@ObservedV2
2288class V2Data {
2289  @Trace name: string = '';
2290  @Trace Id: number = 1;
2291}
2292
2293@ObservedV2
2294export class Sample {
2295  // 对于复杂对象需要@Type修饰,确保序列化成功
2296  @Type(V2Data)
2297  @Trace num: number = 1;
2298  @Trace V2: V2Data = new V2Data();
2299}
2300
2301// 用于判断是否完成数据迁移的辅助数据
2302@ObservedV2
2303class StorageState {
2304  @Trace isCompleteMoving: boolean = false;
2305}
2306
2307function move() {
2308  let movingState = PersistenceV2.globalConnect({type: StorageState, defaultCreator: () => new StorageState()})!;
2309  if (!movingState.isCompleteMoving) {
2310    PersistentStorage.persistProp('numProp', 47);
2311    PersistentStorage.persistProp('dataProp', new Data());
2312    let num = AppStorage.get<number>('numProp')!;
2313    let V1Data = AppStorage.get<Data>('dataProp')!;
2314    PersistentStorage.deleteProp('numProp');
2315    PersistentStorage.deleteProp('dataProp');
2316
2317    // V2创建对应数据
2318    let migrate = PersistenceV2.globalConnect({type: Sample, key: 'connect2', defaultCreator: () => new Sample()})!;  // 使用默认构造函数也可以
2319    // 赋值数据,@Trace修饰的会自动保存,对于非@Trace对象,也可以调用save保存,如:PersistenceV2.save('connect2');
2320    migrate.num = num;
2321    migrate.V2.name = V1Data.name;
2322    migrate.V2.Id = V1Data.id;
2323
2324    // 将迁移标志设置为true
2325    movingState.isCompleteMoving = true;
2326  }
2327}
2328
2329move();
2330
2331@Entry
2332@ComponentV2
2333struct Page1 {
2334  @Local refresh: number = 0;
2335  // 使用key:connect2存入数据
2336  @Local p: Sample = PersistenceV2.globalConnect({type: Sample, key:'connect2', defaultCreator:() => new Sample()})!;
2337
2338  build() {
2339    Column({space: 5}) {
2340      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2341      Text(`numProp: ${this.p.num}`)
2342        .onClick(() => {
2343          this.p.num += 1;
2344        })
2345        .fontSize(30)
2346
2347      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2348      Text(`dataProp.name: ${this.p.V2.name}`)
2349        .onClick(() => {
2350          this.p.V2.name += 'a';
2351        })
2352        .fontSize(30)
2353      // 应用退出时会保存当前结果。重新启动后,会显示上一次的保存结果
2354      Text(`dataProp.id: ${this.p.V2.Id}`)
2355        .onClick(() => {
2356          this.p.V2.Id += 1;
2357        })
2358        .fontSize(30)
2359    }
2360    .width('100%')
2361  }
2362}
2363```
2364
2365## 存量迁移场景
2366
2367对于已经使用V1开发的大型应用,一般不太可能做到一次性的从V1迁移到V2,而是分批次和分组件的部分迁移,这就必然会带来V1和V2的混用。
2368
2369这种场景,一般是父组件是状态管理V1,而迁移的子组件为状态管理V2。为了模拟这种场景,我们举出下面的示例:
2370- 父组件是\@Component,数据源是\@LocalStorageLink。
2371- 子组件是\@ComponentV2,使用\@Param接受数据源的数据。
2372
2373这种情况,我们可以通过以下策略进行迁移:
2374- 声明一个\@ObservedV2装饰的class来封装V1的数据。
2375- 在\@Component和\@ComponentV2之间,定义一个桥接的\@Component自定义组件。
2376- 在桥接层:
2377    - V1->V2的数据同步,可通过\@Watch的监听触发\@ObservedV2装饰的class的属性的赋值。
2378    - V2->V1的数据同步,可通过在\@ObservedV2装饰的class里声明Monitor,通过LocalStorage的API反向通知给V1状态变量。
2379
2380具体示例如下:
2381```
2382let storage: LocalStorage = new LocalStorage();
2383
2384@ObservedV2
2385class V1StorageData {
2386  @Trace title: string = 'V1OldComponent'
2387  @Monitor('title')
2388  onStrChange(monitor: IMonitor) {
2389    monitor.dirty.forEach((path: string) => {
2390      console.log(`${path} changed from ${monitor.value(path)?.before} to ${monitor.value(path)?.now}`)
2391      if (path === 'title') {
2392        storage.setOrCreate('title', this.title);
2393      }
2394    })
2395  }
2396}
2397let v1Data: V1StorageData = new V1StorageData();
2398
2399@Entry(storage)
2400@Component
2401struct V1OldComponent {
2402  @LocalStorageLink('title') title: string = 'V1OldComponent';
2403
2404  build() {
2405    Column() {
2406      Text(`V1OldComponent: ${this.title}`)
2407        .fontSize(20)
2408        .onClick(() => {
2409          this.title = 'new value from V1OldComponent';
2410        })
2411      Bridge()
2412    }
2413  }
2414}
2415
2416
2417@Component
2418struct Bridge {
2419  @LocalStorageLink('title')@Watch('titleWatch') title: string = 'Bridge';
2420  titleWatch() {
2421    v1Data.title = this.title;
2422  }
2423
2424  build() {
2425    NewV2Component()
2426  }
2427}
2428@ComponentV2
2429struct NewV2Component {
2430  build() {
2431    Column() {
2432      Text(`NewV2Component: ${v1Data.title}`)
2433        .fontSize(20)
2434        .onClick(() => {
2435          v1Data.title = 'NewV2Component';
2436        })
2437    }
2438  }
2439}
2440```
2441
2442## 其他迁移场景
2443
2444### 滑动组件
2445
2446#### List
2447
2448开发者可以通过[ChildrenMainSize](../../reference/apis-arkui/arkui-ts/ts-container-list.md#childrenmainsize12)来设置List的子组件在主轴方向的大小信息。
2449
2450V1:
2451
2452在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。
2453
2454具体示例如下:
2455
2456```ts
2457@Entry
2458@Component
2459struct ListExample {
2460  private arr: Array<number> = new Array(10).fill(0);
2461  private scroller: ListScroller = new ListScroller();
2462  @State listSpace: number = 10;
2463  @State listChildrenSize: ChildrenMainSize = new ChildrenMainSize(100);
2464
2465  build() {
2466    Column() {
2467      Button('change Default').onClick(() => {
2468        this.listChildrenSize.childDefaultSize += 10;
2469      })
2470
2471      Button('splice 5').onClick(() => {
2472        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2473      })
2474
2475      Button('update 5').onClick(() => {
2476        this.listChildrenSize.update(0, 200);
2477      })
2478
2479      List({ space: this.listSpace, scroller: this.scroller }) {
2480        ForEach(this.arr, (item: number) => {
2481          ListItem() {
2482            Text(`item-` + item)
2483          }.backgroundColor(Color.Pink)
2484        })
2485      }
2486      .childrenMainSize(this.listChildrenSize) // 10
2487    }
2488  }
2489}
2490```
2491
2492V2:
2493
2494但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为ChildrenMainSize定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注ChildrenMainSize的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。
2495
2496具体示例如下:
2497
2498```ts
2499import { UIUtils } from '@kit.ArkUI';
2500
2501@Entry
2502@ComponentV2
2503struct ListExample {
2504  private arr: Array<number> = new Array(10).fill(0);
2505  private scroller: ListScroller = new ListScroller();
2506  listSpace: number = 10;
2507  // 使用makeObserved的能力来观测ChildrenMainSize
2508  listChildrenSize: ChildrenMainSize = UIUtils.makeObserved(new ChildrenMainSize(100));
2509
2510  build() {
2511    Column() {
2512      Button('change Default').onClick(() => {
2513        this.listChildrenSize.childDefaultSize += 10;
2514      })
2515
2516      Button('splice 5').onClick(() => {
2517        this.listChildrenSize.splice(0, 5, [100, 100, 100, 100, 100]);
2518      })
2519
2520      Button('update 5').onClick(() => {
2521        this.listChildrenSize.update(0, 200);
2522      })
2523
2524      List({ space: this.listSpace, scroller: this.scroller }) {
2525        ForEach(this.arr, (item: number) => {
2526          ListItem() {
2527            Text(`item-` + item)
2528          }.backgroundColor(Color.Pink)
2529        })
2530      }
2531      .childrenMainSize(this.listChildrenSize) // 10
2532    }
2533  }
2534}
2535```
2536
2537#### WaterFlow
2538
2539开发者可以通过[WaterFlowSections](../../reference/apis-arkui/arkui-ts/ts-container-waterflow.md#waterflowsections12)来设置WaterFlow瀑布流分组信息。
2540
2541需要注意的是,数组arr的长度需要与WaterFlowSections的中所有SectionOptions的itemsCount的总和保持一致,否则WaterFlow无法处理,导致UI不刷新。
2542
2543以下两个示例请按照'push option' -> 'splice option' -> 'update option'的顺序进行点击。
2544
2545V1:
2546
2547在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其api调用。
2548
2549具体示例如下:
2550
2551```ts
2552@Entry
2553@Component
2554struct WaterFlowSample {
2555  @State colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2556  @State sections: WaterFlowSections = new WaterFlowSections();
2557  scroller: Scroller = new Scroller();
2558  @State private arr: Array<number> = new Array(9).fill(0);
2559  oneColumnSection: SectionOptions = {
2560    itemsCount: 4,
2561    crossCount: 1,
2562    columnsGap: '5vp',
2563    rowsGap: 10,
2564  };
2565  twoColumnSection: SectionOptions = {
2566    itemsCount: 2,
2567    crossCount: 2,
2568  };
2569  lastSection: SectionOptions = {
2570    itemsCount: 3,
2571    crossCount: 3,
2572  };
2573
2574  aboutToAppear(): void {
2575    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2576    this.sections.splice(0, 0, sectionOptions);
2577  }
2578
2579  build() {
2580    Column() {
2581      Text(`${this.arr.length}`)
2582
2583      Button('push option').onClick(() => {
2584        let section: SectionOptions = {
2585          itemsCount: 1,
2586          crossCount: 1,
2587        };
2588        this.sections.push(section);
2589        this.arr.push(100);
2590      })
2591
2592      Button('splice option').onClick(() => {
2593        let section: SectionOptions = {
2594          itemsCount: 8,
2595          crossCount: 2,
2596        };
2597        this.sections.splice(0, this.arr.length, [section]);
2598        this.arr = new Array(8).fill(10);
2599      })
2600
2601      Button('update option').onClick(() => {
2602        let section: SectionOptions = {
2603          itemsCount: 8,
2604          crossCount: 2,
2605        };
2606        this.sections.update(1, section);
2607        this.arr = new Array(16).fill(1);
2608      })
2609
2610      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2611        ForEach(this.arr, (item: number) => {
2612          FlowItem() {
2613            Text(`${item}`)
2614              .border({ width: 1 })
2615              .backgroundColor(this.colors[item % 6])
2616              .height(30)
2617              .width(50)
2618          }
2619        })
2620      }
2621    }
2622  }
2623}
2624```
2625
2626V2:
2627
2628但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为WaterFlowSections定义在框架中,开发者无法使用[\@Trace](./arkts-new-observedV2-and-trace.md)来标注WaterFlowSections的属性,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。
2629
2630具体示例如下:
2631
2632```ts
2633import { UIUtils } from '@kit.ArkUI';
2634
2635@Entry
2636@ComponentV2
2637struct WaterFlowSample {
2638  colors: Color[] = [Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Pink];
2639  // 使用makeObserved的能力来观测WaterFlowSections
2640  sections: WaterFlowSections = UIUtils.makeObserved(new WaterFlowSections());
2641  scroller: Scroller = new Scroller();
2642  @Local private arr: Array<number> = new Array(9).fill(0);
2643  oneColumnSection: SectionOptions = {
2644    itemsCount: 4,
2645    crossCount: 1,
2646    columnsGap: '5vp',
2647    rowsGap: 10,
2648  };
2649  twoColumnSection: SectionOptions = {
2650    itemsCount: 2,
2651    crossCount: 2,
2652  };
2653  lastSection: SectionOptions = {
2654    itemsCount: 3,
2655    crossCount: 3,
2656  };
2657
2658  aboutToAppear(): void {
2659    let sectionOptions: SectionOptions[] = [this.oneColumnSection, this.twoColumnSection, this.lastSection];
2660    this.sections.splice(0, 0, sectionOptions);
2661  }
2662
2663  build() {
2664    Column() {
2665      Text(`${this.arr.length}`)
2666
2667      Button('push option').onClick(() => {
2668        let section: SectionOptions = {
2669          itemsCount: 1,
2670          crossCount: 1,
2671        };
2672        this.sections.push(section);
2673        this.arr.push(100);
2674      })
2675
2676      Button('splice option').onClick(() => {
2677        let section: SectionOptions = {
2678          itemsCount: 8,
2679          crossCount: 2,
2680        };
2681        this.sections.splice(0, this.arr.length, [section]);
2682        this.arr = new Array(8).fill(10);
2683      })
2684
2685      Button('update option').onClick(() => {
2686        let section: SectionOptions = {
2687          itemsCount: 8,
2688          crossCount: 2,
2689        };
2690        this.sections.update(1, section);
2691        this.arr = new Array(16).fill(1);
2692      })
2693
2694      WaterFlow({ scroller: this.scroller, sections: this.sections }) {
2695        ForEach(this.arr, (item: number) => {
2696          FlowItem() {
2697            Text(`${item}`)
2698              .border({ width: 1 })
2699              .backgroundColor(this.colors[item % 6])
2700              .height(30)
2701              .width(50)
2702          }
2703        })
2704      }
2705    }
2706  }
2707}
2708```
2709
2710### Modifier
2711
2712#### attributeModifier
2713
2714开发者可以通过[attributeModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#attributemodifier)动态设置组件的属性方法。
2715
2716V1:
2717
2718在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。
2719
2720具体示例如下:
2721
2722```ts
2723class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2724  isDark: boolean = false;
2725
2726  applyNormalAttribute(instance: ButtonAttribute): void {
2727    if (this.isDark) {
2728      instance.backgroundColor(Color.Black);
2729    } else {
2730      instance.backgroundColor(Color.Red);
2731    }
2732  }
2733}
2734
2735@Entry
2736@Component
2737struct AttributeDemo {
2738  @State modifier: MyButtonModifier = new MyButtonModifier();
2739
2740  build() {
2741    Row() {
2742      Column() {
2743        Button('Button')
2744          .attributeModifier(this.modifier)
2745          .onClick(() => {
2746            this.modifier.isDark = !this.modifier.isDark;
2747          })
2748      }
2749      .width('100%')
2750    }
2751    .height('100%')
2752  }
2753}
2754```
2755
2756V2:
2757
2758但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,如果要观察attributeModifier的属性变化,可以使用[makeObserved](./arkts-new-makeObserved.md)替代。
2759
2760具体示例如下:
2761
2762```ts
2763import { UIUtils } from '@kit.ArkUI';
2764
2765class MyButtonModifier implements AttributeModifier<ButtonAttribute> {
2766  isDark: boolean = false;
2767
2768  applyNormalAttribute(instance: ButtonAttribute): void {
2769    if (this.isDark) {
2770      instance.backgroundColor(Color.Black);
2771    } else {
2772      instance.backgroundColor(Color.Red);
2773    }
2774  }
2775}
2776
2777@Entry
2778@ComponentV2
2779struct AttributeDemo {
2780  // 使用makeObserved的能力观测attributeModifier的属性this.modifier
2781  modifier: MyButtonModifier = UIUtils.makeObserved(new MyButtonModifier());
2782
2783  build() {
2784    Row() {
2785      Column() {
2786        Button('Button')
2787          .attributeModifier(this.modifier)
2788          .onClick(() => {
2789            this.modifier.isDark = !this.modifier.isDark;
2790          })
2791      }
2792      .width('100%')
2793    }
2794    .height('100%')
2795  }
2796}
2797```
2798
2799#### CommonModifier
2800
2801动态设置组件的属性类。以[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)为例。
2802
2803V1:
2804
2805在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。
2806
2807具体实例如下:
2808
2809```ts
2810import { CommonModifier } from '@ohos.arkui.modifier';
2811
2812class MyModifier extends CommonModifier {
2813  applyNormalAttribute(instance: CommonAttribute): void {
2814    super.applyNormalAttribute?.(instance);
2815  }
2816
2817  public setGroup1(): void {
2818    this.borderStyle(BorderStyle.Dotted);
2819    this.borderWidth(8);
2820  }
2821
2822  public setGroup2(): void {
2823    this.borderStyle(BorderStyle.Dashed);
2824    this.borderWidth(8);
2825  }
2826}
2827
2828@Component
2829struct MyImage1 {
2830  @Link modifier: CommonModifier;
2831
2832  build() {
2833    // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
2834    Image($r('app.media.app_icon'))
2835      .attributeModifier(this.modifier as MyModifier)
2836  }
2837}
2838
2839@Entry
2840@Component
2841struct Index {
2842  @State myModifier: CommonModifier = new MyModifier().width(100).height(100).margin(10);
2843  index: number = 0;
2844
2845  build() {
2846    Column() {
2847      Button($r('app.string.EntryAbility_label'))
2848        .margin(10)
2849        .onClick(() => {
2850          console.log('Modifier', 'onClick');
2851          this.index++;
2852          if (this.index % 2 === 1) {
2853            (this.myModifier as MyModifier).setGroup1();
2854            console.log('Modifier', 'setGroup1');
2855          } else {
2856            (this.myModifier as MyModifier).setGroup2();
2857            console.log('Modifier', 'setGroup2');
2858          }
2859        })
2860
2861      MyImage1({ modifier: this.myModifier })
2862    }
2863    .width('100%')
2864  }
2865}
2866```
2867
2868V2:
2869
2870但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,又因为[CommonModifier](../../reference/apis-arkui/arkui-ts/ts-universal-attributes-attribute-modifier.md#自定义modifier)在框架内是通过其属性触发刷新,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。
2871
2872具体示例如下:
2873
2874```ts
2875import { UIUtils } from '@kit.ArkUI';
2876import { CommonModifier } from '@ohos.arkui.modifier';
2877
2878class MyModifier extends CommonModifier {
2879  applyNormalAttribute(instance: CommonAttribute): void {
2880    super.applyNormalAttribute?.(instance);
2881  }
2882
2883  public setGroup1(): void {
2884    this.borderStyle(BorderStyle.Dotted);
2885    this.borderWidth(8);
2886  }
2887
2888  public setGroup2(): void {
2889    this.borderStyle(BorderStyle.Dashed);
2890    this.borderWidth(8);
2891  }
2892}
2893
2894@ComponentV2
2895struct MyImage1 {
2896  @Param @Require modifier: CommonModifier;
2897
2898  build() {
2899    // 此处'app.media.app_icon'仅作示例,请开发者自行替换,否则imageSource创建失败会导致后续无法正常执行。
2900    Image($r('app.media.app_icon'))
2901      .attributeModifier(this.modifier as MyModifier)
2902  }
2903}
2904
2905@Entry
2906@ComponentV2
2907struct Index {
2908  // 使用makeObserved的能力来观测CommonModifier
2909  @Local myModifier: CommonModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
2910  index: number = 0;
2911
2912  build() {
2913    Column() {
2914      Button($r('app.string.EntryAbility_label'))
2915        .margin(10)
2916        .onClick(() => {
2917          console.log('Modifier', 'onClick');
2918          this.index++;
2919          if (this.index % 2 === 1) {
2920            (this.myModifier as MyModifier).setGroup1();
2921            console.log('Modifier', 'setGroup1');
2922          } else {
2923            (this.myModifier as MyModifier).setGroup2();
2924            console.log('Modifier', 'setGroup2');
2925          }
2926        })
2927
2928      MyImage1({ modifier: this.myModifier })
2929    }
2930    .width('100%')
2931  }
2932}
2933```
2934
2935#### 组件Modfier
2936
2937动态设置组件的属性类。以Text组件为例。
2938
2939V1:
2940
2941在状态管理V1中,可以通过[\@State](./arkts-state.md)装饰观察其变化。
2942
2943具体示例如下:
2944
2945```ts
2946import { TextModifier } from '@ohos.arkui.modifier';
2947
2948class MyModifier extends TextModifier {
2949  applyNormalAttribute(instance: TextModifier): void {
2950    super.applyNormalAttribute?.(instance);
2951  }
2952
2953  public setGroup1(): void {
2954    this.fontSize(50);
2955    this.fontColor(Color.Pink);
2956  }
2957
2958  public setGroup2(): void {
2959    this.fontSize(50);
2960    this.fontColor(Color.Gray);
2961  }
2962}
2963
2964@Component
2965struct MyImage1 {
2966  @Link modifier: TextModifier;
2967  index: number = 0;
2968
2969  build() {
2970    Column() {
2971      Text('Test')
2972        .attributeModifier(this.modifier as MyModifier)
2973
2974      Button($r('app.string.EntryAbility_label'))
2975        .margin(10)
2976        .onClick(() => {
2977          console.log('Modifier', 'onClick');
2978          this.index++;
2979          if (this.index % 2 === 1) {
2980            (this.modifier as MyModifier).setGroup1();
2981            console.log('Modifier', 'setGroup1');
2982          } else {
2983            (this.modifier as MyModifier).setGroup2();
2984            console.log('Modifier', 'setGroup2');
2985          }
2986        })
2987    }
2988  }
2989}
2990
2991@Entry
2992@Component
2993struct Index {
2994  @State myModifier: TextModifier = new MyModifier().width(100).height(100).margin(10);
2995  index: number = 0;
2996
2997  build() {
2998    Column() {
2999      MyImage1({ modifier: this.myModifier })
3000
3001      Button('replace whole')
3002        .margin(10)
3003        .onClick(() => {
3004          this.myModifier = new MyModifier().backgroundColor(Color.Orange);
3005        })
3006    }
3007    .width('100%')
3008  }
3009}
3010```
3011
3012V2:
3013
3014但在状态管理V2中,[\@Local](./arkts-new-local.md)只能观察本身的变化,无法观察第一层的变化,此时可以使用[makeObserved](./arkts-new-makeObserved.md)替代。
3015
3016具体示例如下:
3017
3018```ts
3019import { UIUtils } from '@kit.ArkUI';
3020import { TextModifier } from '@ohos.arkui.modifier';
3021
3022class MyModifier extends TextModifier {
3023  applyNormalAttribute(instance: TextModifier): void {
3024    super.applyNormalAttribute?.(instance);
3025  }
3026
3027  public setGroup1(): void {
3028    this.fontSize(50);
3029    this.fontColor(Color.Pink);
3030  }
3031
3032  public setGroup2(): void {
3033    this.fontSize(50);
3034    this.fontColor(Color.Gray);
3035  }
3036}
3037
3038@ComponentV2
3039struct MyImage1 {
3040  @Param @Require modifier: TextModifier;
3041  index: number = 0;
3042
3043  build() {
3044    Column() {
3045      Text('Test')
3046        .attributeModifier(this.modifier as MyModifier)
3047
3048      Button($r('app.string.EntryAbility_label'))
3049        .margin(10)
3050        .onClick(() => {
3051          console.log('Modifier', 'onClick');
3052          this.index++;
3053          if (this.index % 2 === 1) {
3054            (this.modifier as MyModifier).setGroup1();
3055            console.log('Modifier', 'setGroup1');
3056          } else {
3057            (this.modifier as MyModifier).setGroup2();
3058            console.log('Modifier', 'setGroup2');
3059          }
3060        })
3061    }
3062  }
3063}
3064
3065@Entry
3066@ComponentV2
3067struct Index {
3068  // 使用makeObserved的能力观测TextModifier
3069  @Local myModifier: TextModifier = UIUtils.makeObserved(new MyModifier().width(100).height(100).margin(10));
3070  index: number = 0;
3071
3072  build() {
3073    Column() {
3074      MyImage1({ modifier: this.myModifier })
3075
3076      Button('replace whole')
3077        .margin(10)
3078        .onClick(() => {
3079          this.myModifier = UIUtils.makeObserved(new MyModifier().backgroundColor(Color.Orange));
3080        })
3081    }
3082    .width('100%')
3083  }
3084}
3085```
3086