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