• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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![arkts-old-state-management](figures/arkts-new-observed-and-track-extend-sample.png)
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