1# MVVM
2
3After understanding the concept of state management, you may be eager to develop your own applications. However, if you do not pay attention to the project structure during application development, the relationship between components becomes blurred as the project becomes larger and more state variables are designed. When you develop a new function, the costs of development and maintenance will increase exponentially. Therefore, this document describes the MVVM mode and the relationship between the UI development mode of ArkUI and the MVVM, and provides guidance for you to design your own project structures. In this way, product development and maintenance are easier during product iteration and upgrade.
4
5
6This topic covers most decorators of the state management V1. You are advised to read [State Management Overview](./arkts-state-management-overview.md) and topics related to decorators of V1 in advance.
7
8## Introduction
9
10### Concepts
11
12During application development, UI updates need to be synchronized in real time with data state changes. This synchronization usually determines the performance and user experience of applications. To reduce the complexity of data and UI synchronization, ArkUI uses the Model-View-ViewModel (MVVM) architecture. The MVVM divides an application into three core parts: Model, View, and ViewModel to separate data, views, and logic. In this mode, the UI can be automatically updated with the state change without manual processing, thereby more efficiently managing the binding and update of data and views.
13
14- Model: stores and manages application data and service logic without directly interacting with the UI. Generally, Model obtains data from back-end APIs and serves as the data basis of applications, which ensures data consistency and integrity.
15- View: displays data on the UI and interacts with users. No service logic is contained. It dynamically updates the UI by binding the data provided by the ViewModel.
16- ViewModel: manages UI state and interaction logic. As a bridge between Model and View, ViewModel monitors data changes in Model, notifies views to update the UI, processes user interaction events, and converts the events into data operations.
17
18The UI development mode of ArkUI belongs to the MVVM mode. By introducing the concept of MVVM, you may have basic understanding on how the state management work in MVVM. State management aims to drive data update and enable you to focus only on page design without paying attention to the UI re-render logic. In addition, ViewModel enables state variables to automatically maintain data. In this way, MVVM provides a more efficient way for you to develop applications.
19
20### ArkUI Development
21
22The UI development mode of ArkUI is the MVVM mode, in which the state variables play the role of ViewModel to re-render the UI and data. The following figure shows the overall architecture.
23
24![MVVM image](./figures/MVVM_architecture.png)
25
26### Layer Description
27
28**View**
29
30* Page components: All applications are classified by page, such as the login page, list page, editing page, help page, and copyright page. The data required by each page may be completely different, or the same set of data can be shared with multiple pages.
31* Business components: a functional component that has some service capabilities of the application. Typically, the business component may be associated with the data in the ViewModel of the project and cannot be shared with other projects.
32* Common components: Similar to built-in components, these components are not associated with the ViewModel data in the application. These components can be shared across multiple projects to implement common functions.
33
34**ViewModel**
35
36* Page data: organized by page. When a user opens a page, some pages may not be switched to. Therefore, it is recommended that the page data be designed in lazy loading mode.
37
38> The differences between the ViewModel data and the Model data are as follows:
39>
40> Model data, a set of service data of the application, is organized based on the entire project.
41>
42> ViewModel data provides data used on a page. It may be a part of the service data of the entire application. In addition, ViewModel also provides auxiliary data for page display, which may be irrelevant to the application services.
43
44**Model**
45
46Model provides the original data of applications. From the perspective of the UI, there are two ways to implement this layer:
47
48* Local implementation: through native C++.
49
50* Remote implementation: through the I/O port (RESTful).
51
52> **NOTE**
53>
54> When the local implementation is used, the non-UI thread model must exist when the system processes data. At this time, the processed data change needs to be notified to the ViewModel in real time, causing data changes and UI re-renders. In this case, automatic thread switching becomes very important. Generally, the ViewModel and View can work properly only when they are executed in the UI thread. Therefore, a mechanism is required to automatically complete thread switching when the UI needs to be notified of a re-render.
55
56### Core Principles of the Architecture
57
58**Cross-layer access is not allowed.**
59
60* View cannot directly call data from Model. Instead, use the methods provided by ViewModel to call.
61* Model data cannot modify the UI directly but notifies the ViewModel to update the data.
62
63**The lower layer cannot access the upper layer data.**
64
65The lower layer can only notify the upper layer to update the data. In the service logic, you cannot write code at the lower layer to obtain the upper-layer data. For example, the logic processing at ViewModel cannot depend on a value on the UI at View.
66
67**Non-parent-child components cannot directly access each other.**
68
69This is the core principle of View design. A component should comply with the following logic:
70
71* Do not directly access the parent component (using the event or subscription capability).
72* Do not directly access sibling components. This is because components can access only the child nodes (through parameter passing) and parent nodes (through events or notifications) that they can see. In this way, components are decoupled.
73
74Reasons:
75
76* The child components used by the component are clear, therefore, access is allowed.
77* The parent node where the component is placed is unknown. Therefore, the component can access the parent node only through notifications or events.
78* It is impossible for a component to know its sibling nodes, so the component cannot manipulate the sibling nodes.
79
80## Memo Development
81
82This section describes how to use ArkUI to design your own applications. The sample code in this section directly develops functions without designing the code architecture and considering subsequent maintenance, and the decorators required for function development are introduced as well.
83
84### @State
85
86* As the most commonly used decorator, @State is used to define state variables. Generally, the @State decorator is used as the data source of the parent component. When you click @State, the state variable is updated to re-render the UI. If the @State decorator is removed, the UI cannot be refreshed.
87
88```typescript
89@Entry
90@Component
91struct Index {
92  @State isFinished: boolean = false;
93
94  build() {
95    Column() {
96      Row() {
97        Text('To-Dos')
98          .fontSize(30)
99          .fontWeight(FontWeight.Bold)
100      }
101      .width('100%')
102      .margin({top: 10, bottom: 10})
103
104      // To-Do list
105      Row({space: 15}) {
106        if (this.isFinished) {
107          // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
108          Image($r('app.media.finished'))
109            .width(28)
110            .height(28)
111        }
112        else {
113          // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
114          Image($r('app.media.unfinished'))
115            .width(28)
116            .height(28)
117        }
118        Text('Learn maths')
119          .fontSize(24)
120          .fontWeight(450)
121          .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
122      }
123      .height('40%')
124      .width('100%')
125      .border({width: 5})
126      .padding({left: 15})
127      .onClick(() => {
128        this.isFinished = !this.isFinished;
129      })
130    }
131    .height('100%')
132    .width('100%')
133    .margin({top: 5, bottom: 5})
134    .backgroundColor('#90f1f3f5')
135  }
136}
137```
138
139The following figure shows the final effect.
140
141![state](./figures/MVVM_state.gif)
142
143### @Prop and @Link
144
145In the preceding example, all code is written in the @Entry decorated component. As more and more components need to be rendered, you need to split the @Entry decorated component and use the @Prop and @Link decorators to decorate the split child components.
146
147* @Prop creates a one-way synchronization between the parent and child components. The child component can perform deep copy of the data from the parent component or update the data from the parent component or itself. However, it cannot synchronize data from the parent component.
148* @Link creates a two-way synchronization between the parent and child components. When the parent component changes, all @Links are notified. In addition, when @Link is updated, the corresponding variables of the parent component are notified as well.
149
150```typescript
151@Component
152struct TodoComponent {
153  build() {
154    Row() {
155      Text('To-Dos')
156        .fontSize(30)
157        .fontWeight(FontWeight.Bold)
158    }
159    .width('100%')
160    .margin({top: 10, bottom: 10})
161  }
162}
163
164@Component
165struct AllChooseComponent {
166  @Link isFinished: boolean;
167
168  build() {
169    Row() {
170      Button('Select All', {type: ButtonType.Normal})
171        .onClick(() => {
172          this.isFinished = !this.isFinished;
173        })
174        .fontSize(30)
175        .fontWeight(FontWeight.Bold)
176        .backgroundColor('#f7f6cc74')
177    }
178    .padding({left: 15})
179    .width('100%')
180    .margin({top: 10, bottom: 10})
181  }
182}
183
184@Component
185struct ThingsComponent1 {
186  @Prop isFinished: boolean;
187
188  build() {
189    // Task 1
190    Row({space: 15}) {
191      if (this.isFinished) {
192        // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
193        Image($r('app.media.finished'))
194          .width(28)
195          .height(28)
196      }
197      else {
198        // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
199        Image($r('app.media.unfinished'))
200          .width(28)
201          .height(28)
202      }
203      Text('Study language')
204        .fontSize(24)
205        .fontWeight(450)
206        .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
207    }
208    .height('40%')
209    .width('100%')
210    .border({width: 5})
211    .padding({left: 15})
212    .onClick(() => {
213      this.isFinished = !this.isFinished;
214    })
215  }
216}
217
218@Component
219struct ThingsComponent2 {
220  @Prop isFinished: boolean;
221
222  build() {
223    // Task 1
224    Row({space: 15}) {
225      if (this.isFinished) {
226        // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
227        Image($r('app.media.finished'))
228          .width(28)
229          .height(28)
230      }
231      else {
232        // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
233        Image($r('app.media.unfinished'))
234          .width(28)
235          .height(28)
236      }
237      Text('Learn maths')
238        .fontSize(24)
239        .fontWeight(450)
240        .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
241    }
242    .height('40%')
243    .width('100%')
244    .border({width: 5})
245    .padding({left: 15})
246    .onClick(() => {
247      this.isFinished = !this.isFinished;
248    })
249  }
250}
251
252@Entry
253@Component
254struct Index {
255  @State isFinished: boolean = false;
256
257  build() {
258    Column() {
259      // All To-Do items.
260      TodoComponent()
261
262      // Select all.
263      AllChooseComponent({isFinished: this.isFinished})
264
265      // Task 1
266      ThingsComponent1({isFinished: this.isFinished})
267
268      // Task 2
269      ThingsComponent2({isFinished: this.isFinished})
270    }
271    .height('100%')
272    .width('100%')
273    .margin({top: 5, bottom: 5})
274    .backgroundColor('#90f1f3f5')
275  }
276}
277```
278
279The following figure shows the effect.
280
281![Prop&Link](./figures/MVVM_Prop&Link.gif)
282
283### Rendering Repeated Components
284
285* In the previous example, although the child component is split, the code of component 1 is similar to that of component 2. When the rendered components have the same configurations except data, **ForEach** is used to render the repeated components.
286* In this way, redundant code is decreased and the code structure is clearer.
287
288```typescript
289@Component
290struct TodoComponent {
291  build() {
292    Row() {
293      Text('To-Dos')
294        .fontSize(30)
295        .fontWeight(FontWeight.Bold)
296    }
297    .width('100%')
298    .margin({top: 10, bottom: 10})
299  }
300}
301
302@Component
303struct AllChooseComponent {
304  @Link isFinished: boolean;
305
306  build() {
307    Row() {
308      Button('Select All', {type: ButtonType.Normal})
309        .onClick(() => {
310          this.isFinished = !this.isFinished;
311        })
312        .fontSize(30)
313        .fontWeight(FontWeight.Bold)
314        .backgroundColor('#f7f6cc74')
315    }
316    .padding({left: 15})
317    .width('100%')
318    .margin({top: 10, bottom: 10})
319  }
320}
321
322@Component
323struct ThingsComponent {
324  @Prop isFinished: boolean;
325  @Prop things: string;
326  build() {
327    // Task 1
328    Row({space: 15}) {
329      if (this.isFinished) {
330        // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
331        Image($r('app.media.finished'))
332          .width(28)
333          .height(28)
334      }
335      else {
336        // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
337        Image($r('app.media.unfinished'))
338          .width(28)
339          .height(28)
340      }
341      Text(`${this.things}`)
342        .fontSize(24)
343        .fontWeight(450)
344        .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
345    }
346    .height('8%')
347    .width('90%')
348    .padding({left: 15})
349    .opacity(this.isFinished ? 0.3: 1)
350    .border({width:1})
351    .borderColor(Color.White)
352    .borderRadius(25)
353    .backgroundColor(Color.White)
354    .onClick(() => {
355      this.isFinished = !this.isFinished;
356    })
357  }
358}
359
360@Entry
361@Component
362struct Index {
363  @State isFinished: boolean = false;
364  @State planList: string[] = [
365    '7:30 Get up'
366    '8:30 Breakfast'
367    '11:30 Lunch'
368    '17:30 Dinner'
369    '21:30 Snack'
370    '22:30 Shower'
371    '1:30 Go to sleep'
372  ];
373
374  build() {
375    Column() {
376      // All To-Do items.
377      TodoComponent()
378
379      // Select all.
380      AllChooseComponent({isFinished: this.isFinished})
381
382      List() {
383        ForEach(this.planList, (item: string) => {
384          // Task 1
385          ThingsComponent({isFinished: this.isFinished, things: item})
386            .margin(5)
387        })
388      }
389
390    }
391    .height('100%')
392    .width('100%')
393    .margin({top: 5, bottom: 5})
394    .backgroundColor('#90f1f3f5')
395  }
396}
397```
398
399The following figure shows the effect.
400
401![ForEach](./figures/MVVM_ForEach.gif)
402
403### @Builder
404
405* The **Builder** method is used to define methods in a component so that the same code can be reused in the component.
406* In this example, the @Builder method is used for deduplication and moving out data so that the code is clearer and easier to read. Compared with the initial code, the @Entry decorated component is used only to process page construction logic and does not process a large amount of content irrelevant to page design.
407
408```typescript
409@Observed
410class TodoListData {
411  planList: string[] = [
412    '7:30 Get up'
413    '8:30 Breakfast'
414    '11:30 Lunch'
415    '17:30 Dinner'
416    '21:30 Snack'
417    '22:30 Shower'
418    '1:30 Go to sleep'
419  ];
420}
421
422@Component
423struct TodoComponent {
424  build() {
425    Row() {
426      Text('To-Dos')
427        .fontSize(30)
428        .fontWeight(FontWeight.Bold)
429    }
430    .width('100%')
431    .margin({top: 10, bottom: 10})
432  }
433}
434
435@Component
436struct AllChooseComponent {
437  @Link isFinished: boolean;
438
439  build() {
440    Row() {
441      Button('Select All', {type: ButtonType.Capsule})
442        .onClick(() => {
443          this.isFinished = !this.isFinished;
444        })
445        .fontSize(30)
446        .fontWeight(FontWeight.Bold)
447        .backgroundColor('#f7f6cc74')
448    }
449    .padding({left: 15})
450    .width('100%')
451    .margin({top: 10, bottom: 10})
452  }
453}
454
455@Component
456struct ThingsComponent {
457  @Prop isFinished: boolean;
458  @Prop things: string;
459
460  @Builder displayIcon(icon: Resource) {
461    Image(icon)
462      .width(28)
463      .height(28)
464      .onClick(() => {
465        this.isFinished = !this.isFinished;
466      })
467  }
468
469  build() {
470    // Task 1
471    Row({space: 15}) {
472      if (this.isFinished) {
473        // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
474        this.displayIcon($r('app.media.finished'));
475      }
476      else {
477        // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
478        this.displayIcon($r('app.media.unfinished'));
479      }
480      Text(`${this.things}`)
481        .fontSize(24)
482        .fontWeight(450)
483        .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
484        .onClick(() => {
485          this.things += '!'
486        })
487    }
488    .height('8%')
489    .width('90%')
490    .padding({left: 15})
491    .opacity(this.isFinished ? 0.3: 1)
492    .border({width:1})
493    .borderColor(Color.White)
494    .borderRadius(25)
495    .backgroundColor(Color.White)
496  }
497}
498
499@Entry
500@Component
501struct Index {
502  @State isFinished: boolean = false;
503  @State data: TodoListData = new TodoListData();
504
505  build() {
506    Column() {
507      // All To-Do items.
508      TodoComponent()
509
510      // Select all.
511      AllChooseComponent({isFinished: this.isFinished})
512
513      List() {
514        ForEach(this.data.planList, (item: string) => {
515          // Task 1
516          ThingsComponent({isFinished: this.isFinished, things: item})
517            .margin(5)
518        })
519      }
520
521    }
522    .height('100%')
523    .width('100%')
524    .margin({top: 5, bottom: 5})
525    .backgroundColor('#90f1f3f5')
526  }
527}
528```
529
530 The following figure shows the effect.
531
532![builder](./figures/MVVM_builder.gif)
533
534### Summary
535
536* After the code structure is optimized step by step, you can see that the @Entry decorated component serves as the entry of the page and the **build** function only needs to combine the required components, which is similar to building blocks. A child component called by a page is similar to a block and waits to be called by a required page. A state variable is similar to an adhesive. When a UI re-render event is triggered, the state variable can automatically re-render the bound component to implement on-demand page refresh.
537* Although the existing architecture does not use the MVVM design concept, the core concept of MVVM shows that the UI development of ArkUI should use the MVVM mode. Pages and components are at the View layer, and pages are responsible for combining components. A state variable is used to drive the component re-render to refresh the page. The ViewModel data needs to have a source, which is from the Model layer.
538* The code functions in the example are simple. However, as the number of functions increases, the code of the main page increases. When more functions are added to the Memo application and other pages need to use the components of the main page, how to organize the project structure? The MVVM mode is the answer.
539
540## Developing a To-Do List Through MVVM
541
542The previous section describes how to organize code in non-MVVM mode. As the code of the main page becomes larger, a proper layering method should be adopted to make the project structure clear and prevent components from referencing each other. Therefore, the entire system will not be affected during subsequent maintenance. This section uses MVVM to reorganize the code in the previous section to introduce the core file organization of MVVM.
543
544### MVVM File Structure
545
546* src
547  * ets
548    * pages ------ Stores page components.
549    * views ------ Stores business components.
550    * shares ------ Stores common components.
551    * service ------ Data services.
552      * app.ts ------ Service entry.
553      * LoginViewMode ----- Login page
554      * xxxModel ------ Other pages.
555
556### Layered Design
557
558**Model**
559
560* The Model layer stores the core data structure of the application. This layer is not closely related to UI development. You can encapsulate the data structure based on your service logic.
561
562**ViewModel**
563
564> **NOTE**
565>
566> The ViewModel layer not only stores data, but also provides data services and processing. Therefore, many frameworks use "service" to represent this layer.
567
568* The ViewModel layer is the data layer that serves views. Generally, it has two features:
569  1. Data is organized based on pages.
570  2. Data on each page is lazy loaded.
571
572**View**
573
574The View layer is organized as required. You need to distinguish the following three types of components at this layer:
575
576* Page components: provides the overall page layout, implements redirection between multiple pages, and processes foreground and background events.
577* Business components: referenced by a page to construct a page.
578* Shared components: shared by multiple projects.
579
580> The differences between shared components and business components are as follows:
581>
582> A business component contains ViewModel data. Without ViewModel, the component cannot be executed.
583>
584> A shared component does not contain ViewModel data. The data required needs to be passed from external systems. A shared component contains a self-contained component that can work as long as external parameters (without service parameters) are met.
585
586### Example
587
588The file structure is reconstructed based on the MVVM mode as follows:
589
590* src
591  * ets
592    * pages
593      * index
594    * View
595      * TodoComponent
596      * AllchooseComponent
597      * ThingsComponent
598    * ViewModel
599      * ThingsViewModel
600
601The code is as follows:
602
603* Index.ets
604
605  ```typescript
606  // import view
607  import { TodoComponent } from './../View/TodoComponent'
608  import { MultiChooseComponent } from './../View/AllchooseComponent'
609  import { ThingsComponent } from './../View/ThingsComponent'
610
611  // import viewModel
612  import { TodoListData } from '../ViewModel/ThingsViewModel'
613
614  @Entry
615  @Component
616  struct Index {
617    @State isFinished: boolean = false;
618    @State data: TodoListData = new TodoListData();
619
620    build() {
621      Column() {
622        Row({space: 40}) {
623          // All To-Do items.
624          TodoComponent()
625
626          // Select all.
627          MultiChooseComponent({isFinished: this.isFinished})
628        }
629
630        List() {
631          ForEach(this.data.planList, (item: string) => {
632            // Task 1
633            ThingsComponent({isFinished: this.isFinished, things: item})
634              .margin(5)
635          })
636        }
637
638      }
639      .height('100%')
640      .width('100%')
641      .margin({top: 5, bottom: 5})
642      .backgroundColor('#90f1f3f5')
643    }
644  }
645  ```
646
647  * TodoComponent
648
649  ```typescript
650  @Component
651  export struct TodoComponent {
652    build() {
653      Row() {
654        Text('To-Dos')
655          .fontSize(30)
656          .fontWeight(FontWeight.Bold)
657      }
658      .padding({left: 15})
659      .width('50%')
660      .margin({top: 10, bottom: 10})
661    }
662  }
663  ```
664
665  * AllchooseComponent.ets
666
667  ```typescript
668@Component
669  export struct MultiChooseComponent {
670    @Link isFinished: boolean;
671
672    build() {
673      Row() {
674        Button('Multiselect', {type: ButtonType.Capsule})
675          .onClick(() => {
676            this.isFinished = !this.isFinished;
677          })
678          .fontSize(30)
679          .fontWeight(FontWeight.Bold)
680          .backgroundColor('#f7f6cc74')
681      }
682      .padding({left: 15})
683      .width('100%')
684      .margin({top: 10, bottom: 10})
685    }
686  }
687  ```
688
689  * ThingsComponent
690
691  ```typescript
692@Component
693  export struct ThingsComponent {
694    @Prop isFinished: boolean;
695    @Prop things: string;
696
697    @Builder displayIcon(icon: Resource) {
698      Image(icon)
699        .width(28)
700        .height(28)
701        .onClick(() => {
702          this.isFinished = !this.isFinished;
703        })
704    }
705
706    build() {
707      // Task 1
708      Row({space: 15}) {
709        if (this.isFinished) {
710          // 'app.media.finished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
711          this.displayIcon($r('app.media.finished'));
712        }
713        else {
714          // 'app.media.unfinished' is only an example. Replace it with the actual one in use. Otherwise, the imageSource instance fails to be created, and subsequent operations cannot be performed.
715          this.displayIcon($r('app.media.unfinished'));
716        }
717        Text(`${this.things}`)
718          .fontSize(24)
719          .fontWeight(450)
720          .decoration({type: this.isFinished ? TextDecorationType.LineThrough : TextDecorationType.None})
721          .onClick(() => {
722            this.things += '!'
723          })
724      }
725      .height('8%')
726      .width('90%')
727      .padding({left: 15})
728      .opacity(this.isFinished ? 0.3: 1)
729      .border({width:1})
730      .borderColor(Color.White)
731      .borderRadius(25)
732      .backgroundColor(Color.White)
733    }
734  }
735
736  ```
737
738  ThingsViewModel.ets
739
740  ```typescript
741@Observed
742  export class TodoListData {
743    planList: string[] = [
744      '7:30 Get up'
745      '8:30 Breakfast'
746      '11:30 Lunch'
747      '17:30 Dinner'
748      '21:30 Snack'
749      '22:30 Shower'
750      '1:30 Go to sleep'
751    ];
752  }
753  ```
754
755  After the code is split in MVVM mode, the project structure and responsibilities of each module are clearer. If a new page needs to use the event component, you only need to import the corresponding component because the local data is fixed and the logic at the Model layer is not written. You can reconstruct your project structures based on the example.
756
757  The following figure shows the effect.
758
759  ![MVVM_index.gif](./figures/MVVM_index.gif)
760
761
762
763
764