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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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.png和unfinished.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 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.png和unfinished.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