1# \@ObservedV2装饰器和\@Trace装饰器:类属性变化观测 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiyujia926--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9为了增强状态管理框架对类对象中属性的观测能力,开发者可以使用\@ObservedV2装饰器和\@Trace装饰器装饰类以及类中的属性。 10 11 12\@ObservedV2和\@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。在阅读本文档前,建议提前阅读:[状态管理概述](./arkts-state-management-overview.md)来了解状态管理V2整体的能力架构。 13 14>**说明:** 15> 16> \@ObservedV2与\@Trace装饰器从API version 12开始支持。 17> 18> 从API version 12开始,\@ObservedV2与\@Trace装饰器支持在ArkTS卡片中使用。 19> 20> 从API version 12开始,\@ObservedV2与\@Trace装饰器支持在原子化服务中使用。 21 22## 概述 23 24\@ObservedV2装饰器与\@Trace装饰器用于装饰类以及类中的属性,使得被装饰的类和属性具有深度观测的能力: 25 26- \@ObservedV2装饰器与\@Trace装饰器需要配合使用,单独使用\@ObservedV2装饰器或\@Trace装饰器没有任何作用。 27- 被\@Trace装饰器装饰的属性property变化时,仅会通知property关联的组件进行刷新。 28- 在嵌套类中,嵌套类中的属性property被\@Trace装饰且嵌套类被\@ObservedV2装饰时,才具有触发UI刷新的能力。 29- 在继承类中,父类或子类中的属性property被\@Trace装饰且该property所在类被\@ObservedV2装饰时,才具有触发UI刷新的能力。 30- 未被\@Trace装饰的属性用在UI中无法感知到变化,也无法触发UI刷新。 31- \@ObservedV2的类实例目前不支持使用JSON.stringify进行序列化。 32- 使用\@ObservedV2与\@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。 33 34## 状态管理V1版本对嵌套类对象属性变化直接观测的局限性 35 36现有状态管理V1版本无法实现对嵌套类对象属性变化的直接观测。 37 38```ts 39@Observed 40class Father { 41 son: Son; 42 43 constructor(name: string, age: number) { 44 this.son = new Son(name, age); 45 } 46} 47@Observed 48class Son { 49 name: string; 50 age: number; 51 52 constructor(name: string, age: number) { 53 this.name = name; 54 this.age = age; 55 } 56} 57@Entry 58@Component 59struct Index { 60 @State father: Father = new Father('John', 8); 61 62 build() { 63 Row() { 64 Column() { 65 Text(`name: ${this.father.son.name} age: ${this.father.son.age}`) 66 .fontSize(50) 67 .fontWeight(FontWeight.Bold) 68 .onClick(() => { 69 this.father.son.age++; 70 }) 71 } 72 .width('100%') 73 } 74 .height('100%') 75 } 76} 77``` 78 79在上述代码中,点击Text组件增加age的值时,不会触发UI刷新。原因在于现有的状态管理框架无法观测到嵌套类中属性age的值变化。V1版本的解决方案是使用[\@ObjectLink装饰器](arkts-observed-and-objectlink.md)与自定义组件来实现观测。 80 81```ts 82@Observed 83class Father { 84 son: Son; 85 86 constructor(name: string, age: number) { 87 this.son = new Son(name, age); 88 } 89} 90@Observed 91class Son { 92 name: string; 93 age: number; 94 95 constructor(name: string, age: number) { 96 this.name = name; 97 this.age = age; 98 } 99} 100@Component 101struct Child { 102 @ObjectLink son: Son; 103 104 build() { 105 Row() { 106 Column() { 107 Text(`name: ${this.son.name} age: ${this.son.age}`) 108 .fontSize(50) 109 .fontWeight(FontWeight.Bold) 110 .onClick(() => { 111 this.son.age++; 112 }) 113 } 114 .width('100%') 115 } 116 .height('100%') 117 } 118} 119@Entry 120@Component 121struct Index { 122 @State father: Father = new Father('John', 8); 123 124 build() { 125 Column() { 126 Child({son: this.father.son}) 127 } 128 } 129} 130``` 131 132通过这种方式虽然能够实现对嵌套类中属性变化的观测,但是当嵌套层级较深时,代码将会变得十分复杂,易用性差。因此推出类装饰器\@ObservedV2与成员变量装饰器\@Trace,增强对嵌套类中属性变化的观测能力。 133 134## 装饰器说明 135 136| \@ObservedV2类装饰器 | 说明 | 137| ------------------ | ----------------------------------------------------- | 138| 装饰器参数 | 无。 | 139| 类装饰器 | 装饰class。需要放在class的定义前,使用new创建类对象。 | 140 141| \@Trace成员变量装饰器 | 说明 | 142| --------------------- | ------------------------------------------------------------ | 143| 装饰器参数 | 无。 | 144| 可装饰的变量 | class中成员属性。属性的类型可以为number、string、boolean、class、Array、Date、Map、Set等类型。 | 145 146## 观察变化 147 148使用\@ObservedV2装饰的类中被\@Trace装饰的属性具有被观测变化的能力,当该属性值变化时,会触发该属性绑定的UI组件刷新。 149 150- 在嵌套类中使用\@Trace装饰的属性具有被观测变化的能力。 151 152```ts 153@ObservedV2 154class Son { 155 @Trace age: number = 100; 156} 157class Father { 158 son: Son = new Son(); 159} 160@Entry 161@ComponentV2 162struct Index { 163 father: Father = new Father(); 164 165 build() { 166 Column() { 167 // 当点击改变age时,Text组件会刷新 168 Text(`${this.father.son.age}`) 169 .onClick(() => { 170 this.father.son.age++; 171 }) 172 } 173 } 174} 175 176``` 177 178- 在继承类中使用\@Trace装饰的属性具有被观测变化的能力。 179 180```ts 181@ObservedV2 182class Father { 183 @Trace name: string = 'Tom'; 184} 185class Son extends Father { 186} 187@Entry 188@ComponentV2 189struct Index { 190 son: Son = new Son(); 191 192 build() { 193 Column() { 194 // 当点击改变name时,Text组件会刷新 195 Text(`${this.son.name}`) 196 .onClick(() => { 197 this.son.name = 'Jack'; 198 }) 199 } 200 } 201} 202``` 203 204- 类中使用\@Trace装饰的静态属性具有被观测变化的能力。 205 206```ts 207@ObservedV2 208class Manager { 209 @Trace static count: number = 1; 210} 211@Entry 212@ComponentV2 213struct Index { 214 build() { 215 Column() { 216 // 当点击改变count时,Text组件会刷新 217 Text(`${Manager.count}`) 218 .onClick(() => { 219 Manager.count++; 220 }) 221 } 222 } 223} 224``` 225 226- \@Trace装饰内置类型时,可以观测各自API导致的变化: 227 228 | 类型 | 可观测变化的API | 229 | ----- | ------------------------------------------------------------ | 230 | Array | push、pop、shift、unshift、splice、copyWithin、fill、reverse、sort | 231 | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds | 232 | Map | set, clear, delete | 233 | Set | add, clear, delete | 234 235## 使用限制 236 237\@ObservedV2与\@Trace装饰器存在以下使用限制: 238 239- 非\@Trace装饰的成员属性用在UI上无法触发UI刷新。 240 241```ts 242@ObservedV2 243class Person { 244 id: number = 0; 245 @Trace age: number = 8; 246} 247@Entry 248@ComponentV2 249struct Index { 250 person: Person = new Person(); 251 252 build() { 253 Column() { 254 // age被@Trace装饰,用在UI中可以触发UI刷新 255 Text(`${this.person.age}`) 256 .onClick(() => { 257 this.person.age++; // 点击会触发UI刷新 258 }) 259 // id未被@Trace装饰,用在UI中不会触发UI刷新 260 Text(`${this.person.id}`) // 当id变化时不会刷新 261 .onClick(() => { 262 this.person.id++; // 点击不会触发UI刷新 263 }) 264 } 265 } 266} 267``` 268 269- \@ObservedV2仅能装饰class,无法装饰自定义组件。 270 271```ts 272@ObservedV2 // 错误用法,编译时报错 273struct Index { 274 build() { 275 } 276} 277``` 278 279- \@Trace不能用在没有被\@ObservedV2装饰的class上。 280 281```ts 282class User { 283 id: number = 0; 284 @Trace name: string = 'Tom'; // 错误用法,编译时报错 285} 286``` 287 288- \@Trace是class中属性的装饰器,不能用在struct中。 289 290```ts 291@ComponentV2 292struct Comp { 293 @Trace message: string = 'Hello World'; // 错误用法,编译时报错 294 295 build() { 296 } 297} 298``` 299 300- \@ObservedV2、\@Trace不能与[\@Observed](arkts-observed-and-objectlink.md)、[\@Track](arkts-track.md)混合使用。 301 302```ts 303@Observed 304class User { 305 @Trace name: string = 'Tom'; // 错误用法,编译时报错 306} 307 308@ObservedV2 309class Person { 310 @Track name: string = 'Jack'; // 错误用法,编译时报错 311} 312``` 313 314- 使用\@ObservedV2与\@Trace装饰的类不能和[\@State](arkts-state.md)等V1的装饰器混合使用,编译时报错。 315 316```ts 317// 以@State装饰器为例 318@ObservedV2 319class Job { 320 @Trace jobName: string = 'Teacher'; 321} 322@ObservedV2 323class Info { 324 @Trace name: string = 'Tom'; 325 @Trace age: number = 25; 326 job: Job = new Job(); 327} 328@Entry 329@Component 330struct Index { 331 @State info: Info = new Info(); // 无法混用,编译时报错 332 333 build() { 334 Column() { 335 Text(`name: ${this.info.name}`) 336 Text(`age: ${this.info.age}`) 337 Text(`jobName: ${this.info.job.jobName}`) 338 Button('change age') 339 .onClick(() => { 340 this.info.age++; 341 }) 342 Button('Change job') 343 .onClick(() => { 344 this.info.job.jobName = 'Doctor'; 345 }) 346 } 347 } 348} 349``` 350 351- 继承自\@ObservedV2的类无法和\@State等V1的装饰器混用,运行时报错。 352 353```ts 354// 以@State装饰器为例 355@ObservedV2 356class Job { 357 @Trace jobName: string = 'Teacher'; 358} 359@ObservedV2 360class Info { 361 @Trace name: string = 'Tom'; 362 @Trace age: number = 25; 363 job: Job = new Job(); 364} 365class Message extends Info { 366 constructor() { 367 super(); 368 } 369} 370@Entry 371@Component 372struct Index { 373 @State message: Message = new Message(); // 无法混用,运行时报错 374 375 build() { 376 Column() { 377 Text(`name: ${this.message.name}`) 378 Text(`age: ${this.message.age}`) 379 Text(`jobName: ${this.message.job.jobName}`) 380 Button('change age') 381 .onClick(() => { 382 this.message.age++; 383 }) 384 Button('Change job') 385 .onClick(() => { 386 this.message.job.jobName = 'Doctor'; 387 }) 388 } 389 } 390} 391``` 392 393- \@ObservedV2的类实例目前不支持使用JSON.stringify进行序列化。 394- 使用\@ObservedV2与\@Trace装饰器的类,需通过new操作符实例化后,才具备被观测变化的能力。 395 396## 使用场景 397 398### 嵌套类场景 399 400在下面的嵌套类场景中,Pencil类是Son类中最里层的类,Pencil类被\@ObservedV2装饰且属性length被\@Trace装饰,此时length的变化能够被观测到。 401 402\@Trace装饰器与现有状态管理框架的[\@Track](arkts-track.md)与[\@State](arkts-state.md)装饰器的能力不同,@Track使class具有属性级更新的能力,但并不具备深度观测的能力;而\@State只能观测到对象本身以及第一层的变化,对于多层嵌套场景只能通过封装自定义组件,搭配[\@Observed](arkts-observed-and-objectlink.md)和[\@ObjectLink](arkts-observed-and-objectlink.md)来实现观测。 403 404* 点击Button('change length'),length是被\@Trace装饰的属性,它的变化可以触发关联的UI组件,即UINode (1)的刷新,并输出"id: 1 renderTimes: x"的日志,其中x根据点击次数依次增长。 405* 自定义组件Page中的son是常规变量,因此点击Button('assign Son')并不会观测到变化。 406* 当点击Button('assign Son')后,再点击Button('change length')并不会引起UI刷新。因为此时son的地址改变,其关联的UI组件并没有关联到最新的son。 407 408```ts 409@ObservedV2 410class Pencil { 411 @Trace length: number = 21; // 当length变化时,会刷新关联的组件 412} 413class Bag { 414 width: number = 50; 415 height: number = 60; 416 pencil: Pencil = new Pencil(); 417} 418class Son { 419 age: number = 5; 420 school: string = 'some'; 421 bag: Bag = new Bag(); 422} 423 424@Entry 425@ComponentV2 426struct Page { 427 son: Son = new Son(); 428 renderTimes: number = 0; 429 isRender(id: number): number { 430 console.info(`id: ${id} renderTimes: ${this.renderTimes}`); 431 this.renderTimes++; 432 return 40; 433 } 434 435 build() { 436 Column() { 437 Text('pencil length'+ this.son.bag.pencil.length) 438 .fontSize(this.isRender(1)) // UINode (1) 439 Button('change length') 440 .onClick(() => { 441 // 点击更改length值,UINode(1)会刷新 442 this.son.bag.pencil.length += 100; 443 }) 444 Button('assign Son') 445 .onClick(() => { 446 // 由于变量son非状态变量,因此无法刷新UINode(1) 447 this.son = new Son(); 448 }) 449 } 450 } 451} 452``` 453 454 455### 继承类场景 456 457\@Trace支持在类的继承场景中使用,无论是在基类还是继承类中,只有被\@Trace装饰的属性才具有被观测变化的能力。 458以下例子中,声明class GrandFather、Father、Uncle、Son、Cousin,继承关系如下图。 459 460 461 462 463创建类Son和类Cousin的实例,点击Button('change Son age')和Button('change Cousin age')可以触发UI的刷新。 464 465```ts 466@ObservedV2 467class GrandFather { 468 @Trace age: number = 0; 469 470 constructor(age: number) { 471 this.age = age; 472 } 473} 474class Father extends GrandFather{ 475 constructor(father: number) { 476 super(father); 477 } 478} 479class Uncle extends GrandFather { 480 constructor(uncle: number) { 481 super(uncle); 482 } 483} 484class Son extends Father { 485 constructor(son: number) { 486 super(son); 487 } 488} 489class Cousin extends Uncle { 490 constructor(cousin: number) { 491 super(cousin); 492 } 493} 494@Entry 495@ComponentV2 496struct Index { 497 son: Son = new Son(0); 498 cousin: Cousin = new Cousin(0); 499 renderTimes: number = 0; 500 501 isRender(id: number): number { 502 console.info(`id: ${id} renderTimes: ${this.renderTimes}`); 503 this.renderTimes++; 504 return 40; 505 } 506 507 build() { 508 Row() { 509 Column() { 510 Text(`Son ${this.son.age}`) 511 .fontSize(this.isRender(1)) 512 .fontWeight(FontWeight.Bold) 513 Text(`Cousin ${this.cousin.age}`) 514 .fontSize(this.isRender(2)) 515 .fontWeight(FontWeight.Bold) 516 Button('change Son age') 517 .onClick(() => { 518 this.son.age++; 519 }) 520 Button('change Cousin age') 521 .onClick(() => { 522 this.cousin.age++; 523 }) 524 } 525 .width('100%') 526 } 527 .height('100%') 528 } 529} 530``` 531 532### \@Trace装饰基础类型的数组 533 534\@Trace装饰数组时,使用支持的API能够观测到变化。支持的API见[观察变化](#观察变化)。 535在下面的示例中\@ObservedV2装饰的Arr类中的属性numberArr是\@Trace装饰的数组,当使用数组API操作numberArr时,可以观测到对应的变化。注意使用数组长度进行判断以防越界访问。 536 537```ts 538let nextId: number = 0; 539 540@ObservedV2 541class Arr { 542 id: number = 0; 543 @Trace numberArr: number[] = []; 544 545 constructor() { 546 this.id = nextId++; 547 this.numberArr = [0, 1, 2]; 548 } 549} 550 551@Entry 552@ComponentV2 553struct Index { 554 arr: Arr = new Arr(); 555 556 build() { 557 Column() { 558 Text(`length: ${this.arr.numberArr.length}`) 559 .fontSize(40) 560 Divider() 561 if (this.arr.numberArr.length >= 3) { 562 Text(`${this.arr.numberArr[0]}`) 563 .fontSize(40) 564 .onClick(() => { 565 this.arr.numberArr[0]++; 566 }) 567 Text(`${this.arr.numberArr[1]}`) 568 .fontSize(40) 569 .onClick(() => { 570 this.arr.numberArr[1]++; 571 }) 572 Text(`${this.arr.numberArr[2]}`) 573 .fontSize(40) 574 .onClick(() => { 575 this.arr.numberArr[2]++; 576 }) 577 } 578 579 Divider() 580 581 ForEach(this.arr.numberArr, (item: number, index: number) => { 582 Text(`${index} ${item}`) 583 .fontSize(40) 584 }) 585 586 Button('push') 587 .onClick(() => { 588 this.arr.numberArr.push(50); 589 }) 590 591 Button('pop') 592 .onClick(() => { 593 this.arr.numberArr.pop(); 594 }) 595 596 Button('shift') 597 .onClick(() => { 598 this.arr.numberArr.shift(); 599 }) 600 601 Button('splice') 602 .onClick(() => { 603 this.arr.numberArr.splice(1, 0, 60); 604 }) 605 606 607 Button('unshift') 608 .onClick(() => { 609 this.arr.numberArr.unshift(100); 610 }) 611 612 Button('copywithin') 613 .onClick(() => { 614 this.arr.numberArr.copyWithin(0, 1, 2); 615 }) 616 617 Button('fill') 618 .onClick(() => { 619 this.arr.numberArr.fill(0, 2, 4); 620 }) 621 622 Button('reverse') 623 .onClick(() => { 624 this.arr.numberArr.reverse(); 625 }) 626 627 Button('sort') 628 .onClick(() => { 629 this.arr.numberArr.sort(); 630 }) 631 } 632 } 633} 634``` 635 636### \@Trace装饰对象数组 637 638* \@Trace装饰对象数组personList以及Person类中的age属性,因此当personList、age改变时均可以观测到变化。 639* 点击Text组件更改age时,Text组件会刷新。 640 641```ts 642let nextId: number = 0; 643 644@ObservedV2 645class Person { 646 @Trace age: number = 0; 647 648 constructor(age: number) { 649 this.age = age; 650 } 651} 652 653@ObservedV2 654class Info { 655 id: number = 0; 656 @Trace personList: Person[] = []; 657 658 constructor() { 659 this.id = nextId++; 660 this.personList = [new Person(0), new Person(1), new Person(2)]; 661 } 662} 663 664@Entry 665@ComponentV2 666struct Index { 667 info: Info = new Info(); 668 669 build() { 670 Column() { 671 Text(`length: ${this.info.personList.length}`) 672 .fontSize(40) 673 Divider() 674 if (this.info.personList.length >= 3) { 675 Text(`${this.info.personList[0].age}`) 676 .fontSize(40) 677 .onClick(() => { 678 this.info.personList[0].age++; 679 }) 680 681 Text(`${this.info.personList[1].age}`) 682 .fontSize(40) 683 .onClick(() => { 684 this.info.personList[1].age++; 685 }) 686 687 Text(`${this.info.personList[2].age}`) 688 .fontSize(40) 689 .onClick(() => { 690 this.info.personList[2].age++; 691 }) 692 } 693 694 Divider() 695 696 ForEach(this.info.personList, (item: Person, index: number) => { 697 Text(`${index} ${item.age}`) 698 .fontSize(40) 699 }) 700 } 701 } 702} 703 704``` 705 706### \@Trace装饰Map类型 707 708* 被\@Trace装饰的Map类型属性可以观测到调用API带来的变化,包括 set、clear、delete。 709* 因为Info类被\@ObservedV2装饰且属性memberMap被\@Trace装饰,点击Button('init map')对memberMap赋值也可以观测到变化。 710 711```ts 712@ObservedV2 713class Info { 714 @Trace memberMap: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); 715} 716 717@Entry 718@ComponentV2 719struct MapSample { 720 info: Info = new Info(); 721 722 build() { 723 Row() { 724 Column() { 725 ForEach(Array.from(this.info.memberMap.entries()), (item: [number, string]) => { 726 Text(`${item[0]}`) 727 .fontSize(30) 728 Text(`${item[1]}`) 729 .fontSize(30) 730 Divider() 731 }) 732 Button('init map') 733 .onClick(() => { 734 this.info.memberMap = new Map([[0, 'a'], [1, 'b'], [3, 'c']]); 735 }) 736 Button('set new one') 737 .onClick(() => { 738 this.info.memberMap.set(4, 'd'); 739 }) 740 Button('clear') 741 .onClick(() => { 742 this.info.memberMap.clear(); 743 }) 744 Button('set the key: 0') 745 .onClick(() => { 746 this.info.memberMap.set(0, 'aa'); 747 }) 748 Button('delete the first one') 749 .onClick(() => { 750 this.info.memberMap.delete(0); 751 }) 752 } 753 .width('100%') 754 } 755 .height('100%') 756 } 757} 758``` 759 760### \@Trace装饰Set类型 761 762* 被\@Trace装饰的Set类型属性可以观测到调用API带来的变化,包括 add, clear, delete。 763* 因为Info类被\@ObservedV2装饰且属性memberSet被\@Trace装饰,点击Button('init set')对memberSet赋值也可以观察变化。 764 765```ts 766@ObservedV2 767class Info { 768 @Trace memberSet: Set<number> = new Set([0, 1, 2, 3, 4]); 769} 770 771@Entry 772@ComponentV2 773struct SetSample { 774 info: Info = new Info(); 775 776 build() { 777 Row() { 778 Column() { 779 ForEach(Array.from(this.info.memberSet.entries()), (item: [number, number]) => { 780 Text(`${item[0]}`) 781 .fontSize(30) 782 Divider() 783 }) 784 Button('init set') 785 .onClick(() => { 786 this.info.memberSet = new Set([0, 1, 2, 3, 4]); 787 }) 788 Button('set new one') 789 .onClick(() => { 790 this.info.memberSet.add(5); 791 }) 792 Button('clear') 793 .onClick(() => { 794 this.info.memberSet.clear(); 795 }) 796 Button('delete the first one') 797 .onClick(() => { 798 this.info.memberSet.delete(0); 799 }) 800 } 801 .width('100%') 802 } 803 .height('100%') 804 } 805} 806``` 807 808 809### \@Trace装饰Date类型 810 811* \@Trace装饰的Date类型属性可以观测调用API带来的变化,包括 setFullYear、setMonth、setDate、setHours、setMinutes、setSeconds、setMilliseconds、setTime、setUTCFullYear、setUTCMonth、setUTCDate、setUTCHours、setUTCMinutes、setUTCSeconds、setUTCMilliseconds。 812* 因为Info类被\@ObservedV2装饰且属性selectedDate被\@Trace装饰,点击Button('set selectedDate to 2023-07-08')对selectedDate赋值也可以观测到变化。 813 814```ts 815@ObservedV2 816class Info { 817 @Trace selectedDate: Date = new Date('2021-08-08') 818} 819 820@Entry 821@ComponentV2 822struct DateSample { 823 info: Info = new Info() 824 825 build() { 826 Column() { 827 Button('set selectedDate to 2023-07-08') 828 .margin(10) 829 .onClick(() => { 830 this.info.selectedDate = new Date('2023-07-08'); 831 }) 832 Button('increase the year by 1') 833 .margin(10) 834 .onClick(() => { 835 this.info.selectedDate.setFullYear(this.info.selectedDate.getFullYear() + 1); 836 }) 837 Button('increase the month by 1') 838 .margin(10) 839 .onClick(() => { 840 this.info.selectedDate.setMonth(this.info.selectedDate.getMonth() + 1); 841 }) 842 Button('increase the day by 1') 843 .margin(10) 844 .onClick(() => { 845 this.info.selectedDate.setDate(this.info.selectedDate.getDate() + 1); 846 }) 847 DatePicker({ 848 start: new Date('1970-1-1'), 849 end: new Date('2100-1-1'), 850 selected: this.info.selectedDate 851 }) 852 }.width('100%') 853 } 854} 855``` 856