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