写点什么

鸿蒙 Next MVVM 模式使用

作者:auhgnixgnahz
  • 2025-06-23
    北京
  • 本文字数:3251 字

    阅读完需:约 11 分钟

MVVM 模式介绍


在应用开发中,UI 的更新需要随着数据状态的变化进行实时同步,而这种同步往往决定了应用程序的性能和用户体验。为了解决数据与 UI 同步的复杂性,ArkUI 采用了 Model-View-ViewModel(MVVM)架构模式。MVVM 将应用分为 Model、View 和 ViewModel 三个核心部分,实现数据、视图与逻辑的分离。通过这种模式,UI 可以随着状态的变化自动更新,无需手动处理,从而更加高效地管理数据和视图的绑定与更新。


Model:负责存储和管理应用的数据以及业务逻辑,不直接与用户界面交互。


View:负责用户界面展示数据并与用户交互,不包含任何业务逻辑。它通过绑定 ViewModel 层提供的数据来动态更新 UI。


ViewModel:负责管理 UI 状态和交互逻辑。作为连接 Model 和 View 的桥梁,ViewModel 监控 Model 数据的变化,通知 View 更新 UI,同时处理用户交互事件并转换为数据操作。


实现目标:点击列表的点赞,刷新当前 item 的视图,增加点赞信息



先看一个简单例子:



贴一下 ViewModel 的代码:


@ObservedV2export default class StudentViewModel{  @Trace name:string=''}import StudentViewModel from "./StudentViewModel"
@ObservedV2export default class ClassViewModel{ @Trace className:string='' @Trace stus:StudentViewModel[]=[]

addStu(stu:StudentViewModel){ this.stus.push(stu) }}import { SchoolModel } from "../model/SchoolModel";import ClassViewModel from "./ClassViewModel";import StudentViewModel from "./StudentViewModel";import { UIUtils } from "@ohos.arkui.StateManagement";
@ObservedV2export default class SchoolViewModel{ @Trace cls:ClassViewModel[]=[] @Trace name:string=''
// 不推荐 实际开发中不可能直接创建观察者数据,可做测试使用 async load(){ let stu1 = new StudentViewModel() stu1.name='stu1' let stu2 = new StudentViewModel() stu2.name='stu2' let stu3 = new StudentViewModel() stu3.name='stu3' let stu4 = new StudentViewModel() stu4.name='stu4'
let cls1= new ClassViewModel() cls1.className='cls1' cls1.stus.push(stu1) cls1.stus.push(stu2) let cls2= new ClassViewModel() cls2.className='cls2' cls2.stus.push(stu3) cls2.stus.push(stu4)
let sch = new SchoolViewModel() sch.name='sch' sch.cls.push(cls1) sch.cls.push(cls2)
this.cls=[...sch.cls] this.name=sch.name
} async load2(){ let school = new SchoolModel()//获取到的是非可观察的数据结构,例如从网络获取,本地缓存,数据库等数据 await school.load() //将非观察数据变为可观察数据 this.cls = UIUtils.makeObserved< ClassViewModel[]>(school.cls) as ClassViewModel[] this.name = school.name } //增加这个方法,调用的StudentViewModel 的 add方法,同样在StudentModel也需要增加同样的方法,否则上面的UIUtils.makeObserved会报错 addStu(stu:StudentViewModel,index:number){ this.cls[index].addStu(stu) }}
复制代码


// Argument of type 'ClassModel[]' is not assignable to parameter of type 'ClassViewModel[]'.//  Property 'addStu' is missing in type 'ClassModel' but required in type 'ClassViewModel'.
export class ClassModel{ className:string='' stus:StudentModel[]=[] addStu(stu:StudentModel){ this.stus.push(stu) }}
复制代码


其他类比较简单,就是没有装饰器的基本类,然后看一下 View 层的实现,使用 V2 装饰器,监听数据变化,自动刷新 view


@Entry@ComponentV2struct mvvm{ @Local school:SchoolViewModel = new SchoolViewModel() async  aboutToAppear(){   await this.school.load2()  }  build() {    Column(){      Text('学校:'+this.school.name)      cls({sch:this.school})    }.height('100%').justifyContent(FlexAlign.Center)  }}@ComponentV2 struct cls{  @Param @Require sch:SchoolViewModel  @Event add:(stu:StudentViewModel)=>void = (stu:StudentViewModel)=>{}  build() {    Column(){      List(){        ForEach(this.sch.cls,(itm:ClassViewModel,index:number)=>{          ListItem(){            Column(){              Text('班级:'+itm.className).onClick(()=>{                itm.className='修改班级名字'              })              stu({cls:itm})              Button('添加一名学生').onClick(()=>{                let stu = new StudentViewModel()                stu.name='新学生'               this.sch.addStu(stu,index)              })            }          }        })      }    }  }}@ComponentV2export struct stu{  @Param @Require cls:ClassViewModel  build() {    Column(){      Text('学生:')      List(){        ForEach(this.cls.stus,(itm:StudentViewModel)=>{          ListItem(){            Row(){              Text(itm.name)              Button('添加一名学生').onClick(()=>{                let stu = new StudentViewModel()                stu.name='新学生'                this.cls.addStu(stu)              })            }.width('100%').justifyContent(FlexAlign.Center)          }        })      }      Text('一共'+this.cls.stus.length+'名')      Button('清空').onClick(()=>{        this.cls.stus=[]      })    }  }}
复制代码


动态列表点赞主要代码:


model 层的数据获取:


export default class BlogList{  datas:BlogData[] = []  //返回非观察数据  async loadData(context:common.UIAbilityContext){    let getJson = await context.resourceManager.getRawFileContent('动态列表数据.json');    let textDecoderOptions: util.TextDecoderOptions = { ignoreBOM : true };    let textDecoder = util.TextDecoder.create('utf-8',textDecoderOptions);    let result = textDecoder.decodeToString(getJson);    let resultJson : ResponseResult= JSON.parse(result)    let ares :Array<blogdata>= resultJson.data as Array<blogdata>    ares.forEach((tab:BlogData,index:number)=>{      this.datas.push(tab)    })    return this.datas  }}</blogdata></blogdata>
复制代码


viewModel 层数据处理:


@ObservedV2export default class BlogListViewModel{ @Trace datas:BlogDataViewModel[] = []
async loadData(context:common.UIAbilityContext){ //获取model层的数据 let blogList = new BlogList() await blogList.loadData(context)//将非观察数据变为可观察数据 this.datas = UIUtils.makeObserved<array<blogdataviewmodel>>(blogList.datas) as Array<blogdataviewmodel> }//增加点赞数据 add(pra:PraiseViewModel,index:number){ this.datas[index].praiseList=[... this.datas[index].praiseList,pra] }}</blogdataviewmodel></array<blogdataviewmodel>
复制代码


view 层主要代码:


list列表展示: List({ space: 10 }) {    ForEach(this.blogModel.datas,(item:BlogDataViewModel,index:number)=>{      ListItem(){        ItemBlog({ pageInfos: this.pageInfos,blogdata:item,changePraise:(pra:PraiseViewModel)=>{          this.blogModel.add(pra,index)        } })      }    })  }item 点击:Text('点赞'+this.blogdata.praiseList.length).fontColor(Color.White).fontSize(16).onClick(()=>{  let praise =new PraiseViewModel()  praise.userId+='1'+Math.random()  praise.userName='11212'  //@Event的回调事件  this.changePraise(praise)})
复制代码


发布于: 刚刚阅读数: 2
用户头像

auhgnixgnahz

关注

还未添加个人签名 2018-07-10 加入

还未添加个人简介

评论

发布
暂无评论
鸿蒙Next MVVM模式使用_鸿蒙Next_auhgnixgnahz_InfoQ写作社区