1# 自定义组件混用场景指导 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @zzq212050299--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9状态管理V1(简称V1)为开发者提供了一系列状态变量装饰器(后续称V1的装饰器),例如:@State、@Prop、@Link等,可以在@Component装饰的自定义组件中使用(后续称V1的自定义组件)。 10 11然而V1对于嵌套类的观测存在诸多限制。例如:需要开发者通过\@ObjectLink不断拆解嵌套类才能使得深层次数据具备观测能力。 12 13为此,在API version 12中为开发者提供了一套全新的状态管理V2(简称V2),开发者可以声明\@ComponentV2装饰的自定义组件(后续称V2的自定义组件)并搭配一套全新的装饰器去使用(后续称V2的装饰器)。例如:\@Local、\@Param等。 14 15V2的提出不仅解决了V1对嵌套类观测的不足,还增强了部分装饰器的功能。例如:V2的\@Monitor不仅能够感知变化后的数据,还能获取变化前的数据。 16 17在设计上,我们希望V1和V2的代码是完全隔离的,因为V1能实现的功能,V2能做得更好。但从实际角度出发,V1的开发者已经有很大的基础,让开发者一次性迁移成V2也不符合实际,因此在V1的代码中使用V2的部分能力是允许的,V2中也没有完全禁止V1,这样就涉及到V1和V2的一个混用问题,例如:V1的自定义组件使用了V2的自定义组件或V1去使用V2的装饰器等。 18 19本指南详细阐述了V1与V2混用的场景,旨在帮助开发者将V1代码迁移到V2。 20 21> **说明:** 22> 23> 状态管理V2从API version 12开始支持。 24> 该文档介绍的混用规则仅适用于API version 18及以前。从API version 19开始,为支持应用更便捷地从V1向V2迁移,状态管理提供了新的接口[enableV2Compatibility](../../reference/apis-arkui/js-apis-StateManagement.md#enablev2compatibility19)和[makeV1Observed](../../reference/apis-arkui/js-apis-StateManagement.md#makev1observed19)来帮助开发者解决在V1向V2迁移过程中遇到的混用问题,详见[状态管理V1V2混用文档](./arkts-v1-v2-mixusage.md)。 25 26## 概述 27 28状态管理V1与V2的混用规则如下: 29 30* V1的自定义组件中不可以使用V2的装饰器,否则编译报错。 31 32* 当组件间不传递变量时,V1的自定义组件中可以使用V2的自定义组件,包括导入第三方的\@ComponentV2装饰的自定义组件。 33 34* 组件间存在变量传递时,V1的变量传递给V2的自定义组件,有以下限制: 35 - V1中未被装饰器装饰的变量(后称普通变量):V2只能使用\@Param接收。 36 - V1中被装饰器装饰的变量(后称状态变量):V2只能通过\@Param装饰器接收,且仅限于boolean、number、enum、string、undefined、null这些简单类型数据。 37 38* V2的自定义组件中不可以使用V1的装饰器,否则编译报错。 39 40* 组件间不存在变量传递时,V2自定义组件可以使用V1的自定义组件,包括import第三方\@Component装饰的自定义组件。 41 42* 组件间存在变量传递时,V2的变量传递给V1的自定义组件,有以下限制: 43 - V2中未被装饰器装饰的变量(后称普通变量):若V1使用装饰器装饰接收的数据,只能通过\@State、\@Prop、\@Provide。 44 - V2中被装饰器装饰的变量(后称状态变量):若V1使用装饰器装饰接收的数据,不支持内置类型数据:Array、Set、Map、Date。 45 46## 状态管理装饰器总览 47 48### 状态管理V1的装饰器 49 50| 装饰器类别 | 装饰器 | 51| :----------: | :----------------------------------------------------------: | 52| 组件内装饰器 | \@State、\@Prop、\@Link、\@ObjectLink、\@Provide、\@Consume、\@StorageProp、\@StorageLink、\@LocalStorageProp、\@LocalStorageLink、\@Watch | 53| 类相关装饰器 | \@Observed、\@Track | 54 55### 状态管理V2的装饰器 56 57| 装饰器类别 | 装饰器 | 58| :----------: | :----------------------------------------------------------: | 59| 组件内装饰器 | \@Local、\@Param、\@Provider、\@Consumer、\@Once、\@Event、\@Monitor、\@Computed | 60| 类相关装饰器 | \@ObservedV2、\@Trace、\@Type | 61 62### 状态管理装饰器支持的数据类型总览 63 64状态管理能够支持的数据类型有: 65 66| 数据类型 | 关键字 | 67| ------------ | -------------------------------------------------- | 68| 简单类型数据 | boolean、number、enum、string、null、undefined | 69| function类型 | function(仅V2的\@Event、\@Monitor、\@Computed支持) | 70| Object类型 | Object | 71| Class类型 | Class | 72| 内置类型 | Array、Map、Set、Date | 73 74 75 76## 限制条件 77 78### V1和V2的装饰器不允许混用 79 80**1.V1的自定义组件中不可以使用V2的装饰器** 81 82```typescript 83@Component 84struct Child { 85 // @Param不可以在@Component中使用,编译报错 86 // @Once @Require都是@Param的能力扩展装饰器,必须和@Param一起连用 87 @Param message: string = ""; 88 @Event changeMessage: (val: string) => void; // @Event 不可以在@Component中使用,编译报错 89 90 build() { 91 Column() { 92 Text(this.message) 93 .fontSize(50) 94 .fontWeight(FontWeight.Bold) 95 .onClick(() => { 96 this.changeMessage('world hello'); 97 }) 98 } 99 } 100} 101 102@Entry 103@Component 104struct Index { 105 @Local message: string = 'Hello World'; // @Local不可以在 @Component中使用,编译报错 106 107 build() { 108 Column() { 109 Text(this.message) 110 .fontSize(50) 111 .fontWeight(FontWeight.Bold) 112 Divider() 113 .color(Color.Blue) 114 Child({ 115 message: this.message, 116 changeMessage: (val: string) => { 117 this.message = val; 118 } 119 }) 120 } 121 .height('100%') 122 .width('100%') 123 } 124} 125``` 126 127V2的组件内装饰器不支持在V1的自定义组件中使用,编译会报错。 128 129\@Local、\@Param、\@Event、\@Provider、\@Consumer、\@Monitor、\@Computed和示例代码中的装饰器表现一致。 130 131**2.V2的自定义组件中不可以使用V1的装饰器** 132 133```typescript 134@ComponentV2 135struct Child { 136 @Prop message: string = ""; // @Prop不可以在@ComponentV2中使用,编译报错 137 @Link myId: number; // @Link不可以在@ComponentV2中使用,编译报错 138 139 build() { 140 Column() { 141 Text(this.message) 142 .fontSize(50) 143 .fontWeight(FontWeight.Bold) 144 .onClick(() => { 145 this.message = 'world hello'; 146 }) 147 Divider() 148 .color(Color.Blue) 149 Text(`${this.myId}`) 150 .id('HelloWorld') 151 .fontSize(50) 152 .fontWeight(FontWeight.Bold) 153 .onClick(() => { 154 this.myId++; 155 }) 156 } 157 } 158} 159 160@Entry 161@ComponentV2 162struct Index { 163 @State message: string = 'Hello World'; // @State不可以在@ComponentV2中使用,编译报错 164 @State @Watch('idChange') myId: number = 1; // @Watch不可以在@ComponentV2中使用,编译报错 165 166 idChange(propName: number) : void { 167 console.info(`id changed ${this.myId}`); 168 } 169 170 build() { 171 Column() { 172 Text(this.message) 173 .fontSize(50) 174 .fontWeight(FontWeight.Bold) 175 Divider() 176 .color(Color.Blue) 177 Child({ 178 message: this.message, 179 myId: this.myId 180 }) 181 } 182 .height('100%') 183 .width('100%') 184 .margin(5) 185 } 186} 187``` 188 189V1的组件内装饰器不支持在V2的自定义组件中使用,编译会报错。 190 191\@ObjectLink、\@Provide、\@Consume、\@StorageProp、\@StorageLink、\@LocalStorageProp、\@LocalStorageLink和示例的装饰器表现一致。 192 193### 多个装饰器不允许装饰同一个变量(\@Watch、\@Once、\@Require除外) 194 195```typescript 196@Component 197struct Child { 198 @State @Prop message: string = ""; // 多个V1的装饰器不可以修饰同一个变量,编译器报错 199 200 build() { 201 Column() { 202 Text(this.message) 203 .fontSize(50) 204 .fontWeight(FontWeight.Bold) 205 .onClick(() => { 206 this.message = 'world hello'; 207 }) 208 } 209 } 210} 211 212@Entry 213@ComponentV2 214struct Index { 215 @Local @Param message: string = 'Hello World'; // 多个V2的装饰器不允许修饰同一个变量,编译器报错 216 217 build() { 218 Column() { 219 Text(this.message) 220 .fontSize(50) 221 .fontWeight(FontWeight.Bold) 222 Divider() 223 .color(Color.Blue) 224 Child({ 225 message: this.message 226 }) 227 } 228 .height('100%') 229 .width('100%') 230 } 231} 232``` 233 234除了\@Watch、\@Once、\@Require这些能力扩展装饰器可以与其他装饰器配合使用外,其他装饰器不允许装饰同一个变量。 235 236## 混用场景介绍 237 238### V1和V2类相关装饰器混用 239 240**1.V1的自定义组件中使用被\@ObservedV2装饰的类对象** 241 242```typescript 243@ObservedV2 244class Info { 245 @Trace myId: number; // 有观测能力 246 name: string; // 无观测能力 247 @Track trackId: number = 1; // @Track作为V1的装饰器,不能在@ObservedV2中使用,编译时报错;消除编译错误请去掉@Track 248 249 constructor(id?: number, name?: string) { 250 this.myId = id || 0; 251 this.name = name || 'aaa'; 252 } 253} 254 255@Observed 256class message extends Info { // 继承自@ObservedV2装饰的类不可以被Observed装饰,编译时报错;消除编译错误请去掉@Observed 257} 258 259class MessageInfo extends Info { 260} 261 262@Entry 263@Component 264struct Index { 265 info1: Info = new Info(); // @ObservedV2装饰的Class可以在V1中使用,且被@Trace装饰的类属性具有观测能力 266 @State info2: Info = new Info(); // @ObservedV2装饰的Class不可以被V1的装饰器装饰,否则编译器报错;消除编译错误请去掉@State 267 268 @State messageInfo: MessageInfo = new MessageInfo(); // 继承自@ObservedV2的Class不可以被V1装饰器装饰,运行时报错;消除错误请去掉@State 269 build() { 270 Column() { 271 Text(`info1 name: ${this.info1.name}`) // name未被@Trace装饰,无法观察变化 272 .fontSize(50) 273 .fontWeight(FontWeight.Bold) 274 .onClick(() => { 275 this.info1.name += 'b'; 276 }) 277 Text(`info1 id: ${this.info1.myId}`) // myId被@Trace装饰,可观察变化 278 .fontSize(50) 279 .fontWeight(FontWeight.Bold) 280 .onClick(() => { 281 this.info1.myId += 1; 282 }) 283 Divider() 284 .color(Color.Blue) 285 Text(`info2 id: ${this.info2.myId}`) 286 .fontSize(50) 287 .fontWeight(FontWeight.Bold) 288 .onClick(() => { 289 this.info2.myId += 1; 290 }) 291 Divider() 292 .color(Color.Blue) 293 Text(`messageInfo id: ${this.messageInfo.myId}`) // 继承自@ObservedV2的Class被V1的装饰器装饰时会出现crash,运行时出错,需要去掉装饰器@State 294 .fontSize(50) 295 .fontWeight(FontWeight.Bold) 296 .onClick(() => { 297 this.messageInfo.myId += 1; 298 }) 299 } 300 .height('100%') 301 .width('100%') 302 .margin(5) 303 } 304} 305``` 306 307\@ObservedV2的使用需要遵循如下规则: 308 309* \@ObservedV2只能装饰Class,\@Trace、\@Type只能装饰类属性,且只能在\@ObservedV2中使用。 310* \@Track不可以在\@ObservedV2中使用。 311* 对于被\@ObservedV2装饰的Class,不可以直接被V1的装饰器装饰,否则编译时报错。 312* 示例中,开发者去掉报错的装饰器即可正常运行,被\@Trace装饰的类属性变化时可以观察到变化,否则不可以观测到变化。 313 314**2.V2的自定义组件中使用被\@Observed装饰的类对象** 315 316```typescript 317@Observed 318class Info { 319 @Track myId: number; // 无观测能力,只能防止因其他属性改变而导致的连带刷新 320 name: string; // 无观测能力 321 @Trace trackId: number = 1; // @Trace作为V2的装饰器,不能在@Observed中使用,编译时报错;消除编译错误请去掉@Trace 322 constructor(id?: number, name?: string) { 323 this.myId = id || 0; 324 this.name = name || 'aaa'; 325 } 326} 327 328@ObservedV2 329class message extends Info { // @ObservedV2装饰的Class不能继承@Observed,编译时报错;消除编译错误请去掉@ObservedV2 330} 331 332class MessageInfo extends Info { 333} 334 335@Entry 336@ComponentV2 337struct Index { 338 info1: Info = new Info(); // @Observed装饰的Class可以在V2中使用 339 @Local info2: Info = new Info(); // @Observe装饰的Class不可以被V2的装饰器装饰,否则编译器报错;消除编译错误请去掉@Local 340 @Local messageInfo: MessageInfo = new MessageInfo(); 341 build() { 342 Column() { 343 Text(`info1 name: ${this.info1.name}`) 344 .fontSize(50) 345 .fontWeight(FontWeight.Bold) 346 .onClick(() => { 347 this.info1.name += 'b'; 348 }) 349 Text(`info1 id: ${this.info1.myId}`) 350 .fontSize(50) 351 .fontWeight(FontWeight.Bold) 352 .onClick(() => { 353 this.info1.myId += 1; 354 }) 355 Divider() 356 .color(Color.Blue) 357 Text(`info2 id: ${this.info2.myId}`) 358 .fontSize(50) 359 .fontWeight(FontWeight.Bold) 360 .onClick(() => { 361 this.info2.myId += 1; 362 }) 363 Divider() 364 .color(Color.Blue) 365 // 继承自@ObservedV2的Class被V2装饰器装饰,V2的装饰器无类属性观测能力,所以不建议在V2中使用@Observed装饰的Class 366 Text(`messageInfo id: ${this.messageInfo.myId}`) 367 .fontSize(50) 368 .fontWeight(FontWeight.Bold) 369 .onClick(() => { 370 this.messageInfo.myId += 1; 371 }) 372 } 373 .height('100%') 374 .width('100%') 375 .margin(5) 376 } 377} 378``` 379 380不建议开发者在V2中使用\@Observed装饰的Class,因为\@Observed和\@Track仅能对类属性做区分,无观测能力,使用\@Observed和\@ObjectLink拆分嵌套数据才能够观测深层次数据,但\@ObjectLink无法在V2的自定义组件中使用。 381 382开发者在将V1的代码向V2迁移时,不建议在@ComponentV2中使用@Observed装饰的Class(在V2中无观测能力)。如果一定要使用,则遵循以下规则: 383 384* \@Observed只能装饰Class,且\@Trace不可以在\@Observed中使用。 385* \@Observed和\@Track无任何观测能力,只能用于防止Class中一个类属性改变而导致整个Class的刷新。 386* 继承自\@Observed的Class被V2装饰器装饰,V2的组件内装饰器无类属性观测能力,所以使用\@Observed会无法观测到类属性变化。 387* 示例中,开发者去掉报错的装饰器即可正常运行,由于无观测能力,所以不建议V2中使用\@Observed。 388 389### 不存在变量传递时,V1和V2的自定义组件混用 390 391**1.V1中使用V2的自定义组件** 392 393```typescript 394@ComponentV2 395struct Child { 396 @Local message: string = "hello"; 397 398 build() { 399 Column() { 400 Text(this.message) 401 .fontSize(50) 402 .fontWeight(FontWeight.Bold) 403 .onClick(() => { 404 this.message = 'world'; 405 }) 406 } 407 } 408} 409 410@Entry 411@Component 412struct Index { 413 @State message: string = 'Hello World'; 414 415 build() { 416 Column() { 417 Text(this.message) 418 .fontSize(50) 419 .fontWeight(FontWeight.Bold) 420 .onClick(() => { 421 this.message = 'world hello'; 422 }) 423 Divider() 424 .color(Color.Blue) 425 Child() 426 } 427 .height('100%') 428 .width('100%') 429 } 430} 431``` 432 433在V1中使用V2的自定义组件时,如果不存在变量传递,则不会产生影响。若涉及变量传递,请参见下一节[状态管理V1V2混用文档](arkts-v1-v2-mixusage.md)。 434 435**2.V2中使用V1的自定义组件** 436 437```typescript 438@Component 439struct Child { 440 @State message: string = "hello"; 441 442 build() { 443 Column() { 444 Text(this.message) 445 .fontSize(50) 446 .fontWeight(FontWeight.Bold) 447 .onClick(() => { 448 this.message = 'world'; 449 }) 450 } 451 } 452} 453 454@Entry 455@ComponentV2 456struct Index { 457 @Local message: string = 'Hello World'; 458 459 build() { 460 Column() { 461 Text(this.message) 462 .fontSize(50) 463 .fontWeight(FontWeight.Bold) 464 .onClick(() => { 465 this.message = 'world hello'; 466 }) 467 Divider() 468 .color(Color.Blue) 469 Child() 470 } 471 .height('100%') 472 .width('100%') 473 } 474} 475``` 476 477在V2中使用V1的自定义组件时,如果不存在变量传递,则不会产生影响。如果涉及变量传递,请参见下一节[状态管理V1V2混用文档](arkts-v1-v2-mixusage.md)。 478 479### 存在变量传递时,V1和V2的自定义组件数据混用 480 481**1.V1->V2:V1的普通变量传递给V2的自定义组件** 482 483```typescript 484class Info { 485 myId: number; 486 name: string; 487 488 constructor(myId?: number, name?: string) { 489 this.myId = myId || 0; 490 this.name = name || 'aaa'; 491 } 492} 493 494@ComponentV2 495struct Child { 496 // V2对数据输入有严格的管理,从父组件接受数据时,必须@Param装饰器进行数据接收 497 @Param @Once message: string = "hello"; // 可以观测到变化,同步回父组件依赖@Event,使用了@Once可以修改@Param装饰的变量 498 @Param @Once undefinedVal: string | undefined = undefined; // 使用了@Once可以修改@Param装饰的变量 499 @Param info: Info = new Info(); // 观测不到类属性变化 500 @Require @Param set: Set<number>; 501 502 build() { 503 Column() { 504 Text(`child message:${this.message}`) // 显示 string 505 .fontSize(30) 506 .fontWeight(FontWeight.Bold) 507 .onClick(() => { 508 this.message = 'world'; 509 }) 510 511 Divider() 512 .color(Color.Blue) 513 Text(`undefinedVal:${this.undefinedVal}`) // 显示 undefinedVal 514 .fontSize(30) 515 .fontWeight(FontWeight.Bold) 516 .onClick(() => { 517 this.undefinedVal = "change to define"; 518 }) 519 Divider() 520 .color(Color.Blue) 521 Text(`info id:${this.info.myId}`) // 显示 info:myId 522 .fontSize(30) 523 .fontWeight(FontWeight.Bold) 524 .onClick(() => { 525 this.info.myId++; 526 }) 527 Divider() 528 .color(Color.Blue) 529 ForEach(Array.from(this.set.values()), (item: number) => { // 显示 Set 530 Text(`${item}`) 531 .fontSize(30) 532 }) 533 } 534 .margin(5) 535 } 536} 537 538@Entry 539@Component 540struct Index { 541 message: string = 'Hello World'; // 简单数据 542 undefinedVal: undefined = undefined; // 简单类型,undefined 543 info: Info = new Info(); // Class类型 544 set: Set<number> = new Set([10, 20]); // 内置 类型 545 546 build() { 547 Column() { 548 Text(`message:${this.message}`) 549 .fontSize(30) 550 .fontWeight(FontWeight.Bold) 551 .onClick(() => { 552 this.message = 'world hello'; 553 }) 554 Divider() 555 .color(Color.Blue) 556 Child({ 557 message: this.message, 558 undefinedVal: this.undefinedVal, 559 info: this.info, 560 set: this.set 561 }) 562 } 563 .height('100%') 564 .width('100%') 565 } 566} 567``` 568 569当V1的普通变量传递给V2的自定义组件时,有如下限制: 570 571* V2的自定义组件必须通过\@Param接收数据。 572* 接收数据的观测能力为\@Param能力,对于接收的Class,需要通过\@ObservedV2和\@Trace才能观察变化。 573 574**2.V1->V2:V1的状态变量传递给V2的自定义组件** 575 576```typescript 577class Info { 578 myId: number; 579 name: string; 580 581 constructor(myId?: number, name?: string) { 582 this.myId = myId || 0; 583 this.name = name || 'aaa'; 584 } 585} 586 587@ComponentV2 588struct Child { 589 // V2对数据输入有严格的管理,从父组件接受数据时,必须@Param装饰器进行数据接收 590 @Param @Once message: string = "hello"; 591 @Param @Once undefinedVal: string | undefined = undefined; // 使用了@Once可以修改@Param装饰的变量 592 @Param info: Info = new Info(); 593 @Require @Param set: Set<number>; 594 build() { 595 Column() { 596 Text(`child message:${this.message}`) // 显示string 597 .fontSize(30) 598 .fontWeight(FontWeight.Bold) 599 .onClick(() => { 600 this.message = 'world'; 601 }) 602 Divider() 603 .color(Color.Blue) 604 Text(`undefinedVal:${this.undefinedVal}`) // 显示undefinedVal 605 .fontSize(30) 606 .fontWeight(FontWeight.Bold) 607 .onClick(() => { 608 this.undefinedVal = "change to define"; 609 }) 610 Divider() 611 .color(Color.Blue) 612 Text(`info id:${this.info.myId}`) // 显示info:myId 613 .fontSize(30) 614 .fontWeight(FontWeight.Bold) 615 .onClick(() => { 616 this.info.myId++; 617 }) 618 Divider() 619 .color(Color.Blue) 620 ForEach(Array.from(this.set.values()), (item: number) => { // 显示Set 621 Text(`${item}`) 622 .fontSize(30) 623 }) 624 } 625 .margin(5) 626 } 627} 628 629@Entry 630@Component 631struct Index { 632 @State message: string = 'Hello World'; // 简单类型数据,支持 633 @State undefinedVal: undefined = undefined; // 简单类型数据,undefined,支持 634 @State info: Info = new Info(); // Class类型,不支持传递,编译器报错;消除编译错误请去掉@State 635 @State set: Set<number> = new Set([10, 20]); // 内置类型,不支持传递,编译器报错;消除编译错误请去掉@State 636 637 build() { 638 Column() { 639 Text(`message:${this.message}`) 640 .fontSize(30) 641 .fontWeight(FontWeight.Bold) 642 .onClick(() => { 643 this.message = 'world hello'; 644 }) 645 Divider() 646 .color(Color.Blue) 647 Child({ 648 message: this.message, 649 undefinedVal: this.undefinedVal, 650 info: this.info, 651 set: this.set 652 }) 653 } 654 .height('100%') 655 .width('100%') 656 } 657} 658``` 659 660当V1的状态变量传递给V2的自定义组件时,遵循如下规则: 661 662* 仅支持简单类型变量,其余类型数据会在编译时报错。 663 664* 示例中使用了\@State装饰器,\@Prop、\@Link、\@ObjectLink、\@Provide、\@Consume、\@StorageProp、\@StorageLink、\@LocalStorageProp、\@LocalStorageLink行为和\@State保持一致。 665 666**3.V2->V1:V2的普通变量传递给V1的自定义组件** 667 668```typescript 669class Info { 670 myId: number; 671 name: string; 672 673 constructor(myId?: number, name?: string) { 674 this.myId = myId || 0; 675 this.name = name || 'aaa'; 676 } 677} 678 679@Component 680struct Child { 681 // V1从V2接收的状态变量,若使用装饰器,仅可使用@State、@Prop、@Provide接收 682 @State message: string = "hello"; // 可以观测到变化 683 @State info: Info = new Info(); // 可以观测一层类属性变化 684 @Prop undefinedVal: undefined | string = undefined; 685 @Provide setMap: Set<number> = new Set(); 686 build() { 687 Column() { 688 Text(`child message:${this.message}`) // 显示string 689 .fontSize(30) 690 .fontWeight(FontWeight.Bold) 691 .onClick(() => { 692 this.message = 'world'; 693 }) 694 Divider() 695 .color(Color.Blue) 696 Text(`undefinedVal:${this.undefinedVal}`) // 显示undefinedVal 697 .fontSize(30) 698 .fontWeight(FontWeight.Bold) 699 .onClick(() => { 700 this.undefinedVal = "change to define"; 701 }) 702 Divider() 703 .color(Color.Blue) 704 Text(`info id:${this.info.myId}`) // 显示info:myId 705 .fontSize(30) 706 .fontWeight(FontWeight.Bold) 707 .onClick(() => { 708 this.info.myId++; 709 }) 710 Divider() 711 .color(Color.Blue) 712 ForEach(Array.from(this.setMap.values()), (item: number) => { // 显示 Set 713 Text(`${item}`) 714 .fontSize(30) 715 }) 716 } 717 .margin(5) 718 } 719} 720 721@Entry 722@ComponentV2 723struct Index { 724 message: string = 'Hello World'; // 简单数据类型 725 undefinedVal: undefined = undefined; // 简单数据类型,undefined 726 info: Info = new Info(); // Class类型 727 set: Set<number> = new Set([10, 20]); // 内置 类型 728 729 build() { 730 Column() { 731 Text(`message:${this.message}`) 732 .fontSize(30) 733 .fontWeight(FontWeight.Bold) 734 .onClick(() => { 735 this.message = 'world hello'; 736 }) 737 Divider() 738 .color(Color.Blue) 739 Child({ 740 message: this.message, 741 undefinedVal: this.undefinedVal, 742 info: this.info, 743 setMap: this.set 744 }) 745 } 746 .height('100%') 747 .width('100%') 748 } 749} 750``` 751 752当V2的普通变量传递给V1自定义组件时: 753 754* V1可以不使用装饰器接收数据,接收的变量在V1自定义组件内是普通变量。 755 756* V1使用装饰器接收数据时,仅可通过\@State、\@Prop、\@Provide接收。 757 758**4.V2->V1:V2的状态变量传递给V1的自定义组件** 759 760```typescript 761class Info { 762 myId: number; 763 name: string; 764 765 constructor(myId?: number, name?: string) { 766 this.myId = myId || 0; 767 this.name = name || 'aaa'; 768 } 769} 770 771@Component 772struct Child { 773 // V1从V2接收的状态变量,仅可使用@State、@Prop、@Provide接收 774 @State message: string = "hello"; // 可以观测到变化 775 @State info: Info = new Info(); // 可以观测一层类属性变化 776 @Prop undefinedVal: undefined | string = undefined; 777 @Provide set: Set<number> = new Set(); 778 build() { 779 Column() { 780 Text(`child message:${this.message}`) // 显示 string 781 .fontSize(30) 782 .fontWeight(FontWeight.Bold) 783 .onClick(() => { 784 this.message = 'world'; 785 }) 786 Divider() 787 .color(Color.Blue) 788 Text(`undefinedVal:${this.undefinedVal}`) // 显示 undefinedVal 789 .fontSize(30) 790 .fontWeight(FontWeight.Bold) 791 .onClick(() => { 792 this.undefinedVal = "change to define"; 793 }) 794 Divider() 795 .color(Color.Blue) 796 Text(`info id:${this.info.myId}`) // 显示 info:myId 797 .fontSize(30) 798 .fontWeight(FontWeight.Bold) 799 .onClick(() => { 800 this.info.myId++; 801 }) 802 803 Divider() 804 .color(Color.Blue) 805 ForEach(Array.from(this.set.values()), (item: number) => { // 显示 Set 806 Text(`${item}`) 807 .fontSize(30) 808 }) 809 } 810 .margin(5) 811 } 812} 813 814@Entry 815@ComponentV2 816struct Index { 817 @Local message: string = 'Hello World'; // 简单数据类型,支持传递 818 @Provider() undefinedVal: undefined = undefined; // 简单数据类型,undefined,支持传递 819 @Consumer() info: Info = new Info(); // Class类型,支持传递 820 @Param set: Set<number> = new Set([10, 20]); // 内置类型,不支持传递;消除编译错误请去掉@Param 821 822 build() { 823 Column() { 824 Text(`message:${this.message}`) 825 .fontSize(30) 826 .fontWeight(FontWeight.Bold) 827 .onClick(() => { 828 this.message = 'world hello'; 829 }) 830 831 Divider() 832 .color(Color.Blue) 833 Child({ 834 message: this.message, 835 undefinedVal: this.undefinedVal, 836 info: this.info, 837 set: this.set 838 }) 839 } 840 .height('100%') 841 .width('100%') 842 } 843} 844``` 845 846V2的状态变量传递给V1的自定义组件,存在以下限制: 847 848* V1可以不使用装饰器接收数据,接收过来的变量在V1组定义组件内也会是普通变量。 849* V1使用装饰器接收数据时,仅可通过\@State、\@Prop、\@Provide接收。 850* V1使用装饰器接收数据时,不支持内置类型的数据。 851 852### 混用场景总结 853 854对V1和V2混用场景进行梳理后,可以总结出: 855 8561. 当V2的代码混用V1的代码时(即V1的组件或者类数据向V2传递),大部分V1的能力在V2都是被禁止的。 857 8582. 当V1的代码混用V2的代码时(即V2的组件或者类数据向V1传递),做了部分功能开放。例如:\@ObservedV2和\@Trace,这也是对V1嵌套类数据的观测能提供的最大的帮助。 859 860所以在代码开发时,不鼓励开发者使用V1和V2进行混用开发,但是对于代码迁移上,可以让V1的开发者逐步将代码向V2进行迁移,从而稳步替换V1的功能代码,并且十分不鼓励开发者在V2的代码架构上混用V1的代码。 861 862## 补充场景 863 864\@Observed和\@ObservedV2由于装饰Class类型,而Class可以进行多层级的嵌套,因此场景相对复杂,本节主要是对Class类型的自嵌套和内置类型的嵌套作一个详细的场景说明。由于\@Observed并没有\@ObservedV2+@Trace那样强大的深层次观测能力,不再对\@Observed的深层次嵌套进行讨论,只讨论\@ObservedV2在V1的使用场景。 865 866### 使用\@Observed+\@ObjectLink观测嵌套类 867 868```typescript 869@Observed 870class Info { 871 myId: number; 872 name: string; 873 874 constructor(myId?: number, name?: string) { 875 this.myId = myId || 0; 876 this.name = name || 'aaa'; 877 } 878} 879 880@Observed 881class MessageInfo { // 一层嵌套 882 @Track info: Info; // 防止messageId改变导致info的连带刷新 883 @Track messageId: number; // 防止messageId改变导致info的连带刷新 884 885 constructor(info?: Info, messageId?: number) { 886 this.info = info || new Info(); 887 this.messageId = messageId || 0; 888 } 889} 890 891@Observed 892class MessageInfoNested { // 二层嵌套 893 messageInfo: MessageInfo; 894 895 constructor(messageInfo?: MessageInfo) { 896 this.messageInfo = messageInfo || new MessageInfo(); 897 } 898} 899 900@Component 901struct GrandSon { 902 @ObjectLink info: Info; 903 904 build() { 905 Column() { 906 Text(`ObjectLink info info.myId:${this.info.myId}`) // 经过@ObjectLink拆解两次之后,观测到变化 907 .fontSize(30) 908 .onClick(() => { 909 this.info.myId++; 910 }) 911 } 912 } 913} 914 915@Component 916struct Child { 917 @ObjectLink messageInfo: MessageInfo; 918 919 build() { 920 Column() { 921 Text(`ObjectLink MessageInfo messageId:${this.messageInfo.messageId}`) // 经过@ObjectLink拆解之后,可以观测一层类属性变化 922 .fontSize(30) 923 .onClick(() => { 924 this.messageInfo.messageId++; 925 }) 926 Divider() 927 .color(Color.Blue) 928 Text(`ObjectLink MessageInfo info.myId:${this.messageInfo.info.myId}`) // 经过@ObjectLink拆解之后,依旧观测不到变化 929 .fontSize(30) 930 .onClick(() => { 931 this.messageInfo.info.myId++; 932 }) 933 GrandSon({info: this.messageInfo.info}); // 继续拆解一层子组件 934 } 935 } 936} 937 938 939 940@Entry 941@Component 942struct Index { 943 @State messageInfoNested: MessageInfoNested = new MessageInfoNested(); // 三层嵌套的数据,需要对所有数据进行观测。 944 945 build() { 946 Column() { 947 // 观察messageInfoNested 948 Text(`messageInfoNested messageId:${this.messageInfoNested.messageInfo.messageId}`) // @State只有一层类属性观测能力,无法观察到变化 949 .fontSize(30) 950 .onClick(() => { 951 this.messageInfoNested.messageInfo.messageId++; 952 }) 953 Divider() 954 .color(Color.Blue) 955 // 通过@ObjectLink嵌套观察 messageInfoId 956 Child({messageInfo: this.messageInfoNested.messageInfo}) // 经过拆分后,使用@ObjectLink拆分可以观察到深一层的变化 957 Divider() 958 .color(Color.Blue) 959 } 960 .height('100%') 961 .width('100%') 962 .margin(10) 963 } 964} 965``` 966 967上面的示例是三层嵌套的场景,表明: 968 969* V1装饰器的观测能力是对数据本身做代理,因此当数据存在嵌套时,V1只能通过\@Observed+\@ObjectLink的方式拆分子组件,观测深层次数据。 970 971* \@Track防止MessageInfo类中的info被messageId改变而连带刷新,开发者去掉\@Track可观测到,当messageId改变时,info的连带刷新,但是这并不是\@ObjectLink的观测能力。 972 973### 使用@ObsevedV2+@Trace观测嵌套类 974 975```typescript 976@ObservedV2 977class Info { 978 @Trace myId: number; 979 name: string; 980 981 constructor(myId?: number, name?: string) { 982 this.myId = myId || 0; 983 this.name = name || 'aaa'; 984 } 985} 986 987@Observed 988class MessageInfo { // 一层嵌套 989 @Track info: Info; // 防止messageId改变导致info的连带刷新 990 @Track messageId: number; // 防止messageId改变导致info的连带刷新 991 992 constructor(info?: Info, messageId?: number) { 993 this.info = info || new Info(); // 使用传入的info或创建一个新的Info 994 this.messageId = messageId || 0; 995 } 996} 997 998@Observed 999class MessageInfoNested { // 二层嵌套,MessageInfoNested如果是被@ObservedV2装饰,则不可以被V1的状态变量更新相关的装饰器装饰,如@State 1000 messageInfo: MessageInfo; 1001 1002 constructor(messageInfo?: MessageInfo) { 1003 this.messageInfo = messageInfo || new MessageInfo(); 1004 } 1005} 1006 1007@Component 1008struct Child { 1009 @ObjectLink messageInfo: MessageInfo; 1010 1011 build() { 1012 Column() { 1013 Text(`ObjectLink MessageInfo messageId:${this.messageInfo.messageId}`) // 经过@ObjectLink拆解之后,可以观测一层类属性变化 1014 .fontSize(30) 1015 .onClick(() => { 1016 this.messageInfo.messageId++; 1017 }) 1018 } 1019 } 1020} 1021 1022@Entry 1023@Component 1024struct Index { 1025 @State messageInfoNested: MessageInfoNested = new MessageInfoNested(); // 三层嵌套的数据,如何观测内部。 1026 1027 build() { 1028 Column() { 1029 // 观察messageInfoNested,@State只有一层观测能力,无法观察到变化 1030 Text(`messageInfoNested messageId:${this.messageInfoNested.messageInfo.messageId}`) 1031 .fontSize(30) 1032 .onClick(() => { 1033 this.messageInfoNested.messageInfo.messageId++; 1034 }) 1035 Divider() 1036 .color(Color.Blue) 1037 Text(`messageInfoNested name:${this.messageInfoNested.messageInfo.info.name}`) // 未被@Trace修饰,无法观测 1038 .fontSize(30) 1039 .onClick(() => { 1040 this.messageInfoNested.messageInfo.info.name += 'a'; 1041 }) 1042 Divider() 1043 .color(Color.Blue) 1044 Text(`messageInfoNested myId:${this.messageInfoNested.messageInfo.info.myId}`) // 被@Trace修饰,无论嵌套多少层都能观测 1045 .fontSize(30) 1046 .onClick(() => { 1047 this.messageInfoNested.messageInfo.info.myId++; 1048 }) 1049 Divider() 1050 .color(Color.Blue) 1051 // 通过@ObjectLink嵌套观察 messageInfoId 1052 Child({messageInfo: this.messageInfoNested.messageInfo}) // 经过拆分后,使用@ObjectLink拆分可以观察到深一层的变化 1053 } 1054 .height('100%') 1055 .width('100%') 1056 .margin(10) 1057 } 1058} 1059``` 1060 1061当使用\@ObservedV2+\@Trace时可以发现: 1062 1063* \@ObservedV2+\@Trace将观测能力实现在类属性上,所以当类属性被@Trace标记时,无论嵌套多少层都可以观测到变化。 1064* \@ObservedV2和\@Observed嵌套使用时,类对象能否被V1的装饰器装饰取决于最外层Class使用的装饰器。例如示例中嵌套在深处的\@ObservedV2装饰的Class不影响最外层的Class被V1的装饰器装饰。 1065