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