【鸿蒙学习笔记】MVVM模式

news/2024/10/5 16:31:51/

官方文档:MVVM模式

[Q&A] 什么是MVVM

ArkUI采取MVVM = Model + View + ViewModel模式。

  1. Model层:存储数据和相关逻辑的模型。
  2. View层:在ArkUI中通常是@Component装饰组件渲染的UI。
  3. ViewModel层:在ArkUI中,ViewModel是存储在自定义组件的状态变量LocalStorageAppStorage中的数据
    在这里插入图片描述

MVVM应用示例

开发一个电话簿应用,实现功能如下:

  1. 显示联系人和设备(“Me”)电话号码 。
  2. 选中联系人时,进入可编辑态“Edit”,可以更新该联系人详细信息,包括电话号码,住址。
  3. 在更新联系人信息时,只有在单击保存“Save Changes”之后,才会保存更改。
  4. 可以点击删除联系人“Delete Contact”,可以在联系人列表删除该联系人。

Model

Address,Person,AddressBook,MyArray

ViewModel

PersonView,phonesNumber,PersonEditView,AddressBookView

Vidw

PracExample

// ViewModel classes
let nextId = 0;@Observed
export class MyArray<T> extends Array<T> {constructor(args: T[]) {console.log(`MyArray: ${JSON.stringify(args)} `)if (args instanceof Array) {super(...args);} else {super(args)}}
}@Observed
export class Address {street: string;zip: number;city: string;constructor(street: string,zip: number,city: string) {this.street = street;this.zip = zip;this.city = city;}
}@Observed
export class Person {id_: string;name: string;address: Address;phones: MyArray<string>;constructor(name: string,street: string,zip: number,city: string,phones: string[]) {this.id_ = `${nextId}`;nextId++;this.name = name;this.address = new Address(street, zip, city);this.phones = new MyArray<string>(phones);}
}export class AddressBook {me: Person;friends: MyArray<Person>;constructor(me: Person, contacts: Person[]) {this.me = me;this.friends = new MyArray<Person>(contacts);}
}// 渲染出Person对象的名称和Observed数组<string>中的第一个号码
// 为了更新电话号码,这里需要@ObjectLink person和@ObjectLink phones,
// 不能使用this.person.phones,内部数组的更改不会被观察到。
// 在AddressBookView、PersonEditView中的onClick更新selectedPerson
@Component
struct PersonView {@ObjectLink person: Person;@ObjectLink phones: MyArray<string>;@Link selectedPerson: Person;build() {Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text(this.person.name)if (this.phones.length) {Text(this.phones[0])}}.height(55).backgroundColor(this.selectedPerson.name == this.person.name ? Color.Pink : "#ffffff").onClick(() => {this.selectedPerson = this.person;})}
}@Component
struct phonesNumber {@ObjectLink phoneNumber: MyArray<string>build() {Column() {ForEach(this.phoneNumber,(phone: ResourceStr, index?: number) => {TextInput({ text: phone }).width(150).onChange((value) => {console.log(`${index}. ${value} value has changed`)this.phoneNumber[index!] = value;})},(phone: ResourceStr, index: number) => `${this.phoneNumber[index] + index}`)}}
}// 渲染Person的详细信息
// @Prop装饰的变量从父组件AddressBookView深拷贝数据,将变化保留在本地, TextInput的变化只会在本地副本上进行修改。
// 点击 "Save Changes" 会将所有数据的复制通过@Prop到@Link, 同步到其他组件
@Component
struct PersonEditView {@Consume addrBook: AddressBook;/* 指向父组件selectedPerson的引用 */@Link selectedPerson: Person;/*在本地副本上编辑,直到点击保存*/@Prop name: string = "";@Prop address: Address = new Address("", 0, "");@Prop phones: MyArray<string> = [];selectedPersonIndex(): number {return this.addrBook.friends.findIndex((person: Person) => person.id_ == this.selectedPerson.id_);}build() {Column() {TextInput({ text: this.name }).onChange((value) => {this.name = value;})TextInput({ text: this.address.street }).onChange((value) => {this.address.street = value;})TextInput({ text: this.address.city }).onChange((value) => {this.address.city = value;})TextInput({ text: this.address.zip.toString() }).onChange((value) => {const result = Number.parseInt(value);this.address.zip = Number.isNaN(result) ? 0 : result;})if (this.phones.length > 0) {phonesNumber({ phoneNumber: this.phones })}Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {Text("Save Changes").onClick(() => {// 将本地副本更新的值赋值给指向父组件selectedPerson的引用// 避免创建新对象,在现有属性上进行修改this.selectedPerson.name = this.name;this.selectedPerson.address = new Address(this.address.street, this.address.zip, this.address.city)this.phones.forEach((phone: string, index: number) => {this.selectedPerson.phones[index] = phone});})if (this.selectedPersonIndex() != -1) {Text("Delete Contact").onClick(() => {let index = this.selectedPersonIndex();console.log(`delete contact at index ${index}`);// 删除当前联系人this.addrBook.friends.splice(index, 1);// 删除当前selectedPerson,选中态前移一位index = (index < this.addrBook.friends.length) ? index : index - 1;// 如果contract被删除完,则设置me为选中态this.selectedPerson = (index >= 0) ? this.addrBook.friends[index] : this.addrBook.me;})}}}}
}@Component
struct AddressBookView {@ObjectLink me: Person;@ObjectLink contacts: MyArray<Person>;@State selectedPerson: Person = new Person("", "", 0, "", []);aboutToAppear() {this.selectedPerson = this.me;}build() {Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.Start }) {Text("Me:")PersonView({person: this.me,phones: this.me.phones,selectedPerson: this.selectedPerson})Divider().height(8)ForEach(this.contacts, (contact: Person) => {PersonView({person: contact,phones: contact.phones as MyArray<string>,selectedPerson: this.selectedPerson})}, (contact: Person): string => {return contact.id_;})Divider().height(8)Text("Edit:")PersonEditView({selectedPerson: this.selectedPerson,name: this.selectedPerson.name,address: this.selectedPerson.address,phones: this.selectedPerson.phones})}.borderStyle(BorderStyle.Solid).borderWidth(5).borderColor(0xAFEEEE).borderRadius(5)}
}@Entry
@Component
struct PracExample {@Provide addrBook: AddressBook = new AddressBook(new Person("卧龙", "南路 9", 180, "大连", ["18*********", "18*********", "18*********"]),[new Person("小华", "东路 9", 180, "大连", ["11*********", "12*********"]),new Person("小刚", "西边路 9", 180, "大连", ["13*********", "14*********"]),new Person("小红", "南方街 9", 180, "大连", ["15*********", "168*********"]),]);build() {Column() {AddressBookView({me: this.addrBook.me,contacts: this.addrBook.friends,selectedPerson: this.addrBook.me})}}
}

在这里插入图片描述


http://www.ppmy.cn/news/1473994.html

相关文章

swiftui中封装一个carditem视图,结合toolbar实现滚动的瀑布流,仿小红书首页

实现的效果如上图所示&#xff0c;支持左右滑动切换页面&#xff0c;也支持点击顶部的toolbar菜单切换页面&#xff0c;每个页面里面的每一项都是一个carditem.swift&#xff0c;这是我封装的一个card组件&#xff0c;用于展示每一个card内容&#xff0c;carditem.swift内容如下…

React+TS前台项目实战(二十五)-- 全局常用排序组件SortButton封装

文章目录 前言SortButton组件1. 功能分析2. 代码详细注释3. 使用到的全局hook代码4. 使用方式5. 效果展示 总结 前言 今天要封装的SortButton 组件&#xff0c;主要用在表格列排序上&#xff0c;运用于更新路由并跳转更新&#xff0c;起到刷新页面仍然处于当前排序数据。 Sor…

SpringBoot各类数量限制及超出后抛出的异常

前言 在使用SpringBoot开发接口时&#xff0c;动不动的就发生各种超过默认值的限制&#xff0c;这里总结了下SpringBoot默认限制的设置以及可能会发生的异常&#xff0c;便于问题的排查和快速修改默认值。 配置项配置项说明默认值超过大小后抛出的异常spring.servlet.multipa…

Spring Boot中的数据校验

Spring Boot中的数据校验 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨Spring Boot中的数据校验&#xff0c;了解如何在应用程序中有效地验…

rabbitmq+nginx负载服务部署文档

前言 rabbitmq普通集群部署后&#xff0c;存在服务单点承压的情况&#xff0c;故&#xff0c;需要通过前端负载解决单点承压的问题&#xff1b;将采用nginx作为负载器&#xff0c;对流量进行负载分发到各个集群节点&#xff0c;解决服务单点负载的问题环境 虚拟机4台,如下列表…

【C++】stack和queue的模拟实现 双端队列deque的介绍

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 &#x1f308;前言&#x1f525;stack的模拟实现&#x1f525;queue的模拟实现&#x1f525;deque&#xff08;双端队列&#xff09;deque的缺陷 &#x1f308;为什么选择…

uniapp/Android App上架三星市场需要下载所需要的SDK

只需添加以下一个权限在AndroidManifest.xml <uses-permission android:name"com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY"/>uniapp开发的&#xff0c;需要在App权限配置中加入以上的额外权限&#xff1a;

PyQT: 开发一款ROI绘制小程序

在一些基于图像或者视频流的应用中&#xff0c;比如电子围栏/客流统计等&#xff0c;我们需要手动绘制一些感兴趣&#xff08;Region of Interest&#xff0c;简称ROI&#xff09;区域。 在这里&#xff0c;我们基于Python和PyQt5框架开发了一款桌面应用程序&#xff0c;允许用…