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