android2harmony 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agents/self-tester.md +339 -0
- package/dist/index.js +625 -7
- package/dist/index.js.map +4 -4
- package/package.json +37 -29
- package/skills/hmos-incremental-ui-align/README.md +251 -0
- package/skills/hmos-incremental-ui-align/SKILL.md +365 -0
- package/skills/hmos-incremental-ui-align/diff_analysis.md +53 -0
- package/skills/hmos-incremental-ui-align/page_align.md +62 -0
- package/skills/hmos-incremental-ui-align/references/Comparison_Template.md +38 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Link/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/217/214/345/220/221/345/220/214/346/255/245.md +648 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Observed/350/243/205/351/245/260/345/231/250/345/222/214@ObjectLink/350/243/205/351/245/260/345/231/250/357/274/232/345/265/214/345/245/227/347/261/273/345/257/271/350/261/241/345/261/236/346/200/247/345/217/230/345/214/226.md +2089 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Prop/350/243/205/351/245/260/345/231/250/357/274/232/347/210/266/345/255/220/345/215/225/345/220/221/345/220/214/346/255/245.md +1033 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Provide/350/243/205/351/245/260/345/231/250/345/222/214@Consume/350/243/205/351/245/260/345/231/250/357/274/232/344/270/216/345/220/216/344/273/243/347/273/204/344/273/266/345/217/214/345/220/221/345/220/214/346/255/245.md +1183 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@State/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/347/212/266/346/200/201.md +576 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Track/350/243/205/351/245/260/345/231/250/357/274/232class/345/257/271/350/261/241/345/261/236/346/200/247/347/272/247/346/233/264/346/226/260.md +297 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/@Watch/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/346/233/264/346/224/271/351/200/232/347/237/245.md +395 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/AppStorage/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200/347/232/204UI/347/212/266/346/200/201/345/255/230/345/202/250.md +903 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/Environment/357/274/232/350/256/276/345/244/207/347/216/257/345/242/203/346/237/245/350/257/242.md +106 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/LocalStorage/357/274/232/351/241/265/351/235/242/347/272/247UI/347/212/266/346/200/201/345/255/230/345/202/250.md +1178 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/MVVM/346/250/241/345/274/217V1.md +911 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243/PersistentStorage/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +355 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243//347/256/241/347/220/206/345/272/224/347/224/250/346/213/245/346/234/211/347/232/204/347/212/266/346/200/201/346/246/202/350/277/260.md +11 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/!!/350/257/255/346/263/225/357/274/232/345/217/214/345/220/221/347/273/221/345/256/232.md +206 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Computed/350/243/205/351/245/260/345/231/250/357/274/232/350/256/241/347/256/227/345/261/236/346/200/247.md +373 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Event/350/243/205/351/245/260/345/231/250/357/274/232/350/247/204/350/214/203/347/273/204/344/273/266/350/276/223/345/207/272.md +158 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Local/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/206/205/351/203/250/347/212/266/346/200/201.md +750 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Monitor/350/243/205/351/245/260/345/231/250/357/274/232/347/212/266/346/200/201/345/217/230/351/207/217/344/277/256/346/224/271/345/274/202/346/255/245/347/233/221/345/220/254.md +1704 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@ObservedV2/350/243/205/351/245/260/345/231/250/345/222/214@Trace/350/243/205/351/245/260/345/231/250/357/274/232/347/261/273/345/261/236/346/200/247/345/217/230/345/214/226/350/247/202/346/265/213.md +1012 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Once/350/243/205/351/245/260/345/231/250/357/274/232/345/210/235/345/247/213/345/214/226/345/220/214/346/255/245/344/270/200/346/254/241.md +164 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Param/350/243/205/351/245/260/345/231/250/357/274/232/347/273/204/344/273/266/345/244/226/351/203/250/350/276/223/345/205/245.md +840 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Provider/350/243/205/351/245/260/345/231/250/345/222/214@Consumer/350/243/205/351/245/260/345/231/250/357/274/232/350/267/250/347/273/204/344/273/266/345/261/202/347/272/247/345/217/214/345/220/221/345/220/214/346/255/245.md +856 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/@Type/350/243/205/351/245/260/345/231/250/357/274/232/346/240/207/350/256/260/347/261/273/345/261/236/346/200/247/347/232/204/347/261/273/345/236/213.md +83 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/AppStorageV2/357/274/232/345/272/224/347/224/250/345/205/250/345/261/200UI/347/212/266/346/200/201/345/255/230/345/202/250.md +294 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/MVVM/346/250/241/345/274/217/357/274/210V2/357/274/211.md +1407 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/PersistenceV2/357/274/232/346/214/201/344/271/205/345/214/226/345/255/230/345/202/250UI/347/212/266/346/200/201.md +1220 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2/_/350/214/203/345/274/217/351/200/211/346/213/251/350/257/264/346/230/216.md +47 -0
- package/skills/hmos-incremental-ui-align/references/MVVM/345/274/200/345/217/221/346/226/207/346/241/243V2//347/212/266/346/200/201/347/256/241/347/220/206V1/345/220/221V2/350/277/201/347/247/273/344/270/216/346/267/267/347/224/250/346/214/207/345/257/274.md +840 -0
- package/skills/hmos-incremental-ui-align/references/State_Model_Template.md +74 -0
- package/skills/hmos-incremental-ui-align/references/UI_Analysis_Template.md +34 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-atomic-component-mapping-reference.md +2533 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-interaction-mapping-reference.md +555 -0
- package/skills/hmos-incremental-ui-align/references/android-to-harmonyOS-ui-layout-mapping-reference.md +117 -0
- package/skills/hmos-incremental-ui-align/scripts/app_feature_verify.ts +999 -0
- package/skills/hmos-incremental-ui-align/scripts/extract_checklist.ts +343 -0
- package/skills/hmos-incremental-ui-align/scripts/navigation-capure.md +76 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture.ts +977 -0
- package/skills/hmos-incremental-ui-align/scripts/page_capture_burst.ts +188 -0
- package/tools/autotest/deps/autotest-agent-0.1.1.tgz +0 -0
- package/tools/autotest/engine/batch-launcher.ts +326 -0
- package/tools/autotest/engine/report-tool.ts +773 -0
- package/tools/autotest/engine/self-test-runner.ts +1024 -0
- package/tools/autotest/engine/testcases-tool.ts +246 -0
- package/tools/autotest/resolve-metadata-tool.ts +143 -0
- package/tools/autotest/runner/logger.ts +45 -0
- package/tools/autotest/runner/process-utils.ts +41 -0
- package/tools/autotest/validate.ts +115 -0
|
@@ -0,0 +1,1407 @@
|
|
|
1
|
+
# MVVM模式(V2)
|
|
2
|
+
|
|
3
|
+
## 概述
|
|
4
|
+
|
|
5
|
+
在应用开发中,UI的更新需要随着数据状态的变化进行实时同步,而这种同步往往决定了应用程序的性能和用户体验。为了解决数据与UI同步的复杂性,ArkUI采用了Model-View-ViewModel(MVVM)架构模式。MVVM将应用分为Model、View和ViewModel三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI可以随着状态的变化自动更新,无需手动处理,从而高效管理数据和视图的绑定与更新。
|
|
6
|
+
|
|
7
|
+
* Model:存储和管理应用数据及业务逻辑,不直接与用户界面交互。通常从后端接口获取数据,是应用程序的数据基础,确保数据的一致性和完整性。
|
|
8
|
+
* View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定ViewModel层提供的数据来动态更新UI。
|
|
9
|
+
* ViewModel:负责管理UI状态和交互逻辑。作为连接Model和View的桥梁,ViewModel监控Model数据的变化,通知View更新UI,同时处理用户交互事件并转换为数据操作。
|
|
10
|
+
|
|
11
|
+
## 通过状态管理V2版本实现ViewModel
|
|
12
|
+
|
|
13
|
+
在MVVM模式中,ViewModel负责管理数据状态,并在数据变化时自动更新视图。ArkUI的状态管理V2版本提供了丰富的装饰器和工具,帮助开发者在自定义组件之间共享数据,确保数据变化自动同步到UI。常用的状态管理装饰器包括[@Local](../状态管理(V2)/管理组件拥有的状态/@Local装饰器:组件内部状态/arkts-new-local.md)、[@Param](../状态管理(V2)/管理组件拥有的状态/@Param:组件外部输入/arkts-new-param.md)、[@Event](../状态管理(V2)/管理组件拥有的状态/@Event装饰器:规范组件输出/arkts-new-event.md)、[@ObservedV2、@Trace](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-observedv2-and-trace)等等。此外,V2还提供了[AppStorageV2](<../状态管理(V2)/管理应用拥有的状态/AppStorageV2 应用全局UI状态存储/arkts-new-appstoragev2.md>)和[PersistenceV2](<../状态管理(V2)/管理应用拥有的状态/PersistenceV2 持久化存储UI状态/arkts-new-persistencev2.md>)作为全局状态存储工具,用于应用间的状态共享和持久化存储。
|
|
14
|
+
|
|
15
|
+
本节将通过一个简单的todolist示例,逐步引入和使用状态管理V2的装饰器及工具,从基础的静态任务列表开始,逐步扩展功能。每个步骤都基于上一步扩展,帮助开发者循序渐进地理解并掌握各个装饰器的使用方法。
|
|
16
|
+
|
|
17
|
+
### 基础示例
|
|
18
|
+
|
|
19
|
+
首先,从静态待办事项列表开始。在示例1中,任务是静态的,没有状态变化和动态交互。
|
|
20
|
+
|
|
21
|
+
**示例1**
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
1. // src/main/ets/pages/BasicPage.ets
|
|
25
|
+
2. @Entry
|
|
26
|
+
3. @ComponentV2
|
|
27
|
+
4. struct TodoList {
|
|
28
|
+
5. build() {
|
|
29
|
+
6. Column() {
|
|
30
|
+
7. Text('To do')
|
|
31
|
+
8. .fontSize(40)
|
|
32
|
+
9. .margin({ bottom: 10 })
|
|
33
|
+
10. Text('task1')
|
|
34
|
+
11. Text('task2')
|
|
35
|
+
12. Text('task3')
|
|
36
|
+
13. }
|
|
37
|
+
14. }
|
|
38
|
+
15. }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
[BasicPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/BasicPage.ets#L17-L33)
|
|
42
|
+
|
|
43
|
+
### 添加@Local,实现对组件内部状态观测
|
|
44
|
+
|
|
45
|
+
完成静态待办列表展示后,为了让用户能够更改任务的完成状态,需要使待办事项能够响应交互并动态更新显示。为此,引入@Local装饰器管理组件内部的状态。被@Local装饰的变量发生变化时,触发绑定的UI组件刷新。
|
|
46
|
+
|
|
47
|
+
在示例2中,新增@Local装饰的isFinish属性代表任务是否完成。两个图标finished.png和unfinished.png用于展示任务完成或未完成的状态。点击待办事项时,isFinish状态切换,更新图标和文本删除线的效果。
|
|
48
|
+
|
|
49
|
+
**示例2**
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
1. // src/main/ets/pages/LocalPage.ets
|
|
53
|
+
2. @Entry
|
|
54
|
+
3. @ComponentV2
|
|
55
|
+
4. struct TodoList {
|
|
56
|
+
5. @Local isFinish: boolean = false;
|
|
57
|
+
|
|
58
|
+
7. build() {
|
|
59
|
+
8. Column() {
|
|
60
|
+
9. Text('To do')
|
|
61
|
+
10. .fontSize(40)
|
|
62
|
+
11. .margin({ bottom: 10 })
|
|
63
|
+
12. Row() {
|
|
64
|
+
13. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
65
|
+
14. Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
66
|
+
15. .width(28)
|
|
67
|
+
16. .height(28)
|
|
68
|
+
17. Text('task1')
|
|
69
|
+
18. .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
70
|
+
19. }
|
|
71
|
+
20. .onClick(() => this.isFinish = !this.isFinish)
|
|
72
|
+
21. }
|
|
73
|
+
22. }
|
|
74
|
+
23. }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
[LocalPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/LocalPage.ets#L17-L41)
|
|
78
|
+
|
|
79
|
+
### 添加@Param,实现组件接收外部输入
|
|
80
|
+
|
|
81
|
+
实现任务本地状态切换后,为增强待办事项列表的灵活性,需要能够动态设置每个任务的名称,而不是固定在代码中。引入@Param装饰器后,子组件被装饰的变量可以接收父组件传入的值,实现单向数据同步。@Param默认只读,使用@Param [@Once](../状态管理(V2)/管理组件拥有的状态/@Once:初始化同步一次/arkts-new-once.md)可在子组件中对传入的值进行本地更新。
|
|
82
|
+
|
|
83
|
+
在示例3中,每个待办事项抽象为TaskItem组件。@Param修饰的taskName属性从父组件TodoList传入任务名称,使TaskItem组件灵活且可复用,能够接收并渲染不同的任务名称。@Param @Once装饰的isFinish属性接收初始值后,可在子组件内更新。
|
|
84
|
+
|
|
85
|
+
**示例3**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
1. // src/main/ets/pages/ParamPage.ets
|
|
89
|
+
2. @ComponentV2
|
|
90
|
+
3. struct TaskItem {
|
|
91
|
+
4. @Param taskName: string = '';
|
|
92
|
+
5. @Param @Once isFinish: boolean = false;
|
|
93
|
+
|
|
94
|
+
7. build() {
|
|
95
|
+
8. Row() {
|
|
96
|
+
9. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
97
|
+
10. Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
98
|
+
11. .width(28)
|
|
99
|
+
12. .height(28)
|
|
100
|
+
13. Text(this.taskName)
|
|
101
|
+
14. .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
102
|
+
15. }
|
|
103
|
+
16. .onClick(() => this.isFinish = !this.isFinish)
|
|
104
|
+
17. }
|
|
105
|
+
18. }
|
|
106
|
+
|
|
107
|
+
20. @Entry
|
|
108
|
+
21. @ComponentV2
|
|
109
|
+
22. struct TodoList {
|
|
110
|
+
23. build() {
|
|
111
|
+
24. Column() {
|
|
112
|
+
25. Text('To do')
|
|
113
|
+
26. .fontSize(40)
|
|
114
|
+
27. .margin({ bottom: 10 })
|
|
115
|
+
28. TaskItem({ taskName: 'Task 1', isFinish: false })
|
|
116
|
+
29. TaskItem({ taskName: 'Task 2', isFinish: false })
|
|
117
|
+
30. TaskItem({ taskName: 'Task 3', isFinish: false })
|
|
118
|
+
31. }
|
|
119
|
+
32. }
|
|
120
|
+
33. }
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
[ParamPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/ParamPage.ets#L17-L51)
|
|
124
|
+
|
|
125
|
+
### 添加@Event,实现组件对外输出
|
|
126
|
+
|
|
127
|
+
实现任务名称动态设置后,任务列表内容固定。为了实现任务列表的动态扩展,需要增加任务项的添加和删除功能。为此,引入@Event装饰器,用于子组件向父组件输出数据。
|
|
128
|
+
|
|
129
|
+
在示例4中,每个TaskItem增加了删除按钮,同时任务列表底部增加了添加新任务的功能。点击子组件TaskItem的“删除”按钮时,deleteTask事件会被触发并传递给父组件TodoList,父组件响应并移除任务。通过使用@Param和@Event,子组件不仅能接收父组件的数据,还能将事件传递回父组件,实现数据双向同步。
|
|
130
|
+
|
|
131
|
+
**示例4**
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
1. // src/main/ets/pages/EventPage.ets
|
|
135
|
+
2. @ComponentV2
|
|
136
|
+
3. struct TaskItem {
|
|
137
|
+
4. @Param taskName: string = '';
|
|
138
|
+
5. @Param @Once isFinish: boolean = false;
|
|
139
|
+
6. @Event deleteTask: () => void = () => {};
|
|
140
|
+
|
|
141
|
+
8. build() {
|
|
142
|
+
9. Row() {
|
|
143
|
+
10. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
144
|
+
11. Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
145
|
+
12. .width(28)
|
|
146
|
+
13. .height(28)
|
|
147
|
+
14. Text(this.taskName)
|
|
148
|
+
15. .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
149
|
+
16. Button('Delete')
|
|
150
|
+
17. .onClick(() => {
|
|
151
|
+
18. this.deleteTask();
|
|
152
|
+
19. })
|
|
153
|
+
20. }
|
|
154
|
+
21. .onClick(() => {
|
|
155
|
+
22. this.isFinish = !this.isFinish;
|
|
156
|
+
23. })
|
|
157
|
+
24. }
|
|
158
|
+
25. }
|
|
159
|
+
|
|
160
|
+
27. @Entry
|
|
161
|
+
28. @ComponentV2
|
|
162
|
+
29. struct TodoList {
|
|
163
|
+
30. @Local tasks: string[] = ['task1', 'task2', 'task3'];
|
|
164
|
+
31. @Local newTaskName: string = '';
|
|
165
|
+
|
|
166
|
+
33. build() {
|
|
167
|
+
34. Column() {
|
|
168
|
+
35. Text('To do')
|
|
169
|
+
36. .fontSize(40)
|
|
170
|
+
37. .margin({ bottom: 10 })
|
|
171
|
+
38. ForEach(this.tasks, (task: string) => {
|
|
172
|
+
39. TaskItem({
|
|
173
|
+
40. taskName: task,
|
|
174
|
+
41. isFinish: false,
|
|
175
|
+
42. deleteTask: () => {
|
|
176
|
+
43. this.tasks.splice(this.tasks.indexOf(task), 1);
|
|
177
|
+
44. }
|
|
178
|
+
45. })
|
|
179
|
+
46. })
|
|
180
|
+
47. Row() {
|
|
181
|
+
48. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
182
|
+
49. .onChange((value) => {
|
|
183
|
+
50. this.newTaskName = value;
|
|
184
|
+
51. })
|
|
185
|
+
52. .width('70%')
|
|
186
|
+
53. Button('+')
|
|
187
|
+
54. .onClick(() => {
|
|
188
|
+
55. this.tasks.push(this.newTaskName);
|
|
189
|
+
56. this.newTaskName = '';
|
|
190
|
+
57. })
|
|
191
|
+
58. }
|
|
192
|
+
59. }
|
|
193
|
+
60. }
|
|
194
|
+
61. }
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
[EventPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/EventPage.ets#L17-L79)
|
|
198
|
+
|
|
199
|
+
### 添加Repeat,实现子组件复用
|
|
200
|
+
|
|
201
|
+
添加任务增删功能后,任务列表项增加,需要高效渲染多个结构相同的子组件,提高界面性能。引入[Repeat](../../学习UI范式渲染控制/Repeat:可复用的循环渲染/arkts-new-rendering-control-repeat.md)组件,优化任务列表渲染。
|
|
202
|
+
|
|
203
|
+
Repeat支持两种场景:懒加载场景和非懒加载场景。
|
|
204
|
+
|
|
205
|
+
* 懒加载场景适用于大量数据的场景,在滚动类容器中按需加载组件,极大节省内存和提升渲染效率。
|
|
206
|
+
* 非懒加载场景适用于数据量较小的场景,一次性渲染所有组件,并在数据变化时仅更新需要变化的部分,避免整体重新渲染。
|
|
207
|
+
|
|
208
|
+
在示例5中,由于任务量较少,使用Repeat非懒加载场景。新建任务数组tasks,并使用Repeat方法迭代数组中的每一项,动态生成并复用TaskItem组件。任务增删时,这种方式能高效复用已有组件,避免重复渲染,提高界面响应速度和性能。这种机制有效地提高了代码的复用性和渲染效率。
|
|
209
|
+
|
|
210
|
+
**示例5**
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
1. // src/main/ets/pages/RepeatPage.ets
|
|
214
|
+
2. @ComponentV2
|
|
215
|
+
3. struct TaskItem {
|
|
216
|
+
4. @Param taskName: string = '';
|
|
217
|
+
5. @Param @Once isFinish: boolean = false;
|
|
218
|
+
6. @Event deleteTask: () => void = () => {};
|
|
219
|
+
|
|
220
|
+
8. build() {
|
|
221
|
+
9. Row() {
|
|
222
|
+
10. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
223
|
+
11. Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
224
|
+
12. .width(28)
|
|
225
|
+
13. .height(28)
|
|
226
|
+
14. Text(this.taskName)
|
|
227
|
+
15. .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
228
|
+
16. Button('Delete')
|
|
229
|
+
17. .onClick(() => {
|
|
230
|
+
18. this.deleteTask();
|
|
231
|
+
19. })
|
|
232
|
+
20. }
|
|
233
|
+
21. .onClick(() => {
|
|
234
|
+
22. this.isFinish = !this.isFinish;
|
|
235
|
+
23. })
|
|
236
|
+
24. }
|
|
237
|
+
25. }
|
|
238
|
+
|
|
239
|
+
27. @Entry
|
|
240
|
+
28. @ComponentV2
|
|
241
|
+
29. struct TodoList {
|
|
242
|
+
30. @Local tasks: string[] = ['task1', 'task2', 'task3'];
|
|
243
|
+
31. @Local newTaskName: string = '';
|
|
244
|
+
|
|
245
|
+
33. build() {
|
|
246
|
+
34. Column() {
|
|
247
|
+
35. Text('To do')
|
|
248
|
+
36. .fontSize(40)
|
|
249
|
+
37. .margin({ bottom: 10 })
|
|
250
|
+
38. Repeat<string>(this.tasks)
|
|
251
|
+
39. .each((obj: RepeatItem<string>) => {
|
|
252
|
+
40. TaskItem({
|
|
253
|
+
41. taskName: obj.item,
|
|
254
|
+
42. isFinish: false,
|
|
255
|
+
43. deleteTask: () => {
|
|
256
|
+
44. this.tasks.splice(this.tasks.indexOf(obj.item), 1);
|
|
257
|
+
45. }
|
|
258
|
+
46. })
|
|
259
|
+
47. })
|
|
260
|
+
48. Row() {
|
|
261
|
+
49. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
262
|
+
50. .onChange((value) => {
|
|
263
|
+
51. this.newTaskName = value;
|
|
264
|
+
52. })
|
|
265
|
+
53. .width('70%')
|
|
266
|
+
54. Button('+')
|
|
267
|
+
55. .onClick(() => {
|
|
268
|
+
56. this.tasks.push(this.newTaskName);
|
|
269
|
+
57. this.newTaskName = '';
|
|
270
|
+
58. })
|
|
271
|
+
59. }
|
|
272
|
+
60. }
|
|
273
|
+
61. }
|
|
274
|
+
62. }
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
[RepeatPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/RepeatPage.ets#L17-L80)
|
|
278
|
+
|
|
279
|
+
### 添加@ObservedV2,@Trace,实现类属性观测变化
|
|
280
|
+
|
|
281
|
+
实现多个功能后,任务列表管理变得复杂。为了有效处理任务数据的变化,特别是在多层嵌套结构中,需要确保属性变化能够被深度观测并自动更新UI。为此,引入了@ObservedV2和@Trace装饰器。与仅能观测对象及其第一层变化的@Local不同,@ObservedV2和@Trace适用于多层嵌套和继承等复杂场景。在@ObservedV2装饰的类中,@Trace装饰的属性变化时,会触发绑定的UI组件刷新。
|
|
282
|
+
|
|
283
|
+
在示例6中,任务(Task)被抽象为一个类,并用@ObservedV2标记该类,用@Trace标记isFinish属性。TodoList组件嵌套了TaskItem,TaskItem又嵌套了Task。在最外层的TodoList中,添加了"全部完成"和"全部未完成"的按钮,每次点击这些按钮都会直接更新最内层Task类的isFinish属性。@ObservedV2和@Trace确保可以观察到对应isFinish UI组件的刷新,从而实现了对嵌套类属性的深度观测。
|
|
284
|
+
|
|
285
|
+
**示例6**
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
1. // src/main/ets/pages/ObservedV2TracePage.ets
|
|
289
|
+
2. @ObservedV2
|
|
290
|
+
3. class Task {
|
|
291
|
+
4. public taskName: string = '';
|
|
292
|
+
5. @Trace public isFinish: boolean = false;
|
|
293
|
+
|
|
294
|
+
7. constructor(taskName: string, isFinish: boolean) {
|
|
295
|
+
8. this.taskName = taskName;
|
|
296
|
+
9. this.isFinish = isFinish;
|
|
297
|
+
10. }
|
|
298
|
+
11. }
|
|
299
|
+
|
|
300
|
+
13. @ComponentV2
|
|
301
|
+
14. struct TaskItem {
|
|
302
|
+
15. @Param task: Task = new Task('', false);
|
|
303
|
+
16. @Event deleteTask: () => void = () => {};
|
|
304
|
+
|
|
305
|
+
18. build() {
|
|
306
|
+
19. Row() {
|
|
307
|
+
20. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
308
|
+
21. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
309
|
+
22. .width(28)
|
|
310
|
+
23. .height(28)
|
|
311
|
+
24. Text(this.task.taskName)
|
|
312
|
+
25. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
313
|
+
26. Button('Delete')
|
|
314
|
+
27. .onClick(() => {
|
|
315
|
+
28. this.deleteTask();
|
|
316
|
+
29. })
|
|
317
|
+
30. }
|
|
318
|
+
31. .onClick(() => {
|
|
319
|
+
32. this.task.isFinish = !this.task.isFinish;
|
|
320
|
+
33. })
|
|
321
|
+
34. }
|
|
322
|
+
35. }
|
|
323
|
+
|
|
324
|
+
37. @Entry
|
|
325
|
+
38. @ComponentV2
|
|
326
|
+
39. struct TodoList {
|
|
327
|
+
40. @Local tasks: Task[] = [
|
|
328
|
+
41. new Task('task1', false),
|
|
329
|
+
42. new Task('task2', false),
|
|
330
|
+
43. new Task('task3', false),
|
|
331
|
+
44. ];
|
|
332
|
+
45. @Local newTaskName: string = '';
|
|
333
|
+
|
|
334
|
+
47. finishAll(ifFinish: boolean) {
|
|
335
|
+
48. for (let task of this.tasks) {
|
|
336
|
+
49. task.isFinish = ifFinish;
|
|
337
|
+
50. }
|
|
338
|
+
51. }
|
|
339
|
+
|
|
340
|
+
53. build() {
|
|
341
|
+
54. Column() {
|
|
342
|
+
55. Text('To do')
|
|
343
|
+
56. .fontSize(40)
|
|
344
|
+
57. .margin({ bottom: 10 })
|
|
345
|
+
58. Repeat<Task>(this.tasks)
|
|
346
|
+
59. .each((obj: RepeatItem<Task>) => {
|
|
347
|
+
60. TaskItem({
|
|
348
|
+
61. task: obj.item,
|
|
349
|
+
62. deleteTask: () => {
|
|
350
|
+
63. this.tasks.splice(this.tasks.indexOf(obj.item), 1);
|
|
351
|
+
64. }
|
|
352
|
+
65. })
|
|
353
|
+
66. })
|
|
354
|
+
67. Row() {
|
|
355
|
+
68. Button('All Completed')
|
|
356
|
+
69. .onClick(() => {
|
|
357
|
+
70. this.finishAll(true);
|
|
358
|
+
71. })
|
|
359
|
+
72. Button('All Not Completed')
|
|
360
|
+
73. .onClick(() => {
|
|
361
|
+
74. this.finishAll(false);
|
|
362
|
+
75. })
|
|
363
|
+
76. }
|
|
364
|
+
77. Row() {
|
|
365
|
+
78. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
366
|
+
79. .onChange((value) => {
|
|
367
|
+
80. this.newTaskName = value;
|
|
368
|
+
81. })
|
|
369
|
+
82. .width('70%')
|
|
370
|
+
83. Button('+')
|
|
371
|
+
84. .onClick(() => {
|
|
372
|
+
85. this.tasks.push(new Task(this.newTaskName, false));
|
|
373
|
+
86. this.newTaskName = '';
|
|
374
|
+
87. })
|
|
375
|
+
88. }
|
|
376
|
+
89. }
|
|
377
|
+
90. }
|
|
378
|
+
91. }
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
[ObservedV2TracePage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/ObservedV2TracePage.ets#L17-L109)
|
|
382
|
+
|
|
383
|
+
### 添加@Monitor,@Computed,实现监听状态变量和计算属性
|
|
384
|
+
|
|
385
|
+
在当前任务列表功能基础上,为了提升体验,可以增加一些额外的功能,如任务状态变化的监听和未完成任务数量的动态计算。为此,引入[@Monitor](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-monitor)和[@Computed](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-new-computed)装饰器。@Monitor用于深度监听状态变量,在属性变化时触发自定义回调方法。@Computed用于装饰getter方法,检测被计算的属性变化。被计算的值变化时,仅计算一次,减少重复计算开销。
|
|
386
|
+
|
|
387
|
+
在示例7中,使用@Monitor装饰器深度监听TaskItem中task的isFinish属性。当任务完成状态变化时,触发onTaskFinished回调,记录任务完成状态的变化。同时,新增对todolist中未完成任务数量的记录。使用@Computed装饰器定义tasksUnfinished,每当任务状态变化时自动重新计算。通过这两个装饰器,实现了状态变量的深度监听和高效的计算属性。
|
|
388
|
+
|
|
389
|
+
**示例7**
|
|
390
|
+
|
|
391
|
+
```
|
|
392
|
+
1. // src/main/ets/pages/MonitorComputedPage.ets
|
|
393
|
+
2. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
394
|
+
|
|
395
|
+
4. @ObservedV2
|
|
396
|
+
5. class Task {
|
|
397
|
+
6. public taskName: string = '';
|
|
398
|
+
7. @Trace public isFinish: boolean = false;
|
|
399
|
+
|
|
400
|
+
9. constructor(taskName: string, isFinish: boolean) {
|
|
401
|
+
10. this.taskName = taskName;
|
|
402
|
+
11. this.isFinish = isFinish;
|
|
403
|
+
12. }
|
|
404
|
+
13. }
|
|
405
|
+
|
|
406
|
+
15. @ComponentV2
|
|
407
|
+
16. struct TaskItem {
|
|
408
|
+
17. @Param task: Task = new Task('', false);
|
|
409
|
+
18. @Event deleteTask: () => void = () => {};
|
|
410
|
+
|
|
411
|
+
20. @Monitor('task.isFinish')
|
|
412
|
+
21. onTaskFinished(mon: IMonitor) {
|
|
413
|
+
22. hilog.info(0x0000, 'testTag', '%{public}s', 'Task ' + this.task.taskName + ' completion status changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
|
|
414
|
+
23. }
|
|
415
|
+
|
|
416
|
+
25. build() {
|
|
417
|
+
26. Row() {
|
|
418
|
+
27. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
419
|
+
28. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
420
|
+
29. .width(28)
|
|
421
|
+
30. .height(28)
|
|
422
|
+
31. Text(this.task.taskName)
|
|
423
|
+
32. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
424
|
+
33. Button('Delete')
|
|
425
|
+
34. .onClick(() => {
|
|
426
|
+
35. this.deleteTask();
|
|
427
|
+
36. })
|
|
428
|
+
37. }
|
|
429
|
+
38. .onClick(() => {
|
|
430
|
+
39. this.task.isFinish = !this.task.isFinish;
|
|
431
|
+
40. })
|
|
432
|
+
41. }
|
|
433
|
+
42. }
|
|
434
|
+
|
|
435
|
+
44. @Entry
|
|
436
|
+
45. @ComponentV2
|
|
437
|
+
46. struct TodoList {
|
|
438
|
+
47. @Local tasks: Task[] = [
|
|
439
|
+
48. new Task('task1', false),
|
|
440
|
+
49. new Task('task2', false),
|
|
441
|
+
50. new Task('task3', false),
|
|
442
|
+
51. ];
|
|
443
|
+
52. @Local newTaskName: string = '';
|
|
444
|
+
|
|
445
|
+
54. finishAll(ifFinish: boolean) {
|
|
446
|
+
55. for (let task of this.tasks) {
|
|
447
|
+
56. task.isFinish = ifFinish;
|
|
448
|
+
57. }
|
|
449
|
+
58. }
|
|
450
|
+
|
|
451
|
+
60. @Computed
|
|
452
|
+
61. get tasksUnfinished(): number {
|
|
453
|
+
62. return this.tasks.filter(task => !task.isFinish).length;
|
|
454
|
+
63. }
|
|
455
|
+
|
|
456
|
+
65. build() {
|
|
457
|
+
66. Column() {
|
|
458
|
+
67. Text('To do')
|
|
459
|
+
68. .fontSize(40)
|
|
460
|
+
69. .margin({ bottom: 10 })
|
|
461
|
+
70. Text('Unfinished task' + `:${this.tasksUnfinished}`)
|
|
462
|
+
71. Repeat<Task>(this.tasks)
|
|
463
|
+
72. .each((obj: RepeatItem<Task>) => {
|
|
464
|
+
73. TaskItem({
|
|
465
|
+
74. task: obj.item,
|
|
466
|
+
75. deleteTask: () => {
|
|
467
|
+
76. this.tasks.splice(this.tasks.indexOf(obj.item), 1);
|
|
468
|
+
77. }
|
|
469
|
+
78. })
|
|
470
|
+
79. })
|
|
471
|
+
80. Row() {
|
|
472
|
+
81. Button('All Completed')
|
|
473
|
+
82. .onClick(() => {
|
|
474
|
+
83. this.finishAll(true);
|
|
475
|
+
84. })
|
|
476
|
+
85. Button('All Not Completed')
|
|
477
|
+
86. .onClick(() => {
|
|
478
|
+
87. this.finishAll(false);
|
|
479
|
+
88. })
|
|
480
|
+
89. }
|
|
481
|
+
90. Row() {
|
|
482
|
+
91. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
483
|
+
92. .onChange((value) => {
|
|
484
|
+
93. this.newTaskName = value;
|
|
485
|
+
94. })
|
|
486
|
+
95. .width('70%')
|
|
487
|
+
96. Button('+')
|
|
488
|
+
97. .onClick(() => {
|
|
489
|
+
98. this.tasks.push(new Task(this.newTaskName, false));
|
|
490
|
+
99. this.newTaskName = '';
|
|
491
|
+
100. })
|
|
492
|
+
101. }
|
|
493
|
+
102. }
|
|
494
|
+
103. }
|
|
495
|
+
104. }
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
[MonitorComputedPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/MonitorComputedPage.ets#L16-L121)
|
|
499
|
+
|
|
500
|
+
### 添加AppStorageV2,实现应用全局UI状态存储
|
|
501
|
+
|
|
502
|
+
随着待办事项功能的增强,应用涉及多个页面或功能模块时,需要在这些页面之间共享全局状态。例如:在待办事项应用中,新增一个设置页面与主界面联动。为实现跨页面的状态共享,引入AppStorageV2,用于在多个UIAbility实例之间存储和共享应用的全局状态。
|
|
503
|
+
|
|
504
|
+
在这个示例中,新增了一个Ability,SettingAbility,用于加载设置页面SettingPage。SettingPage包含了一个Setting类,其中的showCompletedTask属性用于控制是否显示已完成的任务。用户可以通过一个开关切换该选项。两个Ability通过AppStorageV2共享设置数据,键为 "Setting",对应的数据为Setting类。第一次通过connect连接Setting时,如果不存在存储的数据,会创建一个showCompletedTask默认为true的Setting实例。后续用户在设置页面修改设置后,主页面会根据这一设置更新任务列表的显示。通过AppStorageV2,实现了跨Ability、跨页面的数据共享。
|
|
505
|
+
|
|
506
|
+
**示例8**
|
|
507
|
+
|
|
508
|
+
```
|
|
509
|
+
1. // src/main/ets/pages/AppStorageV2Page.ets
|
|
510
|
+
2. import { AppStorageV2 } from '@kit.ArkUI';
|
|
511
|
+
3. import { common, Want } from '@kit.AbilityKit';
|
|
512
|
+
4. import { Setting } from './SettingPage';
|
|
513
|
+
5. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
514
|
+
|
|
515
|
+
7. @ObservedV2
|
|
516
|
+
8. class Task {
|
|
517
|
+
9. public taskName: string = '';
|
|
518
|
+
10. @Trace public isFinish: boolean = false;
|
|
519
|
+
|
|
520
|
+
12. constructor(taskName: string, isFinish: boolean) {
|
|
521
|
+
13. this.taskName = taskName;
|
|
522
|
+
14. this.isFinish = isFinish;
|
|
523
|
+
15. }
|
|
524
|
+
16. }
|
|
525
|
+
|
|
526
|
+
18. @ComponentV2
|
|
527
|
+
19. struct TaskItem {
|
|
528
|
+
20. @Param task: Task = new Task('', false);
|
|
529
|
+
21. @Event deleteTask: () => void = () => {};
|
|
530
|
+
|
|
531
|
+
23. @Monitor('task.isFinish')
|
|
532
|
+
24. onTaskFinished(mon: IMonitor) {
|
|
533
|
+
25. hilog.info(0x0000, 'testTag', '%{public}s', 'Task ' + this.task.taskName + ' completion status changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
|
|
534
|
+
26. }
|
|
535
|
+
|
|
536
|
+
28. build() {
|
|
537
|
+
29. Row() {
|
|
538
|
+
30. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
539
|
+
31. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
540
|
+
32. .width(28)
|
|
541
|
+
33. .height(28)
|
|
542
|
+
34. Text(this.task.taskName)
|
|
543
|
+
35. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
544
|
+
36. Button('Delete')
|
|
545
|
+
37. .onClick(() => {
|
|
546
|
+
38. this.deleteTask();
|
|
547
|
+
39. })
|
|
548
|
+
40. }
|
|
549
|
+
41. .onClick(() => {
|
|
550
|
+
42. this.task.isFinish = !this.task.isFinish;
|
|
551
|
+
43. })
|
|
552
|
+
44. }
|
|
553
|
+
45. }
|
|
554
|
+
|
|
555
|
+
47. @Entry
|
|
556
|
+
48. @ComponentV2
|
|
557
|
+
49. struct TodoList {
|
|
558
|
+
50. @Local tasks: Task[] = [
|
|
559
|
+
51. new Task('task1', false),
|
|
560
|
+
52. new Task('task2', false),
|
|
561
|
+
53. new Task('task3', false),
|
|
562
|
+
54. ];
|
|
563
|
+
55. @Local newTaskName: string = '';
|
|
564
|
+
56. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
565
|
+
57. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
566
|
+
|
|
567
|
+
59. finishAll(ifFinish: boolean) {
|
|
568
|
+
60. for (let task of this.tasks) {
|
|
569
|
+
61. task.isFinish = ifFinish;
|
|
570
|
+
62. }
|
|
571
|
+
63. }
|
|
572
|
+
|
|
573
|
+
65. @Computed
|
|
574
|
+
66. get tasksUnfinished(): number {
|
|
575
|
+
67. return this.tasks.filter(task => !task.isFinish).length;
|
|
576
|
+
68. }
|
|
577
|
+
|
|
578
|
+
70. build() {
|
|
579
|
+
71. Column() {
|
|
580
|
+
72. Text('To do')
|
|
581
|
+
73. .fontSize(40)
|
|
582
|
+
74. .margin({ bottom: 10 })
|
|
583
|
+
75. Text('Unfinished task' + `:${this.tasksUnfinished}`)
|
|
584
|
+
76. Repeat<Task>(this.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
|
|
585
|
+
77. .each((obj: RepeatItem<Task>) => {
|
|
586
|
+
78. TaskItem({
|
|
587
|
+
79. task: obj.item,
|
|
588
|
+
80. deleteTask: () => {
|
|
589
|
+
81. this.tasks.splice(this.tasks.indexOf(obj.item), 1);
|
|
590
|
+
82. }
|
|
591
|
+
83. })
|
|
592
|
+
84. })
|
|
593
|
+
85. Row() {
|
|
594
|
+
86. Button('All Completed')
|
|
595
|
+
87. .onClick(() => {
|
|
596
|
+
88. this.finishAll(true);
|
|
597
|
+
89. })
|
|
598
|
+
90. Button('All Not Completed')
|
|
599
|
+
91. .onClick(() => {
|
|
600
|
+
92. this.finishAll(false);
|
|
601
|
+
93. })
|
|
602
|
+
94. Button('Setting')
|
|
603
|
+
95. .onClick(() => {
|
|
604
|
+
96. let wantInfo: Want = {
|
|
605
|
+
97. deviceId: '', // deviceId为空表示本设备。
|
|
606
|
+
98. bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName。
|
|
607
|
+
99. abilityName: 'SettingAbility',
|
|
608
|
+
100. };
|
|
609
|
+
101. this.context.startAbility(wantInfo);
|
|
610
|
+
102. })
|
|
611
|
+
103. }
|
|
612
|
+
104. Row() {
|
|
613
|
+
105. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
614
|
+
106. .onChange((value) => {
|
|
615
|
+
107. this.newTaskName = value;
|
|
616
|
+
108. })
|
|
617
|
+
109. .width('70%')
|
|
618
|
+
110. Button('+')
|
|
619
|
+
111. .onClick(() => {
|
|
620
|
+
112. this.tasks.push(new Task(this.newTaskName, false));
|
|
621
|
+
113. this.newTaskName = '';
|
|
622
|
+
114. })
|
|
623
|
+
115. }
|
|
624
|
+
116. }
|
|
625
|
+
117. }
|
|
626
|
+
118. }
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
[AppStorageV2Page.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/AppStorageV2Page.ets#L17-L136)
|
|
630
|
+
|
|
631
|
+
```
|
|
632
|
+
1. // src/main/ets/pages/SettingPage.ets
|
|
633
|
+
2. import { AppStorageV2 } from '@kit.ArkUI';
|
|
634
|
+
3. import { common } from '@kit.AbilityKit';
|
|
635
|
+
|
|
636
|
+
5. @ObservedV2
|
|
637
|
+
6. export class Setting {
|
|
638
|
+
7. @Trace public showCompletedTask: boolean = true;
|
|
639
|
+
8. }
|
|
640
|
+
|
|
641
|
+
10. @Entry
|
|
642
|
+
11. @ComponentV2
|
|
643
|
+
12. struct SettingPage {
|
|
644
|
+
13. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
645
|
+
14. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
646
|
+
|
|
647
|
+
16. build() {
|
|
648
|
+
17. Column() {
|
|
649
|
+
18. Text('Setting')
|
|
650
|
+
19. .fontSize(40)
|
|
651
|
+
20. .margin({ bottom: 10 })
|
|
652
|
+
21. Row() {
|
|
653
|
+
22. Text('Show completed tasks')
|
|
654
|
+
23. Toggle({ type: ToggleType.Switch, isOn: this.setting.showCompletedTask })
|
|
655
|
+
24. .onChange((isOn) => {
|
|
656
|
+
25. this.setting.showCompletedTask = isOn;
|
|
657
|
+
26. })
|
|
658
|
+
27. }
|
|
659
|
+
28. Button('Back to To do')
|
|
660
|
+
29. .onClick(() => {
|
|
661
|
+
30. this.context.terminateSelf();
|
|
662
|
+
31. })
|
|
663
|
+
32. .margin({ top: 10 })
|
|
664
|
+
33. }
|
|
665
|
+
34. .alignItems(HorizontalAlign.Start)
|
|
666
|
+
35. }
|
|
667
|
+
36. }
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
[SettingPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/SettingPage.ets#L17-L54)
|
|
671
|
+
|
|
672
|
+
### 添加PersistenceV2,实现持久化UI状态存储
|
|
673
|
+
|
|
674
|
+
为了确保用户重新打开应用时能看到之前的任务状态,建议使用PersistenceV2进行数据持久化存储。PersistenceV2可将数据保存在设备磁盘上,与AppStorageV2的运行时内存相比,它能确保数据在应用关闭后再次启动时保持不变。
|
|
675
|
+
|
|
676
|
+
在示例9中,创建了一个TaskList类,用于通过PersistenceV2持久化存储所有任务信息,键为"TaskList",数据对应TaskList类。第一次通过connect连接TaskList时,如果没有数据,会创建一个默认tasks数组为空的新TaskList实例。在aboutToAppear生命周期函数中,连接到PersistenceV2的TaskList,若无存储任务数据,会从本地文件defaultTasks.json中加载任务并存储到PersistenceV2中。此后,每个任务的完成状态都会同步到PersistenceV2中。这样,即使应用关闭后再次打开,所有任务数据依旧保持不变,实现了持久化的应用状态存储功能。
|
|
677
|
+
|
|
678
|
+
**示例9**
|
|
679
|
+
|
|
680
|
+
```
|
|
681
|
+
1. // src/main/ets/pages/PersistenceV2Page.ets
|
|
682
|
+
2. import { AppStorageV2, PersistenceV2, Type } from '@kit.ArkUI';
|
|
683
|
+
3. import { common, Want } from '@kit.AbilityKit';
|
|
684
|
+
4. import { Setting } from './SettingPage';
|
|
685
|
+
5. import { util } from '@kit.ArkTS';
|
|
686
|
+
6. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
687
|
+
|
|
688
|
+
8. @ObservedV2
|
|
689
|
+
9. class Task {
|
|
690
|
+
10. // 未实现构造函数,因为@Type当前不支持带参数的构造函数。
|
|
691
|
+
11. @Trace public taskName: string = 'Todo';
|
|
692
|
+
12. @Trace public isFinish: boolean = false;
|
|
693
|
+
13. }
|
|
694
|
+
|
|
695
|
+
15. @ObservedV2
|
|
696
|
+
16. class TaskList {
|
|
697
|
+
17. // 对于复杂对象需要@Type修饰,确保序列化成功。
|
|
698
|
+
18. @Type(Task)
|
|
699
|
+
19. @Trace public tasks: Task[] = [];
|
|
700
|
+
|
|
701
|
+
21. constructor(tasks: Task[]) {
|
|
702
|
+
22. this.tasks = tasks;
|
|
703
|
+
23. }
|
|
704
|
+
|
|
705
|
+
25. async loadTasks(context: common.UIAbilityContext) {
|
|
706
|
+
26. let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
|
|
707
|
+
27. let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM: true };
|
|
708
|
+
28. let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions);
|
|
709
|
+
29. let result = textDecoder.decodeToString(getJson);
|
|
710
|
+
30. this.tasks = JSON.parse(result).map((task: Task) => {
|
|
711
|
+
31. let newTask = new Task();
|
|
712
|
+
32. newTask.taskName = task.taskName;
|
|
713
|
+
33. newTask.isFinish = task.isFinish;
|
|
714
|
+
34. return newTask;
|
|
715
|
+
35. });
|
|
716
|
+
36. }
|
|
717
|
+
37. }
|
|
718
|
+
|
|
719
|
+
39. @ComponentV2
|
|
720
|
+
40. struct TaskItem {
|
|
721
|
+
41. @Param task: Task = new Task();
|
|
722
|
+
42. @Event deleteTask: () => void = () => {};
|
|
723
|
+
|
|
724
|
+
44. @Monitor('task.isFinish')
|
|
725
|
+
45. onTaskFinished(mon: IMonitor) {
|
|
726
|
+
46. hilog.info(0x0000, 'testTag', '%{public}s', 'Task ' + this.task.taskName + ' completion status changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
|
|
727
|
+
47. }
|
|
728
|
+
|
|
729
|
+
49. build() {
|
|
730
|
+
50. Row() {
|
|
731
|
+
51. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
732
|
+
52. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
733
|
+
53. .width(28)
|
|
734
|
+
54. .height(28)
|
|
735
|
+
55. Text(this.task.taskName)
|
|
736
|
+
56. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
737
|
+
57. Button('Delete')
|
|
738
|
+
58. .onClick(() => {
|
|
739
|
+
59. this.deleteTask();
|
|
740
|
+
60. })
|
|
741
|
+
61. }
|
|
742
|
+
62. .onClick(() => {
|
|
743
|
+
63. this.task.isFinish = !this.task.isFinish;
|
|
744
|
+
64. })
|
|
745
|
+
65. }
|
|
746
|
+
66. }
|
|
747
|
+
|
|
748
|
+
68. @Entry
|
|
749
|
+
69. @ComponentV2
|
|
750
|
+
70. struct TodoList {
|
|
751
|
+
71. @Local taskList: TaskList = new TaskList([]);
|
|
752
|
+
72. @Local newTaskName: string = '';
|
|
753
|
+
73. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
754
|
+
74. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
755
|
+
|
|
756
|
+
76. async aboutToAppear() {
|
|
757
|
+
77. this.taskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
|
|
758
|
+
78. if (this.taskList.tasks.length === 0) {
|
|
759
|
+
79. await this.taskList.loadTasks(this.context);
|
|
760
|
+
80. }
|
|
761
|
+
81. }
|
|
762
|
+
|
|
763
|
+
83. finishAll(ifFinish: boolean) {
|
|
764
|
+
84. for (let task of this.taskList.tasks) {
|
|
765
|
+
85. task.isFinish = ifFinish;
|
|
766
|
+
86. }
|
|
767
|
+
87. }
|
|
768
|
+
|
|
769
|
+
89. @Computed
|
|
770
|
+
90. get tasksUnfinished(): number {
|
|
771
|
+
91. return this.taskList.tasks.filter(task => !task.isFinish).length;
|
|
772
|
+
92. }
|
|
773
|
+
|
|
774
|
+
94. build() {
|
|
775
|
+
95. Column() {
|
|
776
|
+
96. Text('To do')
|
|
777
|
+
97. .fontSize(40)
|
|
778
|
+
98. .margin({ bottom: 10 })
|
|
779
|
+
99. Text('Unfinished task' + `:${this.tasksUnfinished}`)
|
|
780
|
+
100. Repeat<Task>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
|
|
781
|
+
101. .each((obj: RepeatItem<Task>) => {
|
|
782
|
+
102. TaskItem({
|
|
783
|
+
103. task: obj.item,
|
|
784
|
+
104. deleteTask: () => {
|
|
785
|
+
105. this.taskList.tasks.splice(this.taskList.tasks.indexOf(obj.item), 1);
|
|
786
|
+
106. }
|
|
787
|
+
107. })
|
|
788
|
+
108. })
|
|
789
|
+
109. Row() {
|
|
790
|
+
110. Button('All Completed')
|
|
791
|
+
111. .onClick(() => {
|
|
792
|
+
112. this.finishAll(true);
|
|
793
|
+
113. })
|
|
794
|
+
114. Button('All Not Completed')
|
|
795
|
+
115. .onClick(() => {
|
|
796
|
+
116. this.finishAll(false);
|
|
797
|
+
117. })
|
|
798
|
+
118. Button('Setting')
|
|
799
|
+
119. .onClick(() => {
|
|
800
|
+
120. let wantInfo: Want = {
|
|
801
|
+
121. deviceId: '', // deviceId为空表示本设备。
|
|
802
|
+
122. bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName。
|
|
803
|
+
123. abilityName: 'SettingAbility',
|
|
804
|
+
124. };
|
|
805
|
+
125. this.context.startAbility(wantInfo);
|
|
806
|
+
126. })
|
|
807
|
+
127. }
|
|
808
|
+
128. Row() {
|
|
809
|
+
129. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
810
|
+
130. .onChange((value) => {
|
|
811
|
+
131. this.newTaskName = value;
|
|
812
|
+
132. })
|
|
813
|
+
133. .width('70%')
|
|
814
|
+
134. Button('+')
|
|
815
|
+
135. .onClick(() => {
|
|
816
|
+
136. let newTask = new Task();
|
|
817
|
+
137. newTask.taskName = this.newTaskName;
|
|
818
|
+
138. this.taskList.tasks.push(newTask);
|
|
819
|
+
139. this.newTaskName = '';
|
|
820
|
+
140. })
|
|
821
|
+
141. }
|
|
822
|
+
142. }
|
|
823
|
+
143. }
|
|
824
|
+
144. }
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
[PersistenceV2Page.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/PersistenceV2Page.ets#L17-L162)
|
|
828
|
+
|
|
829
|
+
JSON文件存放在src/main/resources/rawfile/defaultTasks.json路径下。
|
|
830
|
+
|
|
831
|
+
```
|
|
832
|
+
1. [
|
|
833
|
+
2. {"taskName": "学习ArkTS开发", "isFinish": false},
|
|
834
|
+
3. {"taskName": "健身", "isFinish": false},
|
|
835
|
+
4. {"taskName": "买水果", "isFinish": true},
|
|
836
|
+
5. {"taskName": "取快递", "isFinish": true},
|
|
837
|
+
6. {"taskName": "刷题", "isFinish": true}
|
|
838
|
+
7. ]
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
### 添加@Builder,实现自定义构建函数
|
|
842
|
+
|
|
843
|
+
随着应用功能逐步扩展,代码中的某些UI元素开始重复,不仅增加了代码量,也让维护变得复杂。为解决此问题,建议使用@Builder装饰器,将重复的UI组件抽象为独立的构建方法,便于复用和代码模块化。
|
|
844
|
+
|
|
845
|
+
在示例10中,通过使用@Builder定义的ActionButton方法,实现了按钮文字、样式和点击事件的统一管理,提高了代码的简洁性和可维护性。同时优化了界面组件的布局和样式,包括间距、颜色和尺寸等视觉元素,最终呈现出一个功能完善且界面简洁美观的待办事项应用。
|
|
846
|
+
|
|
847
|
+
**示例10**
|
|
848
|
+
|
|
849
|
+
```
|
|
850
|
+
1. // src/main/ets/pages/BuilderPage.ets
|
|
851
|
+
2. import { AppStorageV2, PersistenceV2, Type } from '@kit.ArkUI';
|
|
852
|
+
3. import { common, Want } from '@kit.AbilityKit';
|
|
853
|
+
4. import { Setting } from './SettingPage';
|
|
854
|
+
5. import { util } from '@kit.ArkTS';
|
|
855
|
+
6. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
856
|
+
|
|
857
|
+
8. @ObservedV2
|
|
858
|
+
9. class Task {
|
|
859
|
+
10. // 未实现构造函数,因为@Type当前不支持带参数的构造函数。
|
|
860
|
+
11. @Trace public taskName: string = 'Todo';
|
|
861
|
+
12. @Trace public isFinish: boolean = false;
|
|
862
|
+
13. }
|
|
863
|
+
|
|
864
|
+
15. @Builder
|
|
865
|
+
16. function actionButton(text: string | Resource, onClick: () => void) {
|
|
866
|
+
17. Button(text, { buttonStyle: ButtonStyleMode.NORMAL })
|
|
867
|
+
18. .onClick(onClick)
|
|
868
|
+
19. .margin({
|
|
869
|
+
20. left: 10,
|
|
870
|
+
21. right: 10,
|
|
871
|
+
22. top: 5,
|
|
872
|
+
23. bottom: 5
|
|
873
|
+
24. })
|
|
874
|
+
25. }
|
|
875
|
+
|
|
876
|
+
27. @ObservedV2
|
|
877
|
+
28. class TaskList {
|
|
878
|
+
29. // 对于复杂对象需要@Type修饰,确保序列化成功。
|
|
879
|
+
30. @Type(Task)
|
|
880
|
+
31. @Trace public tasks: Task[] = [];
|
|
881
|
+
|
|
882
|
+
33. constructor(tasks: Task[]) {
|
|
883
|
+
34. this.tasks = tasks;
|
|
884
|
+
35. }
|
|
885
|
+
|
|
886
|
+
37. async loadTasks(context: common.UIAbilityContext) {
|
|
887
|
+
38. let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
|
|
888
|
+
39. let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM: true };
|
|
889
|
+
40. let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions);
|
|
890
|
+
41. let result = textDecoder.decodeToString(getJson);
|
|
891
|
+
42. this.tasks = JSON.parse(result).map((task: Task) => {
|
|
892
|
+
43. let newTask = new Task();
|
|
893
|
+
44. newTask.taskName = task.taskName;
|
|
894
|
+
45. newTask.isFinish = task.isFinish;
|
|
895
|
+
46. return newTask;
|
|
896
|
+
47. });
|
|
897
|
+
48. }
|
|
898
|
+
49. }
|
|
899
|
+
|
|
900
|
+
51. @ComponentV2
|
|
901
|
+
52. struct TaskItem {
|
|
902
|
+
53. @Param task: Task = new Task();
|
|
903
|
+
54. @Event deleteTask: () => void = () => {};
|
|
904
|
+
|
|
905
|
+
56. @Monitor('task.isFinish')
|
|
906
|
+
57. onTaskFinished(mon: IMonitor) {
|
|
907
|
+
58. hilog.info(0x0000, 'testTag', '%{public}s', 'Task ' + this.task.taskName + ' completion status changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
|
|
908
|
+
59. }
|
|
909
|
+
|
|
910
|
+
61. build() {
|
|
911
|
+
62. Row() {
|
|
912
|
+
63. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
913
|
+
64. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
914
|
+
65. .width(28)
|
|
915
|
+
66. .height(28)
|
|
916
|
+
67. .margin({ left: 15, right: 10 })
|
|
917
|
+
68. Text(this.task.taskName)
|
|
918
|
+
69. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
919
|
+
70. .fontSize(18)
|
|
920
|
+
71. actionButton('Delete', () => {
|
|
921
|
+
72. this.deleteTask();
|
|
922
|
+
73. })
|
|
923
|
+
74. }
|
|
924
|
+
75. .height('7%')
|
|
925
|
+
76. .width('90%')
|
|
926
|
+
77. .backgroundColor('#90f1f3f5')
|
|
927
|
+
78. .borderRadius(25)
|
|
928
|
+
79. .onClick(() => {
|
|
929
|
+
80. this.task.isFinish = !this.task.isFinish;
|
|
930
|
+
81. })
|
|
931
|
+
82. }
|
|
932
|
+
83. }
|
|
933
|
+
|
|
934
|
+
85. @Entry
|
|
935
|
+
86. @ComponentV2
|
|
936
|
+
87. struct TodoList {
|
|
937
|
+
88. @Local taskList: TaskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
|
|
938
|
+
89. @Local newTaskName: string = '';
|
|
939
|
+
90. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
940
|
+
91. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
941
|
+
|
|
942
|
+
93. async aboutToAppear() {
|
|
943
|
+
94. if (this.taskList.tasks.length === 0) {
|
|
944
|
+
95. await this.taskList.loadTasks(this.context);
|
|
945
|
+
96. }
|
|
946
|
+
97. }
|
|
947
|
+
|
|
948
|
+
99. finishAll(ifFinish: boolean) {
|
|
949
|
+
100. for (let task of this.taskList.tasks) {
|
|
950
|
+
101. task.isFinish = ifFinish;
|
|
951
|
+
102. }
|
|
952
|
+
103. }
|
|
953
|
+
|
|
954
|
+
105. @Computed
|
|
955
|
+
106. get tasksUnfinished(): number {
|
|
956
|
+
107. return this.taskList.tasks.filter(task => !task.isFinish).length;
|
|
957
|
+
108. }
|
|
958
|
+
|
|
959
|
+
110. build() {
|
|
960
|
+
111. Column() {
|
|
961
|
+
112. Text('To do')
|
|
962
|
+
113. .fontSize(40)
|
|
963
|
+
114. .margin(10)
|
|
964
|
+
115. Text('Unfinished task' + `:${this.tasksUnfinished}`)
|
|
965
|
+
116. .margin({ left: 10, bottom: 10 })
|
|
966
|
+
117. Repeat<Task>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
|
|
967
|
+
118. .each((obj: RepeatItem<Task>) => {
|
|
968
|
+
119. TaskItem({
|
|
969
|
+
120. task: obj.item,
|
|
970
|
+
121. deleteTask: () => {
|
|
971
|
+
122. this.taskList.tasks.splice(this.taskList.tasks.indexOf(obj.item), 1);
|
|
972
|
+
123. }
|
|
973
|
+
124. })
|
|
974
|
+
125. .margin(5)
|
|
975
|
+
126. })
|
|
976
|
+
127. Row() {
|
|
977
|
+
128. actionButton('All Completed', (): void => this.finishAll(true))
|
|
978
|
+
129. actionButton('All Not Completed', (): void => this.finishAll(false))
|
|
979
|
+
130. actionButton('Setting', (): void => {
|
|
980
|
+
131. let wantInfo: Want = {
|
|
981
|
+
132. deviceId: '', // deviceId为空表示本设备。
|
|
982
|
+
133. bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName。
|
|
983
|
+
134. abilityName: 'SettingAbility',
|
|
984
|
+
135. };
|
|
985
|
+
136. this.context.startAbility(wantInfo);
|
|
986
|
+
137. })
|
|
987
|
+
138. }
|
|
988
|
+
139. .margin({ top: 10, bottom: 5 })
|
|
989
|
+
140. Row() {
|
|
990
|
+
141. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
991
|
+
142. .onChange((value) => {
|
|
992
|
+
143. this.newTaskName = value;
|
|
993
|
+
144. })
|
|
994
|
+
145. .width('70%')
|
|
995
|
+
146. actionButton('+', (): void => {
|
|
996
|
+
147. let newTask = new Task();
|
|
997
|
+
148. newTask.taskName = this.newTaskName;
|
|
998
|
+
149. this.taskList.tasks.push(newTask);
|
|
999
|
+
150. this.newTaskName = '';
|
|
1000
|
+
151. })
|
|
1001
|
+
152. }
|
|
1002
|
+
153. }
|
|
1003
|
+
154. .height('100%')
|
|
1004
|
+
155. .width('100%')
|
|
1005
|
+
156. .alignItems(HorizontalAlign.Start)
|
|
1006
|
+
157. .margin({ left: 15 })
|
|
1007
|
+
158. }
|
|
1008
|
+
159. }
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
[BuilderPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/BuilderPage.ets#L17-L177)
|
|
1012
|
+
|
|
1013
|
+
### 效果图展示
|
|
1014
|
+
|
|
1015
|
+

|
|
1016
|
+
|
|
1017
|
+
## 重构代码以符合MVVM架构
|
|
1018
|
+
|
|
1019
|
+
前面的例子通过使用一系列的状态管理装饰器,实现了todolist中的数据同步与UI更新。然而,随着应用功能的复杂化,代码的结构变得难以维护,Model、View和ViewModel的职责没有完全分离,存在耦合。为了更好地组织代码和提升可维护性,使用MVVM模式重构代码,进一步将数据层(Model)、逻辑层(ViewModel)和展示层(View)分离。
|
|
1020
|
+
|
|
1021
|
+
### 重构后的代码结构
|
|
1022
|
+
|
|
1023
|
+
```
|
|
1024
|
+
1. /src
|
|
1025
|
+
2. ├── /main
|
|
1026
|
+
3. │ ├── /ets
|
|
1027
|
+
4. │ │ ├── /entryability
|
|
1028
|
+
5. │ │ ├── /model
|
|
1029
|
+
6. │ │ │ ├── TaskListModel.ets
|
|
1030
|
+
7. │ │ │ └── TaskModel.ets
|
|
1031
|
+
8. │ │ ├── /pages
|
|
1032
|
+
9. │ │ │ ├── SettingPage.ets
|
|
1033
|
+
10. │ │ │ └── TodoListPage.ets
|
|
1034
|
+
11. │ │ ├── /settingability
|
|
1035
|
+
12. │ │ ├── /view
|
|
1036
|
+
13. │ │ │ ├── BottomView.ets
|
|
1037
|
+
14. │ │ │ ├── ListView.ets
|
|
1038
|
+
15. │ │ │ └── TitleView.ets
|
|
1039
|
+
16. │ │ ├── /viewmodel
|
|
1040
|
+
17. │ │ │ ├── TaskListViewModel.ets
|
|
1041
|
+
18. │ │ │ └── TaskViewModel.ets
|
|
1042
|
+
19. │ └── /resources
|
|
1043
|
+
20. │ ├── ...
|
|
1044
|
+
21. ├─── ...
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
### Model层
|
|
1048
|
+
|
|
1049
|
+
Model层负责管理应用的数据及其业务逻辑,通常与后端或数据存储进行交互。在todolist应用中,Model层的主要职责是存储任务数据、加载任务列表,并提供数据操作的接口,而不直接涉及UI展示。
|
|
1050
|
+
|
|
1051
|
+
* TaskModel:单个任务的基本数据结构,包含任务名称和完成状态。
|
|
1052
|
+
|
|
1053
|
+
```
|
|
1054
|
+
1. export default class TaskModel {
|
|
1055
|
+
2. public taskName: string = 'Todo';
|
|
1056
|
+
3. public isFinish: boolean = false;
|
|
1057
|
+
4. }
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
[TaskModel.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/model/TaskModel.ets#L18-L23)
|
|
1061
|
+
* TaskListModel:任务的集合,提供从本地加载任务数据的功能。
|
|
1062
|
+
|
|
1063
|
+
```
|
|
1064
|
+
1. import { common } from '@kit.AbilityKit';
|
|
1065
|
+
2. import { util } from '@kit.ArkTS';
|
|
1066
|
+
3. import TaskModel from './TaskModel';
|
|
1067
|
+
4. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
1068
|
+
|
|
1069
|
+
6. const DOMAIN = 0x0000;
|
|
1070
|
+
|
|
1071
|
+
8. // Model层:TaskListModel负责加载任务列表
|
|
1072
|
+
9. export default class TaskListModel {
|
|
1073
|
+
10. public tasks: TaskModel[] = [];
|
|
1074
|
+
|
|
1075
|
+
12. constructor(tasks: TaskModel[]) {
|
|
1076
|
+
13. this.tasks = tasks;
|
|
1077
|
+
14. }
|
|
1078
|
+
|
|
1079
|
+
16. async loadTasks(context: common.UIAbilityContext) {
|
|
1080
|
+
17. try {
|
|
1081
|
+
18. let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
|
|
1082
|
+
19. let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM: true };
|
|
1083
|
+
20. let textDecoder = util.TextDecoder.create('utf-8', textDecoderOptions);
|
|
1084
|
+
21. let result = textDecoder.decodeToString(getJson);
|
|
1085
|
+
22. this.tasks = JSON.parse(result).map((task: TaskModel) => {
|
|
1086
|
+
23. let newTask = new TaskModel();
|
|
1087
|
+
24. newTask.taskName = task.taskName;
|
|
1088
|
+
25. newTask.isFinish = task.isFinish;
|
|
1089
|
+
26. return newTask;
|
|
1090
|
+
27. });
|
|
1091
|
+
28. } catch (e) {
|
|
1092
|
+
29. hilog.error(DOMAIN, 'testTag', 'Failed to getRawFileContent', JSON.stringify(e) ?? '');
|
|
1093
|
+
30. }
|
|
1094
|
+
31. }
|
|
1095
|
+
32. }
|
|
1096
|
+
```
|
|
1097
|
+
|
|
1098
|
+
[TaskListModel.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/model/TaskListModel.ets#L18-L50)
|
|
1099
|
+
|
|
1100
|
+
### ViewModel层
|
|
1101
|
+
|
|
1102
|
+
ViewModel层管理UI状态和业务逻辑,连接Model和View。通过监控Model数据变化,处理应用逻辑,将数据同步到View层,从而实现UI的自动更新。使用ViewModel实现数据与视图解耦,提高代码可读性和可维护性。
|
|
1103
|
+
|
|
1104
|
+
* TaskViewModel:封装单个任务的数据和状态变更逻辑,通过状态装饰器监控数据的变化。
|
|
1105
|
+
|
|
1106
|
+
```
|
|
1107
|
+
1. // src/main/ets/viewmodel/TaskViewModel.ets
|
|
1108
|
+
2. import TaskModel from '../model/TaskModel';
|
|
1109
|
+
|
|
1110
|
+
4. @ObservedV2
|
|
1111
|
+
5. export default class TaskViewModel {
|
|
1112
|
+
6. @Trace public taskName: string = 'Todo';
|
|
1113
|
+
7. @Trace public isFinish: boolean = false;
|
|
1114
|
+
|
|
1115
|
+
9. updateTask(task: TaskModel) {
|
|
1116
|
+
10. this.taskName = task.taskName;
|
|
1117
|
+
11. this.isFinish = task.isFinish;
|
|
1118
|
+
12. }
|
|
1119
|
+
|
|
1120
|
+
14. updateIsFinish(): void {
|
|
1121
|
+
15. this.isFinish = !this.isFinish;
|
|
1122
|
+
16. }
|
|
1123
|
+
17. }
|
|
1124
|
+
```
|
|
1125
|
+
|
|
1126
|
+
[TaskViewModel.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/viewmodel/TaskViewModel.ets#L17-L35)
|
|
1127
|
+
* TaskListViewModel:封装了任务列表以及管理功能,包括加载任务、批量更新任务状态,以及添加和删除任务。
|
|
1128
|
+
|
|
1129
|
+
```
|
|
1130
|
+
1. // src/main/ets/viewmodel/TaskListViewModel.ets
|
|
1131
|
+
2. import { common } from '@kit.AbilityKit';
|
|
1132
|
+
3. import { Type } from '@kit.ArkUI';
|
|
1133
|
+
4. import TaskListModel from '../model/TaskListModel';
|
|
1134
|
+
5. import TaskViewModel from './TaskViewModel';
|
|
1135
|
+
|
|
1136
|
+
7. @ObservedV2
|
|
1137
|
+
8. export default class TaskListViewModel {
|
|
1138
|
+
9. @Type(TaskViewModel)
|
|
1139
|
+
10. @Trace public tasks: TaskViewModel[] = [];
|
|
1140
|
+
|
|
1141
|
+
12. async loadTasks(context: common.UIAbilityContext) {
|
|
1142
|
+
13. let taskList = new TaskListModel([]);
|
|
1143
|
+
14. await taskList.loadTasks(context);
|
|
1144
|
+
15. for (let task of taskList.tasks) {
|
|
1145
|
+
16. let taskViewModel = new TaskViewModel();
|
|
1146
|
+
17. taskViewModel.updateTask(task);
|
|
1147
|
+
18. this.tasks.push(taskViewModel);
|
|
1148
|
+
19. }
|
|
1149
|
+
20. }
|
|
1150
|
+
|
|
1151
|
+
22. finishAll(ifFinish: boolean): void {
|
|
1152
|
+
23. for (let task of this.tasks) {
|
|
1153
|
+
24. task.isFinish = ifFinish;
|
|
1154
|
+
25. }
|
|
1155
|
+
26. }
|
|
1156
|
+
|
|
1157
|
+
28. addTask(newTask: TaskViewModel): void {
|
|
1158
|
+
29. this.tasks.push(newTask);
|
|
1159
|
+
30. }
|
|
1160
|
+
|
|
1161
|
+
32. removeTask(removedTask: TaskViewModel): void {
|
|
1162
|
+
33. this.tasks.splice(this.tasks.indexOf(removedTask), 1);
|
|
1163
|
+
34. }
|
|
1164
|
+
35. }
|
|
1165
|
+
```
|
|
1166
|
+
|
|
1167
|
+
[TaskListViewModel.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/viewmodel/TaskListViewModel.ets#L17-L53)
|
|
1168
|
+
|
|
1169
|
+
### View层
|
|
1170
|
+
|
|
1171
|
+
View层负责应用程序的UI展示和与用户的交互。它只关注如何渲染用户界面和展示数据,不包含业务逻辑。所有的数据状态和逻辑都来自ViewModel层,View层通过接收ViewModel传递的状态数据进行渲染,确保视图和数据分离。
|
|
1172
|
+
|
|
1173
|
+
* TitleView:负责展示应用的标题和未完成任务的统计信息。
|
|
1174
|
+
|
|
1175
|
+
```
|
|
1176
|
+
1. // src/main/ets/view/TitleView.ets
|
|
1177
|
+
2. @ComponentV2
|
|
1178
|
+
3. export default struct TitleView {
|
|
1179
|
+
4. @Param tasksUnfinished: number = 0;
|
|
1180
|
+
|
|
1181
|
+
6. build() {
|
|
1182
|
+
7. Column() {
|
|
1183
|
+
8. Text('To do')
|
|
1184
|
+
9. .fontSize(40)
|
|
1185
|
+
10. .margin(10)
|
|
1186
|
+
11. Text(`All Not Completed:${this.tasksUnfinished}`)
|
|
1187
|
+
12. .margin({ left: 10, bottom: 10 })
|
|
1188
|
+
13. }
|
|
1189
|
+
14. }
|
|
1190
|
+
15. }
|
|
1191
|
+
```
|
|
1192
|
+
|
|
1193
|
+
[TitleView.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/view/TitleView.ets#L17-L33)
|
|
1194
|
+
* ListView:负责展示任务列表,并根据Setting中的设置筛选是否显示已完成的任务。它依赖于TaskListViewModel来获取任务数据,并通过TaskItem组件进行渲染,包括任务的名称、完成状态以及删除按钮。通过TaskViewModel和TaskListViewModel实现用户的交互,如切换任务完成状态和删除任务。
|
|
1195
|
+
|
|
1196
|
+
```
|
|
1197
|
+
1. // src/main/ets/view/ListView.ets
|
|
1198
|
+
2. import TaskViewModel from '../viewmodel/TaskViewModel';
|
|
1199
|
+
3. import TaskListViewModel from '../viewmodel/TaskListViewModel';
|
|
1200
|
+
4. import { Setting } from '../pages/SettingPage';
|
|
1201
|
+
5. import { ActionButton } from './BottomView';
|
|
1202
|
+
6. import { hilog } from '@kit.PerformanceAnalysisKit';
|
|
1203
|
+
|
|
1204
|
+
8. @ComponentV2
|
|
1205
|
+
9. struct TaskItem {
|
|
1206
|
+
10. @Param task: TaskViewModel = new TaskViewModel();
|
|
1207
|
+
11. @Event deleteTask: () => void = () => {};
|
|
1208
|
+
|
|
1209
|
+
13. @Monitor('task.isFinish')
|
|
1210
|
+
14. onTaskFinished(mon: IMonitor) {
|
|
1211
|
+
15. hilog.info(0x0000, 'testTag', '%{public}s', 'Task ' + this.task.taskName + ' completion status changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
|
|
1212
|
+
16. }
|
|
1213
|
+
|
|
1214
|
+
18. build() {
|
|
1215
|
+
19. Row() {
|
|
1216
|
+
20. // 请开发者自行在src/main/resources/base/media路径下添加finished.png和unfinished.png两张图片,否则运行时会因资源缺失而报错。
|
|
1217
|
+
21. Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
|
|
1218
|
+
22. .width(28)
|
|
1219
|
+
23. .height(28)
|
|
1220
|
+
24. .margin({ left: 15, right: 10 })
|
|
1221
|
+
25. Text(this.task.taskName)
|
|
1222
|
+
26. .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
|
|
1223
|
+
27. .fontSize(18)
|
|
1224
|
+
28. ActionButton('Delete', () => this.deleteTask());
|
|
1225
|
+
29. }
|
|
1226
|
+
30. .height('7%')
|
|
1227
|
+
31. .width('90%')
|
|
1228
|
+
32. .backgroundColor('#90f1f3f5')
|
|
1229
|
+
33. .borderRadius(25)
|
|
1230
|
+
34. .onClick(() => this.task.updateIsFinish())
|
|
1231
|
+
35. }
|
|
1232
|
+
36. }
|
|
1233
|
+
|
|
1234
|
+
38. @ComponentV2
|
|
1235
|
+
39. export default struct ListView {
|
|
1236
|
+
40. @Param taskList: TaskListViewModel = new TaskListViewModel();
|
|
1237
|
+
41. @Param setting: Setting = new Setting();
|
|
1238
|
+
|
|
1239
|
+
43. build() {
|
|
1240
|
+
44. Repeat<TaskViewModel>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
|
|
1241
|
+
45. .each((obj: RepeatItem<TaskViewModel>) => {
|
|
1242
|
+
46. TaskItem({
|
|
1243
|
+
47. task: obj.item,
|
|
1244
|
+
48. deleteTask: () => this.taskList.removeTask(obj.item)
|
|
1245
|
+
49. }).margin(5)
|
|
1246
|
+
50. })
|
|
1247
|
+
51. }
|
|
1248
|
+
52. }
|
|
1249
|
+
```
|
|
1250
|
+
|
|
1251
|
+
[ListView.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/view/ListView.ets#L17-L70)
|
|
1252
|
+
* BottomView:负责提供与任务操作相关的按钮和输入框,如"全部完成"、"全部未完成","设置"三个按钮,以及添加新任务的输入框。点击"全部完成"和"全部未完成"时,通过TaskListViewModel更改所有任务的状态。点击"设置"按钮时,会导航到SettingAbility的设置页面。添加新任务时,通过TaskListViewModel新增任务到任务列表中。
|
|
1253
|
+
|
|
1254
|
+
```
|
|
1255
|
+
1. // src/main/ets/view/BottomView.ets
|
|
1256
|
+
2. import { common, Want } from '@kit.AbilityKit';
|
|
1257
|
+
3. import TaskViewModel from '../viewmodel/TaskViewModel';
|
|
1258
|
+
4. import TaskListViewModel from '../viewmodel/TaskListViewModel';
|
|
1259
|
+
|
|
1260
|
+
6. @Builder
|
|
1261
|
+
7. export function ActionButton(text: string | Resource, onClick: () => void) {
|
|
1262
|
+
8. Button(text, { buttonStyle: ButtonStyleMode.NORMAL })
|
|
1263
|
+
9. .onClick(onClick)
|
|
1264
|
+
10. .margin({
|
|
1265
|
+
11. left: 10,
|
|
1266
|
+
12. right: 10,
|
|
1267
|
+
13. top: 5,
|
|
1268
|
+
14. bottom: 5
|
|
1269
|
+
15. })
|
|
1270
|
+
16. }
|
|
1271
|
+
|
|
1272
|
+
18. @ComponentV2
|
|
1273
|
+
19. export default struct BottomView {
|
|
1274
|
+
20. @Param taskList: TaskListViewModel = new TaskListViewModel();
|
|
1275
|
+
21. @Local newTaskName: string = '';
|
|
1276
|
+
22. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
1277
|
+
|
|
1278
|
+
24. build() {
|
|
1279
|
+
25. Column() {
|
|
1280
|
+
26. Row() {
|
|
1281
|
+
27. ActionButton('All Completed', (): void => this.taskList.finishAll(true))
|
|
1282
|
+
28. ActionButton('All Not Completed', (): void => this.taskList.finishAll(false))
|
|
1283
|
+
29. }
|
|
1284
|
+
30. .margin({ top: 10 })
|
|
1285
|
+
|
|
1286
|
+
32. Row() {
|
|
1287
|
+
33. ActionButton('Setting', (): void => {
|
|
1288
|
+
34. let wantInfo: Want = {
|
|
1289
|
+
35. deviceId: '', // deviceId为空表示本设备。
|
|
1290
|
+
36. bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName。
|
|
1291
|
+
37. abilityName: 'SettingAbility',
|
|
1292
|
+
38. };
|
|
1293
|
+
39. this.context.startAbility(wantInfo);
|
|
1294
|
+
40. })
|
|
1295
|
+
41. }
|
|
1296
|
+
42. .margin({ bottom: 5 })
|
|
1297
|
+
|
|
1298
|
+
44. Row() {
|
|
1299
|
+
45. TextInput({ placeholder: 'Add new tasks', text: this.newTaskName })
|
|
1300
|
+
46. .onChange((value) => this.newTaskName = value)
|
|
1301
|
+
47. .width('70%')
|
|
1302
|
+
48. ActionButton('+', (): void => {
|
|
1303
|
+
49. let newTask = new TaskViewModel();
|
|
1304
|
+
50. newTask.taskName = this.newTaskName;
|
|
1305
|
+
51. this.taskList.addTask(newTask);
|
|
1306
|
+
52. this.newTaskName = '';
|
|
1307
|
+
53. })
|
|
1308
|
+
54. }
|
|
1309
|
+
55. }
|
|
1310
|
+
56. }
|
|
1311
|
+
57. }
|
|
1312
|
+
```
|
|
1313
|
+
|
|
1314
|
+
[BottomView.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/view/BottomView.ets#L17-L75)
|
|
1315
|
+
* TodoListPage:todolist的主页面,包含以上的三个View组件(TitleView、ListView、BottomView),用于统一展示待办事项的各个部分,管理任务列表和用户设置。TodoListPage负责从ViewModel中获取数据,并将数据传递给各个子View组件进行渲染,通过PersistenceV2持久化任务数据,确保数据在应用重启后仍能保持一致。
|
|
1316
|
+
|
|
1317
|
+
```
|
|
1318
|
+
1. // src/main/ets/pages/TodoListPage.ets
|
|
1319
|
+
2. import TaskListViewModel from '../viewmodel/TaskListViewModel';
|
|
1320
|
+
3. import { common } from '@kit.AbilityKit';
|
|
1321
|
+
4. import { AppStorageV2, PersistenceV2 } from '@kit.ArkUI';
|
|
1322
|
+
5. import { Setting } from '../pages/SettingPage';
|
|
1323
|
+
6. import TitleView from '../view/TitleView';
|
|
1324
|
+
7. import ListView from '../view/ListView';
|
|
1325
|
+
8. import BottomView from '../view/BottomView';
|
|
1326
|
+
|
|
1327
|
+
10. @Entry
|
|
1328
|
+
11. @ComponentV2
|
|
1329
|
+
12. struct TodoList {
|
|
1330
|
+
13. @Local taskList: TaskListViewModel = PersistenceV2.connect(TaskListViewModel, 'TaskList', () => new TaskListViewModel())!;
|
|
1331
|
+
14. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
1332
|
+
15. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
1333
|
+
|
|
1334
|
+
17. async aboutToAppear() {
|
|
1335
|
+
18. if (this.taskList.tasks.length === 0) {
|
|
1336
|
+
19. await this.taskList.loadTasks(this.context);
|
|
1337
|
+
20. }
|
|
1338
|
+
21. }
|
|
1339
|
+
|
|
1340
|
+
23. @Computed
|
|
1341
|
+
24. get tasksUnfinished(): number {
|
|
1342
|
+
25. return this.taskList.tasks.filter(task => !task.isFinish).length;
|
|
1343
|
+
26. }
|
|
1344
|
+
|
|
1345
|
+
28. build() {
|
|
1346
|
+
29. Column() {
|
|
1347
|
+
30. TitleView({ tasksUnfinished: this.tasksUnfinished })
|
|
1348
|
+
31. ListView({ taskList: this.taskList, setting: this.setting });
|
|
1349
|
+
32. BottomView({ taskList: this.taskList });
|
|
1350
|
+
33. }
|
|
1351
|
+
34. .height('100%')
|
|
1352
|
+
35. .width('100%')
|
|
1353
|
+
36. .alignItems(HorizontalAlign.Start)
|
|
1354
|
+
37. .margin({ left: 15 })
|
|
1355
|
+
38. }
|
|
1356
|
+
39. }
|
|
1357
|
+
```
|
|
1358
|
+
|
|
1359
|
+
[TodoListPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/TodoListPage.ets#L17-L57)
|
|
1360
|
+
* SettingPage:设置页面,负责管理是否显示已完成任务的设置。通过AppStorageV2应用全局存储用户的设置,用户通过Toggle开关切换showCompletedTask状态。
|
|
1361
|
+
|
|
1362
|
+
```
|
|
1363
|
+
1. // src/main/ets/pages/SettingPage.ets
|
|
1364
|
+
2. import { AppStorageV2 } from '@kit.ArkUI';
|
|
1365
|
+
3. import { common } from '@kit.AbilityKit';
|
|
1366
|
+
|
|
1367
|
+
5. @ObservedV2
|
|
1368
|
+
6. export class Setting {
|
|
1369
|
+
7. @Trace public showCompletedTask: boolean = true;
|
|
1370
|
+
8. }
|
|
1371
|
+
|
|
1372
|
+
10. @Entry
|
|
1373
|
+
11. @ComponentV2
|
|
1374
|
+
12. struct SettingPage {
|
|
1375
|
+
13. @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
|
|
1376
|
+
14. private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
|
|
1377
|
+
|
|
1378
|
+
16. build(){
|
|
1379
|
+
17. Column(){
|
|
1380
|
+
18. Text('Setting')
|
|
1381
|
+
19. .fontSize(40)
|
|
1382
|
+
20. .margin({ bottom: 10 })
|
|
1383
|
+
21. Row() {
|
|
1384
|
+
22. Text('Show completed tasks')
|
|
1385
|
+
23. Toggle({ type: ToggleType.Switch, isOn:this.setting.showCompletedTask })
|
|
1386
|
+
24. .onChange((isOn) => {
|
|
1387
|
+
25. this.setting.showCompletedTask = isOn;
|
|
1388
|
+
26. })
|
|
1389
|
+
27. }
|
|
1390
|
+
28. Button('Back to To do')
|
|
1391
|
+
29. .onClick(()=>this.context.terminateSelf())
|
|
1392
|
+
30. .margin({ top: 10 })
|
|
1393
|
+
31. }
|
|
1394
|
+
32. .alignItems(HorizontalAlign.Start)
|
|
1395
|
+
33. }
|
|
1396
|
+
34. }
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
[SettingPage.ets](https://gitcode.com/HarmonyOS_Samples/guide-snippets/blob/HarmonyOS-feature-20260402/ArkUISample/StateMgmtV2MVVM/entry/src/main/ets/pages/SettingPage.ets#L17-L54)
|
|
1400
|
+
|
|
1401
|
+
## 总结
|
|
1402
|
+
|
|
1403
|
+
本指南通过待办事项应用示例,引入状态管理V2装饰器,并通过代码重构实现MVVM架构。最终将数据、业务逻辑和视图展示分层处理,使得代码结构更加清晰且易于维护。开发者通过正确应用Model、View和ViewModel分层结构,能够更好地理解和应用MVVM模式,进而在实际项目中提升开发效率、保证代码质量,并优化数据与UI的同步机制,简化整体开发流程。
|
|
1404
|
+
|
|
1405
|
+
## 代码示例
|
|
1406
|
+
|
|
1407
|
+
[完整源码](https://gitcode.com/openharmony/applications_app_samples/tree/master/code/DocsSample/ArkUISample/StateMgmtV2MVVM/entry)
|