• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
12## 通过状态管理V2版本实现ViewModel
13
14在MVVM模式中,ViewModel扮演着至关重要的角色,负责管理数据状态,并在数据发生变化时自动更新视图。ArkUI的状态管理V2版本提供了丰富的装饰器和工具,帮助开发者在自定义组件之间共享数据,确保数据变化自动同步到UI。常用的状态管理装饰器包括\@Local、\@Param、\@Event、\@ObservedV2、\@Trace等等。除此之外,V2还提供了AppStorageV2和PersistenceV2作为全局状态存储工具,用于应用间的状态共享和持久化存储。
15
16本节将通过一个简单的todolist示例,逐步引入和使用状态管理V2的装饰器及工具,从基础的静态任务列表开始,逐步扩展功能。每个步骤都基于上一步扩展,帮助开发者循序渐进地理解并掌握各个装饰器的使用方法。
17
18### 基础示例
19
20首先,从最基础的静态待办事项列表开始。在这个例子中,任务是静态的,没有状态变化和动态交互。
21
22```ts
23// src/main/ets/pages/1-Basic.ets
24
25@Entry
26@ComponentV2
27struct TodoList {
28  build() {
29    Column() {
30      Text('待办')
31        .fontSize(40)
32        .margin({ bottom: 10 })
33      Text('Task1')
34      Text('Task2')
35      Text('Task3')
36    }
37  }
38}
39```
40
41### 添加\@Local,实现对组件内部状态观测
42
43完成静态待办列表展示后,为了让用户能够更改任务的完成状态,需要使待办事项能够响应交互并动态更新显示。为此,引入\@Local装饰器管理组件内部的状态。被\@Local装饰的变量发生变化时,会触发绑定的UI组件刷新。
44
45在这个例子中,新增了一个被\@Local装饰的isFinish属性代表任务是否完成。准备了两个图标:finished.pngunfinished.png,用于展示任务完成或未完成的状态。点击待办事项时,isFinish状态切换,从而更新图标和文本删除线的效果。
46
47```ts
48// src/main/ets/pages/2-Local.ets
49
50@Entry
51@ComponentV2
52struct TodoList {
53  @Local isFinish: boolean = false;
54
55  build() {
56    Column() {
57      Text('待办')
58        .fontSize(40)
59        .margin({ bottom: 10 })
60      Row() {
61        // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
62        Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
63          .width(28)
64          .height(28)
65        Text('Task1')
66          .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
67      }
68      .onClick(() => this.isFinish = !this.isFinish)
69    }
70  }
71}
72```
73
74### 添加\@Param,实现组件接受外部输入
75实现了任务本地状态切换后,为了增强待办事项列表的灵活性,需要能够动态设置每个任务的名称,而不是固定在代码中。引入\@Param装饰器后,子组件被修饰的变量能够接收父组件传入的值,实现从父到子的单向数据同步。默认情况下,\@Param是只读的。如需在子组件中对传入的值进行本地更新,可使用\@Param \@Once进行配置。
76
77在这个例子中,每个待办事项被抽象为TaskItem组件。被\@Param修饰的taskName属性从父组件TodoList传入任务名称,使TaskItem组件灵活且可复用,能接收并渲染不同的任务名称。被\@Param \@Once装饰的isFinish属性在接收初始值后,可以在子组件内更新。
78
79```ts
80// src/main/ets/pages/3-Param.ets
81
82@ComponentV2
83struct TaskItem {
84  @Param taskName: string = '';
85  @Param @Once isFinish: boolean = false;
86
87  build() {
88    Row() {
89      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
90      Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
91        .width(28)
92        .height(28)
93      Text(this.taskName)
94        .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
95    }
96    .onClick(() => this.isFinish = !this.isFinish)
97  }
98}
99
100@Entry
101@ComponentV2
102struct TodoList {
103  build() {
104    Column() {
105      Text('待办')
106        .fontSize(40)
107        .margin({ bottom: 10 })
108      TaskItem({ taskName: 'Task 1', isFinish: false })
109      TaskItem({ taskName: 'Task 2', isFinish: false })
110      TaskItem({ taskName: 'Task 3', isFinish: false })
111    }
112  }
113}
114```
115
116### 添加\@Event,实现组件对外输出
117
118在实现任务名称动态设置后,任务列表内容依然是固定的,需要增加任务项的添加和删除功能,以实现任务列表的动态扩展。为此,引入\@Event装饰器,用于实现子组件向父组件输出数据。
119
120在这个例子中,每个TaskItem增加了删除按钮,同时任务列表底部增加了添加新任务的功能。点击子组件TaskItem的“删除”按钮时,deleteTask事件会被触发并传递给父组件TodoList,父组件响应并将该任务从列表中移除。通过使用\@Param和\@Event,子组件不仅能接收父组件的数据,还能够将事件传递回父组件,实现数据的双向同步。
121
122```ts
123// src/main/ets/pages/4-Event.ets
124
125@ComponentV2
126struct TaskItem {
127  @Param taskName: string = '';
128  @Param @Once isFinish: boolean = false;
129  @Event deleteTask: () => void = () => {};
130
131  build() {
132    Row() {
133      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
134      Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
135        .width(28)
136        .height(28)
137      Text(this.taskName)
138        .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
139      Button('删除')
140        .onClick(() => this.deleteTask())
141    }
142    .onClick(() => this.isFinish = !this.isFinish)
143  }
144}
145
146@Entry
147@ComponentV2
148struct TodoList {
149  @Local tasks: string[] = ['task1','task2','task3'];
150  @Local newTaskName: string = '';
151  build() {
152    Column() {
153      Text('待办')
154        .fontSize(40)
155        .margin({ bottom: 10 })
156      ForEach(this.tasks, (task: string) => {
157          TaskItem({
158            taskName: task,
159            isFinish: false,
160            deleteTask: () => this.tasks.splice(this.tasks.indexOf(task), 1)
161          })
162      })
163      Row() {
164        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
165          .onChange((value) => this.newTaskName = value)
166          .width('70%')
167        Button('增加事项')
168          .onClick(() => {
169            this.tasks.push(this.newTaskName);
170            this.newTaskName = '';
171          })
172      }
173    }
174  }
175}
176```
177
178### 添加Repeat,实现子组件复用
179
180添加了任务增删功能后,随着任务列表项的增加,需要一种高效渲染多个结构相同的子组件的方法,以提高界面的性能表现。为此,引入了Repeat方法,用于优化任务列表的渲染过程。Repeat支持两种模式:virtualScroll和non-virtualScroll。virtualScroll适用于大量数据的场景,在滚动类容器中按需加载组件,极大节省内存和提升渲染效率。non-virtualScroll适用于数据量较小的场景,一次性渲染所有组件,并在数据变化时仅更新需要变化的部分,避免整体重新渲染。
181
182在本例中,任务量较少,选择了non-virtualScroll模式。新建了一个任务数组tasks,并使用Repeat方法迭代数组中的每一项,动态生成并复用TaskItem组件。在任务增删时,这种方式能高效复用已有组件,避免重复渲染,从而提高界面响应速度和性能。这种机制有效地提高了代码的复用性和渲染效率。
183
184```ts
185// src/main/ets/pages/5-Repeat.ets
186
187@ComponentV2
188struct TaskItem {
189  @Param taskName: string = '';
190  @Param @Once isFinish: boolean = false;
191  @Event deleteTask: () => void = () => {};
192
193  build() {
194    Row() {
195      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
196      Image(this.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
197        .width(28)
198        .height(28)
199      Text(this.taskName)
200        .decoration({ type: this.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
201      Button('删除')
202        .onClick(() => this.deleteTask())
203    }
204    .onClick(() => this.isFinish = !this.isFinish)
205  }
206}
207
208@Entry
209@ComponentV2
210struct TodoList {
211  @Local tasks: string[] = ['task1','task2','task3'];
212  @Local newTaskName: string = '';
213  build() {
214    Column() {
215      Text('待办')
216        .fontSize(40)
217        .margin({ bottom: 10 })
218      Repeat<string>(this.tasks)
219        .each((obj: RepeatItem<string>) => {
220          TaskItem({
221            taskName: obj.item,
222            isFinish: false,
223            deleteTask: () => this.tasks.splice(this.tasks.indexOf(obj.item), 1)
224          })
225        })
226      Row() {
227        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
228          .onChange((value) => this.newTaskName = value)
229          .width('70%')
230        Button('增加事项')
231          .onClick(() => {
232            this.tasks.push(this.newTaskName);
233            this.newTaskName = '';
234          })
235      }
236    }
237  }
238}
239```
240
241### 添加\@ObservedV2,\@Trace,实现类属性观测变化
242
243实现了多个功能之后,任务列表的管理逐渐变得复杂。为了更好地处理任务数据的变化,特别在多层嵌套结构中,需要确保属性的变化可以被深度观测并自动更新UI。为此,引入了\@ObservedV2和\@Trace装饰器。相比于\@Local只能观测对象本身及其第一层的变化,\@ObservedV2和\@Trace更适用于处理多层嵌套、继承等复杂结构场景。在\@ObservedV2装饰的类中,被\@Trace装饰的属性发生变化时,会触发其绑定的UI组件刷新。
244
245在这个例子中,任务(Task)被抽象为一个类,并用\@ObservedV2标记该类,用\@Trace标记isFinish属性。TodoList组件嵌套了TaskItem,TaskItem又嵌套了Task。在最外层的TodoList中,添加了"全部完成"和"全部未完成"的按钮,每次点击这些按钮都会直接更新最内层Task类的isFinish属性。\@ObservedV2和\@Trace确保可以观察到对应isFinish UI组件的刷新,从而实现了对嵌套类属性的深度观测。
246
247```ts
248// src/main/ets/pages/6-ObservedV2Trace.ets
249
250@ObservedV2
251class Task {
252  taskName: string = '';
253  @Trace isFinish: boolean = false;
254
255  constructor (taskName: string, isFinish: boolean) {
256    this.taskName = taskName;
257    this.isFinish = isFinish;
258  }
259}
260
261@ComponentV2
262struct TaskItem {
263  @Param task: Task = new Task('', false);
264  @Event deleteTask: () => void = () => {};
265
266  build() {
267    Row() {
268      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
269      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
270        .width(28)
271        .height(28)
272      Text(this.task.taskName)
273        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
274      Button('删除')
275        .onClick(() => this.deleteTask())
276    }
277    .onClick(() => this.task.isFinish = !this.task.isFinish)
278  }
279}
280
281@Entry
282@ComponentV2
283struct TodoList {
284  @Local tasks: Task[] = [
285    new Task('task1', false),
286    new Task('task2', false),
287    new Task('task3', false),
288  ];
289  @Local newTaskName: string = '';
290
291  finishAll(ifFinish: boolean) {
292    for (let task of this.tasks) {
293      task.isFinish = ifFinish;
294    }
295  }
296
297  build() {
298    Column() {
299      Text('待办')
300        .fontSize(40)
301        .margin({ bottom: 10 })
302      Repeat<Task>(this.tasks)
303        .each((obj: RepeatItem<Task>) => {
304          TaskItem({
305            task: obj.item,
306            deleteTask: () => this.tasks.splice(this.tasks.indexOf(obj.item), 1)
307          })
308        })
309      Row() {
310        Button('全部完成')
311          .onClick(() => this.finishAll(true))
312        Button('全部未完成')
313          .onClick(() => this.finishAll(false))
314      }
315      Row() {
316        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
317          .onChange((value) => this.newTaskName = value)
318          .width('70%')
319        Button('增加事项')
320          .onClick(() => {
321            this.tasks.push(new Task(this.newTaskName, false));
322            this.newTaskName = '';
323          })
324      }
325    }
326  }
327}
328```
329
330### 添加\@Monitor,\@Computed,实现监听状态变量和计算属性
331
332在当前任务列表功能基础上,为了提升体验,可以增加一些额外的功能,如任务状态变化的监听和未完成任务数量的动态计算。为此,引入\@Monitor和\@Computed装饰器。\@Monitor用于深度监听状态变量,在属性变化时触发自定义回调方法。\@Computed用于装饰getter方法,检测被计算的属性变化。在被计算的值变化时,仅会计算一次,减少重复计算开销。
333
334在这个例子中,使用\@Monitor深度监听TaskItem中task的isFinish属性。当任务完成状态变化时会触发onTasksFinished回调,输出日志记录任务完成状态的变化。此外,新增了对todolist中未完成任务的数量的记录。用\@Computed装饰tasksUnfinished,每当任务状态变化时自动重新计算。通过这两个装饰器,实现了对状态变量的深度监听和高效的计算属性。
335
336```ts
337// src/main/ets/pages/7-MonitorComputed.ets
338
339@ObservedV2
340class Task {
341  taskName: string = '';
342  @Trace isFinish: boolean = false;
343
344  constructor (taskName: string, isFinish: boolean) {
345    this.taskName = taskName;
346    this.isFinish = isFinish;
347  }
348}
349
350@ComponentV2
351struct TaskItem {
352  @Param task: Task = new Task('', false);
353  @Event deleteTask: () => void = () => {};
354  @Monitor('task.isFinish')
355  onTaskFinished(mon: IMonitor) {
356    console.log('任务' + this.task.taskName + '的完成状态从' + mon.value()?.before + '变为了' + mon.value()?.now);
357  }
358
359  build() {
360    Row() {
361      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
362      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
363        .width(28)
364        .height(28)
365      Text(this.task.taskName)
366        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
367      Button('删除')
368        .onClick(() => this.deleteTask())
369    }
370    .onClick(() => this.task.isFinish = !this.task.isFinish)
371  }
372}
373
374@Entry
375@ComponentV2
376struct TodoList {
377  @Local tasks: Task[] = [
378    new Task('task1', false),
379    new Task('task2', false),
380    new Task('task3', false),
381  ];
382  @Local newTaskName: string = '';
383
384  finishAll(ifFinish: boolean) {
385    for (let task of this.tasks) {
386      task.isFinish = ifFinish;
387    }
388  }
389
390  @Computed
391  get tasksUnfinished(): number {
392    return this.tasks.filter(task => !task.isFinish).length;
393  }
394
395  build() {
396    Column() {
397      Text('待办')
398        .fontSize(40)
399        .margin({ bottom: 10 })
400      Text(`未完成任务:${this.tasksUnfinished}`)
401      Repeat<Task>(this.tasks)
402        .each((obj: RepeatItem<Task>) => {
403          TaskItem({
404            task: obj.item,
405            deleteTask: () => this.tasks.splice(this.tasks.indexOf(obj.item), 1)
406          })
407        })
408      Row() {
409        Button('全部完成')
410          .onClick(() => this.finishAll(true))
411        Button('全部未完成')
412          .onClick(() => this.finishAll(false))
413      }
414      Row() {
415        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
416          .onChange((value) => this.newTaskName = value)
417          .width('70%')
418        Button('增加事项')
419          .onClick(() => {
420            this.tasks.push(new Task(this.newTaskName, false));
421            this.newTaskName = '';
422          })
423      }
424    }
425  }
426}
427```
428
429### 添加AppStorageV2,实现应用全局UI状态存储
430
431随着待办事项功能的不断增强,应用可能涉及到多个页面或功能模块,此时常常需要在这些页面之间共享全局状态。例如,在待办事项应用中,可以新增一个设置页面与主界面联动。为实现跨页面的状态共享,引入AppStorageV2,用于在多个UIAbility实例之间存储和共享应用的全局状态。
432
433在这个例子中,新增了一个Ability,SettingAbility,用于加载设置页SettingPage。SettingPage包含了一个Setting类,其中的showCompletedTask属性用于控制是否显示已完成的任务,用户通过一个开关可以切换该选项。两个Ability通过AppStorageV2共享设置数据,键为"Setting",对应的数据为Setting类。第一次通过connect连接Setting时,若不存在储存的数据,会新建一个默认showCompletedTask为trueSetting实例。后续用户在设置页面修改设置后,主页面会根据这一设置更新任务列表的显示。通过AppStorageV2,实现了跨Ability、跨页面的数据共享。
434
435```ts
436// src/main/ets/pages/8-AppStorageV2.ets
437
438import { AppStorageV2 } from '@kit.ArkUI';
439import { common, Want } from '@kit.AbilityKit';
440import { Setting } from './SettingPage';
441
442@ObservedV2
443class Task {
444  taskName: string = '';
445  @Trace isFinish: boolean = false;
446
447  constructor (taskName: string, isFinish: boolean) {
448    this.taskName = taskName;
449    this.isFinish = isFinish;
450  }
451}
452
453@ComponentV2
454struct TaskItem {
455  @Param task: Task = new Task('', false);
456  @Event deleteTask: () => void = () => {};
457  @Monitor('task.isFinish')
458  onTaskFinished(mon: IMonitor) {
459    console.log('任务' + this.task.taskName + '的完成状态从' + mon.value()?.before + '变为了' + mon.value()?.now);
460  }
461
462  build() {
463    Row() {
464      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
465      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
466        .width(28)
467        .height(28)
468      Text(this.task.taskName)
469        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
470      Button('删除')
471        .onClick(() => this.deleteTask())
472    }
473    .onClick(() => this.task.isFinish = !this.task.isFinish)
474  }
475}
476
477@Entry
478@ComponentV2
479struct TodoList {
480  @Local tasks: Task[] = [
481    new Task('task1', false),
482    new Task('task2', false),
483    new Task('task3', false),
484  ];
485  @Local newTaskName: string = '';
486  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
487  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
488
489  finishAll(ifFinish: boolean) {
490    for (let task of this.tasks) {
491      task.isFinish = ifFinish;
492    }
493  }
494
495  @Computed
496  get tasksUnfinished(): number {
497    return this.tasks.filter(task => !task.isFinish).length;
498  }
499
500  build() {
501    Column() {
502      Text('待办')
503        .fontSize(40)
504        .margin({ bottom: 10 })
505      Text(`未完成任务:${this.tasksUnfinished}`)
506      Repeat<Task>(this.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
507        .each((obj: RepeatItem<Task>) => {
508          TaskItem({
509            task: obj.item,
510            deleteTask: () => this.tasks.splice(this.tasks.indexOf(obj.item), 1)
511          })
512        })
513      Row() {
514        Button('全部完成')
515          .onClick(() => this.finishAll(true))
516        Button('全部未完成')
517          .onClick(() => this.finishAll(false))
518        Button('设置')
519          .onClick(() => {
520            let wantInfo: Want = {
521              deviceId: '', // deviceId为空表示本设备
522              bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName
523              abilityName: 'SettingAbility',
524            };
525            this.context.startAbility(wantInfo);
526          })
527      }
528      Row() {
529        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
530          .onChange((value) => this.newTaskName = value)
531          .width('70%')
532        Button('增加事项')
533          .onClick(() => {
534            this.tasks.push(new Task(this.newTaskName, false));
535            this.newTaskName = '';
536          })
537      }
538    }
539  }
540}
541```
542
543```ts
544// SettingAbility的SettingPage页面代码
545import { AppStorageV2 } from '@kit.ArkUI';
546import { common } from '@kit.AbilityKit';
547
548@ObservedV2
549export class Setting {
550  @Trace showCompletedTask: boolean = true;
551}
552
553@Entry
554@ComponentV2
555struct SettingPage {
556  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
557  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
558
559  build() {
560    Column() {
561      Text('设置')
562        .fontSize(40)
563        .margin({ bottom: 10 })
564      Row() {
565        Text('显示已完成任务');
566        Toggle({ type: ToggleType.Switch, isOn:this.setting.showCompletedTask })
567          .onChange((isOn) => {
568            this.setting.showCompletedTask = isOn;
569          })
570      }
571      Button('返回待办')
572        .onClick(()=>this.context.terminateSelf())
573        .margin({ top: 10 })
574    }
575    .alignItems(HorizontalAlign.Start)
576  }
577}
578```
579
580### 添加PersistenceV2,实现持久化UI状态存储
581
582为了保证用户在重新打开应用时仍然能够看到之前的任务状态,可以引入持久化存储方案。使用PersistenceV2能够将数据持久化保存在设备磁盘上。与AppStorageV2的运行时内存不同,PersistenceV2能确保即使应用关闭后再启动,数据依然保持不变。
583
584在这个例子中,创建了一个TaskList类,用于通过PersistenceV2持久化存储所有任务信息,键为"TaskList",数据对应TaskList类。第一次通过connect连接TaskList时,如果没有数据,会创建一个默认tasks数组为空的新TaskList实例。在aboutToAppear生命周期函数中,连接到PersistenceV2的TaskList,若无存储任务数据,会从本地文件defaultTasks.json中加载任务并存储到PersistenceV2中。此后,每个任务的完成状态都会同步到PersistenceV2中。这样,即使应用关闭后再次打开,所有任务数据依旧保持不变,实现了持久化的应用状态存储功能。
585
586```ts
587// src/main/ets/pages/9-PersistenceV2.ets
588
589import { AppStorageV2, PersistenceV2, Type } from '@kit.ArkUI';
590import { common, Want } from '@kit.AbilityKit';
591import { Setting } from './SettingPage';
592import util from '@ohos.util';
593
594@ObservedV2
595class Task {
596  // 未实现构造函数,因为@Type当前不支持带参数的构造函数
597  @Trace taskName: string = 'Todo';
598  @Trace isFinish: boolean = false;
599}
600
601@ObservedV2
602class TaskList {
603  // 对于复杂对象需要@Type修饰,确保序列化成功
604  @Type(Task)
605  @Trace tasks: Task[] = [];
606
607  constructor(tasks: Task[]) {
608    this.tasks = tasks;
609  }
610
611  async loadTasks(context: common.UIAbilityContext) {
612    let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
613    let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM : true };
614    let textDecoder = util.TextDecoder.create('utf-8',textDecoderOptions);
615    let result = textDecoder.decodeToString(getJson);
616    this.tasks =JSON.parse(result).map((task: Task)=>{
617      let newTask = new Task();
618      newTask.taskName = task.taskName;
619      newTask.isFinish = task.isFinish;
620      return newTask;
621    });
622  }
623}
624
625@ComponentV2
626struct TaskItem {
627  @Param task: Task = new Task();
628  @Event deleteTask: () => void = () => {};
629  @Monitor('task.isFinish')
630  onTaskFinished(mon: IMonitor) {
631    console.log('任务' + this.task.taskName + '的完成状态从' + mon.value()?.before + '变为了' + mon.value()?.now);
632  }
633
634  build() {
635    Row() {
636      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
637      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
638        .width(28)
639        .height(28)
640      Text(this.task.taskName)
641        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
642      Button('删除')
643        .onClick(() => this.deleteTask())
644    }
645    .onClick(() => this.task.isFinish = !this.task.isFinish)
646  }
647}
648
649@Entry
650@ComponentV2
651struct TodoList {
652  @Local taskList: TaskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
653  @Local newTaskName: string = '';
654  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
655  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
656
657  async aboutToAppear() {
658    this.taskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
659    if (this.taskList.tasks.length === 0) {
660      await this.taskList.loadTasks(this.context);
661    }
662  }
663
664  finishAll(ifFinish: boolean) {
665    for (let task of this.taskList.tasks) {
666      task.isFinish = ifFinish;
667    }
668  }
669
670  @Computed
671  get tasksUnfinished(): number {
672    return this.taskList.tasks.filter(task => !task.isFinish).length;
673  }
674
675  build() {
676    Column() {
677      Text('待办')
678        .fontSize(40)
679        .margin({ bottom: 10 })
680      Text(`未完成任务:${this.tasksUnfinished}`)
681      Repeat<Task>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
682        .each((obj: RepeatItem<Task>) => {
683          TaskItem({
684            task: obj.item,
685            deleteTask: () => this.taskList.tasks.splice(this.taskList.tasks.indexOf(obj.item), 1)
686          })
687        })
688      Row() {
689        Button('全部完成')
690          .onClick(() => this.finishAll(true))
691        Button('全部未完成')
692          .onClick(() => this.finishAll(false))
693        Button('设置')
694          .onClick(() => {
695            let wantInfo: Want = {
696              deviceId: '', // deviceId为空表示本设备
697              bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName
698              abilityName: 'SettingAbility',
699            };
700            this.context.startAbility(wantInfo);
701          })
702      }
703      Row() {
704        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
705          .onChange((value) => this.newTaskName = value)
706          .width('70%')
707        Button('增加事项')
708          .onClick(() => {
709            let newTask = new Task();
710            newTask.taskName = this.newTaskName;
711            this.taskList.tasks.push(newTask);
712            this.newTaskName = '';
713          })
714      }
715    }
716  }
717}
718```
719
720JSON文件存放在src/main/resources/rawfile/defaultTasks.json路径下。
721```json
722[
723  {"taskName": "学习ArkTS开发", "isFinish": false},
724  {"taskName": "健身", "isFinish": false},
725  {"taskName": "买水果", "isFinish": true},
726  {"taskName": "取快递", "isFinish": true},
727  {"taskName": "刷题", "isFinish": true}
728]
729```
730
731### 添加\@Builder,实现自定义构建函数
732
733随着应用功能逐步扩展,代码中的某些UI元素开始重复,这不仅增加了代码量,也让维护变得复杂。为了解决这一问题,可以使用\@Builder装饰器,将重复的UI组件抽象成独立的构建方法,便于复用和代码的模块化。
734
735在这个例子中,使用\@Builder定义了ActionButton方法,统一管理各类按钮的文字、样式和点击事件,使代码更简洁、结构更清晰,提升了代码的可维护性。在此基础上,调整了待办事项界面的布局和样式,例如组件的间距、颜色和大小,使UI更美观,最终呈现一个功能完善、界面简洁的待办事项应用。
736
737```ts
738// src/main/ets/pages/10-Builder.ets
739
740import { AppStorageV2, PersistenceV2, Type } from '@kit.ArkUI';
741import { common, Want } from '@kit.AbilityKit';
742import { Setting } from './SettingPage';
743import util from '@ohos.util';
744
745@ObservedV2
746class Task {
747  // 未实现构造函数,因为@Type当前不支持带参数的构造函数
748  @Trace taskName: string = 'Todo';
749  @Trace isFinish: boolean = false;
750}
751
752@Builder function ActionButton(text: string, onClick:() => void) {
753  Button(text, { buttonStyle: ButtonStyleMode.NORMAL })
754    .onClick(onClick)
755    .margin({ left: 10, right: 10, top: 5, bottom: 5 })
756}
757
758@ObservedV2
759class TaskList {
760  // 对于复杂对象需要@Type修饰,确保序列化成功
761  @Type(Task)
762  @Trace tasks: Task[] = [];
763
764  constructor(tasks: Task[]) {
765    this.tasks = tasks;
766  }
767
768  async loadTasks(context: common.UIAbilityContext) {
769    let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
770    let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM : true };
771    let textDecoder = util.TextDecoder.create('utf-8',textDecoderOptions);
772    let result = textDecoder.decodeToString(getJson);
773    this.tasks =JSON.parse(result).map((task: Task)=>{
774      let newTask = new Task();
775      newTask.taskName = task.taskName;
776      newTask.isFinish = task.isFinish;
777      return newTask;
778    });
779  }
780}
781
782@ComponentV2
783struct TaskItem {
784  @Param task: Task = new Task();
785  @Event deleteTask: () => void = () => {};
786  @Monitor('task.isFinish')
787  onTaskFinished(mon: IMonitor) {
788    console.log('任务' + this.task.taskName + '的完成状态从' + mon.value()?.before + '变为了' + mon.value()?.now);
789  }
790
791  build() {
792    Row() {
793      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
794      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
795        .width(28)
796        .height(28)
797        .margin({ left : 15, right : 10 })
798      Text(this.task.taskName)
799        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
800        .fontSize(18)
801      ActionButton('删除', () => this.deleteTask())
802    }
803    .height('7%')
804    .width('90%')
805    .backgroundColor('#90f1f3f5')
806    .borderRadius(25)
807    .onClick(() => this.task.isFinish = !this.task.isFinish)
808  }
809}
810
811@Entry
812@ComponentV2
813struct TodoList {
814  @Local taskList: TaskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
815  @Local newTaskName: string = '';
816  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
817  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
818
819  async aboutToAppear() {
820    this.taskList = PersistenceV2.connect(TaskList, 'TaskList', () => new TaskList([]))!;
821    if (this.taskList.tasks.length === 0) {
822      await this.taskList.loadTasks(this.context);
823    }
824  }
825
826  finishAll(ifFinish: boolean) {
827    for (let task of this.taskList.tasks) {
828      task.isFinish = ifFinish;
829    }
830  }
831
832  @Computed
833  get tasksUnfinished(): number {
834    return this.taskList.tasks.filter(task => !task.isFinish).length;
835  }
836
837  build() {
838    Column() {
839      Text('待办')
840        .fontSize(40)
841        .margin(10)
842      Text(`未完成任务:${this.tasksUnfinished}`)
843        .margin({ left: 10, bottom: 10 })
844      Repeat<Task>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
845        .each((obj: RepeatItem<Task>) => {
846          TaskItem({
847            task: obj.item,
848            deleteTask: () => this.taskList.tasks.splice(this.taskList.tasks.indexOf(obj.item), 1)
849          }).margin(5)
850        })
851      Row() {
852        ActionButton('全部完成', (): void => this.finishAll(true))
853        ActionButton('全部未完成', (): void => this.finishAll(false))
854        ActionButton('设置', (): void => {
855          let wantInfo: Want = {
856            deviceId: '', // deviceId为空表示本设备
857            bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName
858            abilityName: 'SettingAbility',
859          };
860          this.context.startAbility(wantInfo);
861        })
862      }
863      .margin({ top: 10, bottom: 5 })
864      Row() {
865        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
866          .onChange((value) => this.newTaskName = value)
867          .width('70%')
868        ActionButton('+', (): void => {
869          let newTask = new Task();
870          newTask.taskName = this.newTaskName;
871          this.taskList.tasks.push(newTask);
872          this.newTaskName = '';
873        })
874      }
875    }
876    .height('100%')
877    .width('100%')
878    .alignItems(HorizontalAlign.Start)
879    .margin({ left: 15 })
880  }
881}
882```
883
884### 效果图展示
885![todolist](./figures/MVVMV2-todolist.gif)
886
887## 重构代码以符合MVVM架构
888
889前面的例子通过使用一系列的状态管理装饰器,实现了todolist中的数据同步与UI更新。然而,随着应用功能的复杂化,代码的结构变得难以维护,Model、View和ViewModel的职责并没有完全分离,仍然存在一定的耦合。为了更好地组织代码和提升可维护性,使用MVVM模式重构代码,进一步将数据层(Model)、逻辑层(ViewModel)和展示层(View)分离。
890
891### 重构后的代码结构
892```
893/src
894├── /main
895│   ├── /ets
896│   │   ├── /entryability
897│   │   ├── /model
898│   │   │   ├── TaskListModel.ets
899│   │   │   └── TaskModel.ets
900│   │   ├── /pages
901│   │   │   ├── SettingPage.ets
902│   │   │   └── TodoListPage.ets
903│   │   ├── /settingability
904│   │   ├── /view
905│   │   │   ├── BottomView.ets
906│   │   │   ├── ListView.ets
907│   │   │   └── TitleView.ets
908│   │   ├── /viewmodel
909│   │   │   ├── TaskListViewModel.ets
910│   │   │   └── TaskViewModel.ets
911│   └── /resources
912│       ├── ...
913├─── ...
914```
915
916### Model层
917Model层负责管理应用的数据及其业务逻辑,通常与后端或数据存储进行交互。在todolist应用中,Model层的主要职责是存储任务数据、加载任务列表,并提供数据操作的接口,而不直接涉及UI展示。
918
919- TaskModel: 单个任务的基本数据结构,包含任务名称和完成状态。
920
921```ts
922// src/main/ets/model/TaskModel.ets
923
924export default class TaskModel {
925  taskName: string = 'Todo';
926  isFinish: boolean = false;
927}
928```
929
930- TaskListModel: 任务的集合,提供从本地加载任务数据的功能。
931```ts
932// src/main/ets/model/TaskListModel.ets
933
934import { common } from '@kit.AbilityKit';
935import util from '@ohos.util';
936import TaskModel from'./TaskModel';
937
938export default class TaskListModel {
939  tasks: TaskModel[] = [];
940
941  constructor(tasks: TaskModel[]) {
942    this.tasks = tasks;
943  }
944
945  async loadTasks(context: common.UIAbilityContext){
946    let getJson = await context.resourceManager.getRawFileContent('defaultTasks.json');
947    let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM : true };
948    let textDecoder = util.TextDecoder.create('utf-8',textDecoderOptions);
949    let result = textDecoder.decodeToString(getJson);
950    this.tasks =JSON.parse(result).map((task: TaskModel)=>{
951      let newTask = new TaskModel();
952      newTask.taskName = task.taskName;
953      newTask.isFinish = task.isFinish;
954      return newTask;
955    });
956  }
957}
958```
959
960### ViewModel层
961
962ViewModel层负责管理UI状态和业务逻辑,扮演Model和View之间的桥梁角色。在ViewModel中,负责监控Model数据的变化,处理应用的逻辑,并将数据同步到View层,从而实现UI的自动更新。ViewModel的使用实现了数据与视图的解耦,提高了代码的可读性和可维护性。
963
964- TaskViewModel:封装单个任务的数据和状态变更逻辑,通过状态装饰器监控数据的变化。
965
966```ts
967// src/main/ets/viewmodel/TaskViewModel.ets
968
969import TaskModel from '../model/TaskModel';
970
971@ObservedV2
972export default class TaskViewModel {
973  @Trace taskName: string = 'Todo';
974  @Trace isFinish: boolean = false;
975
976  updateTask(task: TaskModel) {
977    this.taskName = task.taskName;
978    this.isFinish = task.isFinish;
979  }
980
981  updateIsFinish(): void {
982    this.isFinish = !this.isFinish;
983  }
984}
985```
986
987- TaskListViewModel: 封装了任务列表以及管理功能,包括加载任务、批量更新任务状态,以及添加和删除任务。
988
989```ts
990// src/main/ets/viewmodel/TaskListViewModel.ets
991
992import { common } from '@kit.AbilityKit';
993import { Type } from '@kit.ArkUI';
994import TaskListModel from '../model/TaskListModel';
995import TaskViewModel from'./TaskViewModel';
996
997@ObservedV2
998export default class TaskListViewModel {
999  @Type(TaskViewModel)
1000  @Trace tasks: TaskViewModel[] = [];
1001
1002  async loadTasks(context: common.UIAbilityContext) {
1003    let taskList = new TaskListModel([]);
1004    await taskList.loadTasks(context);
1005    for(let task of taskList.tasks){
1006      let taskViewModel = new TaskViewModel();
1007      taskViewModel.updateTask(task);
1008      this.tasks.push(taskViewModel);
1009    }
1010  }
1011
1012  finishAll(ifFinish: boolean): void {
1013    for(let task of this.tasks){
1014      task.isFinish = ifFinish;
1015    }
1016  }
1017
1018  addTask(newTask: TaskViewModel): void {
1019    this.tasks.push(newTask);
1020  }
1021
1022  removeTask(removedTask: TaskViewModel): void {
1023    this.tasks.splice(this.tasks.indexOf(removedTask), 1)
1024  }
1025}
1026```
1027
1028### View层
1029
1030View层负责应用程序的UI展示和与用户的交互。它只关注如何渲染用户界面和展示数据,不包含业务逻辑。所有的数据状态和逻辑都来自ViewModel层,View层通过接收ViewModel传递的状态数据进行渲染,确保视图和数据分离。
1031
1032- TitleView:负责展示应用的标题和未完成任务的统计信息。
1033
1034```ts
1035// src/main/ets/view/TitleView.ets
1036
1037@ComponentV2
1038export default struct TitleView {
1039  @Param tasksUnfinished: number = 0;
1040
1041  build() {
1042    Column() {
1043      Text('待办')
1044        .fontSize(40)
1045        .margin(10)
1046      Text(`未完成任务:${this.tasksUnfinished}`)
1047        .margin({ left: 10, bottom: 10 })
1048    }
1049  }
1050}
1051```
1052
1053- ListView:负责展示任务列表,并根据Setting中的设置筛选是否显示已完成的任务。它依赖于TaskListViewModel来获取任务数据,并通过TaskItem组件进行渲染,包括任务的名称、完成状态以及删除按钮。通过TaskViewModel和TaskListViewModel实现用户的交互,如切换任务完成状态和删除任务。
1054
1055```ts
1056// src/main/ets/view/ListView.ets
1057
1058import TaskViewModel from '../viewmodel/TaskViewModel';
1059import TaskListViewModel from '../viewmodel/TaskListViewModel';
1060import { Setting } from '../pages/SettingPage';
1061import { ActionButton } from './BottomView';
1062
1063@ComponentV2
1064struct TaskItem {
1065  @Param task: TaskViewModel = new TaskViewModel();
1066  @Event deleteTask: () => void = () => {};
1067  @Monitor('task.isFinish')
1068  onTaskFinished(mon: IMonitor) {
1069    console.log('任务' + this.task.taskName + '的完成状态从' + mon.value()?.before + '变为了' + mon.value()?.now);
1070  }
1071
1072  build() {
1073    Row() {
1074      // 请开发者自行在src/main/resources/base/media路径下添加finished.pngunfinished.png两张图片,否则运行时会因资源缺失而报错
1075      Image(this.task.isFinish ? $r('app.media.finished') : $r('app.media.unfinished'))
1076        .width(28)
1077        .height(28)
1078        .margin({ left: 15, right: 10 })
1079      Text(this.task.taskName)
1080        .decoration({ type: this.task.isFinish ? TextDecorationType.LineThrough : TextDecorationType.None })
1081        .fontSize(18)
1082      ActionButton('删除', () => this.deleteTask());
1083    }
1084    .height('7%')
1085    .width('90%')
1086    .backgroundColor('#90f1f3f5')
1087    .borderRadius(25)
1088    .onClick(() => this.task.updateIsFinish())
1089  }
1090}
1091
1092@ComponentV2
1093export default struct ListView {
1094  @Param taskList: TaskListViewModel = new TaskListViewModel();
1095  @Param setting: Setting = new Setting();
1096
1097  build() {
1098    Repeat<TaskViewModel>(this.taskList.tasks.filter(task => this.setting.showCompletedTask || !task.isFinish))
1099      .each((obj: RepeatItem<TaskViewModel>) => {
1100        TaskItem({
1101          task: obj.item,
1102          deleteTask: () => this.taskList.removeTask(obj.item)
1103        }).margin(5)
1104      })
1105  }
1106}
1107```
1108
1109- BottomView:负责提供与任务操作相关的按钮和输入框,如"全部完成"、"全部未完成","设置"三个按钮,以及添加新任务的输入框。点击"全部完成"和"全部未完成"时,通过TaskListViewModel更改所有任务的状态。点击"设置"按钮时,会导航到SettingAbility的设置页面。添加新任务时,通过TaskListViewModel新增任务到任务列表中。
1110
1111```ts
1112// src/main/ets/view/BottomView.ets
1113
1114import { common, Want } from '@kit.AbilityKit';
1115import TaskViewModel from '../viewmodel/TaskViewModel';
1116import TaskListViewModel from '../viewmodel/TaskListViewModel';
1117
1118@Builder export function ActionButton(text: string, onClick:() => void) {
1119  Button(text, { buttonStyle: ButtonStyleMode.NORMAL })
1120    .onClick(onClick)
1121    .margin({ left: 10, right: 10, top: 5, bottom: 5 })
1122}
1123
1124@ComponentV2
1125export default struct BottomView {
1126  @Param taskList: TaskListViewModel = new TaskListViewModel();
1127  @Local newTaskName: string = '';
1128  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1129
1130  build() {
1131    Column() {
1132      Row() {
1133        ActionButton('全部完成', (): void => this.taskList.finishAll(true))
1134        ActionButton('全部未完成', (): void => this.taskList.finishAll(false))
1135        ActionButton('设置', (): void => {
1136          let wantInfo: Want = {
1137            deviceId: '', // deviceId为空表示本设备
1138            bundleName: 'com.samples.statemgmtv2mvvm', // 替换成AppScope/app.json5里的bundleName
1139            abilityName: 'SettingAbility',
1140          };
1141          this.context.startAbility(wantInfo);
1142        })
1143      }
1144      .margin({ top: 10, bottom: 5 })
1145      Row() {
1146        TextInput({ placeholder: '添加新任务', text: this.newTaskName })
1147          .onChange((value) => this.newTaskName = value)
1148          .width('70%')
1149        ActionButton('+', (): void => {
1150          let newTask = new TaskViewModel();
1151          newTask.taskName = this.newTaskName;
1152          this.taskList.addTask(newTask);
1153          this.newTaskName = '';
1154        })
1155      }
1156    }
1157  }
1158}
1159```
1160
1161- TodoListPage:todolist的主页面,包含以上的三个View组件(TitleView、ListView、BottomView),用于统一展示待办事项的各个部分,管理任务列表和用户设置。TodoListPage负责从ViewModel中获取数据,并将数据传递给各个子View组件进行渲染,通过PersistenceV2持久化任务数据,确保数据在应用重启后仍能保持一致。
1162
1163```ts
1164// src/main/ets/pages/TodoListPage.ets
1165
1166import TaskListViewModel from '../viewmodel/TaskListViewModel';
1167import { common } from '@kit.AbilityKit';
1168import { AppStorageV2, PersistenceV2 } from '@kit.ArkUI';
1169import { Setting } from '../pages/SettingPage';
1170import TitleView from '../view/TitleView';
1171import ListView from '../view/ListView';
1172import BottomView from '../view/BottomView';
1173
1174@Entry
1175@ComponentV2
1176struct TodoList {
1177  @Local taskList: TaskListViewModel = PersistenceV2.connect(TaskListViewModel, 'TaskList', () => new TaskListViewModel())!;
1178  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
1179  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1180
1181  async aboutToAppear() {
1182    this.taskList = PersistenceV2.connect(TaskListViewModel, 'TaskList', () => new TaskListViewModel())!;
1183    if (this.taskList.tasks.length === 0) {
1184      await this.taskList.loadTasks(this.context);
1185    }
1186  }
1187
1188  @Computed
1189  get tasksUnfinished(): number {
1190    return this.taskList.tasks.filter(task => !task.isFinish).length;
1191  }
1192
1193  build() {
1194    Column() {
1195      TitleView({ tasksUnfinished: this.tasksUnfinished })
1196      ListView({ taskList: this.taskList, setting: this.setting });
1197      BottomView({ taskList: this.taskList });
1198    }
1199    .height('100%')
1200    .width('100%')
1201    .alignItems(HorizontalAlign.Start)
1202    .margin({ left: 15 })
1203  }
1204}
1205```
1206
1207- SettingPage:设置页面,负责管理是否显示已完成任务的设置。通过AppStorageV2应用全局存储用户的设置,用户通过Toggle开关切换showCompletedTask状态。
1208
1209```ts
1210// src/main/ets/pages/SettingPage.ets
1211
1212import { AppStorageV2 } from '@kit.ArkUI';
1213import { common } from '@kit.AbilityKit';
1214
1215@ObservedV2
1216export class Setting {
1217  @Trace showCompletedTask: boolean = true;
1218}
1219
1220@Entry
1221@ComponentV2
1222struct SettingPage {
1223  @Local setting: Setting = AppStorageV2.connect(Setting, 'Setting', () => new Setting())!;
1224  private context = this.getUIContext().getHostContext() as common.UIAbilityContext;
1225
1226  build(){
1227    Column(){
1228      Text('设置')
1229        .fontSize(40)
1230        .margin({ bottom: 10 })
1231      Row() {
1232        Text('显示已完成任务');
1233        Toggle({ type: ToggleType.Switch, isOn:this.setting.showCompletedTask })
1234          .onChange((isOn) => {
1235            this.setting.showCompletedTask = isOn;
1236          })
1237      }
1238      Button('返回待办')
1239        .onClick(()=>this.context.terminateSelf())
1240        .margin({ top: 10 })
1241    }
1242    .alignItems(HorizontalAlign.Start)
1243  }
1244}
1245```
1246
1247## 总结
1248
1249本教程通过一个简单的待办事项应用示例,逐步引入了状态管理V2装饰器,并通过代码重构实现了MVVM架构。最终,将数据、逻辑和视图分层,使得代码结构更加清晰、易于维护。合理地使用Model、View和ViewModel,可以帮助开发者实现高效的数据与UI同步,简化开发流程并降低复杂性。希望通过这个示例,开发者能够更好地理解MVVM模式,并能将其灵活应用到自己项目的开发中,从而提高开发效率和代码质量。
1250
1251## 代码示例
1252[完整源码](https://gitee.com/openharmony/applications_app_samples/tree/master/code/DocsSample/ArkUISample/StateMgmtV2MVVM/entry)
1253