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