【鸿蒙学习笔记】MVVM模式

news2024/11/25 4:35:36

官方文档: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.coloradmin.cn/o/1904189.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

四大常见的排序算法JAVA

1. 冒泡排序 相邻的元素两两比较&#xff0c;大的放右边&#xff0c;小的放左边 第一轮比较完毕之后&#xff0c;最大值就已经确定&#xff0c;第二轮可以少循环一次&#xff0c;后面以此类推 如果数组中有n个数据&#xff0c;总共我们只要执行n-1轮的代码就可以 package Bu…

转盘输入法-键盘加鼠标版本

序 转盘输入法&#xff0c;给你的聊天加点新意。它不用常见的九宫格或全键盘&#xff0c;而是把字母摆在圆盘上&#xff0c;一滑一滑&#xff0c;字就出来了&#xff0c;新鲜又直接。 键盘加鼠标版本GIF演示 演示软件下载 转盘输入法PC演示版本EXE下载https://download.csdn…

一招解决找不到d3dcompiler43.dll,无法继续执行代码问题

当您的电脑遇到d3dcompiler43.dll缺失问题时&#xff0c;首先需要了解d3dcompiler43.dll文件及其可能导致问题的原因&#xff0c;之后便可以选择合适的解决方案。在此&#xff0c;我们将会为您提供寻找d3dcompiler43.dll文件的多种处理方法。 一、d3dcompiler43.dll文件分析 d…

virtualbox安装unbuntu22.04

准备 virtualbox https://www.virtualbox.org/ ubuntu ios https://ubuntu.com/ 安装 等待安装结束即可&#xff0c;输入账号密码登录系统 远程连接发现失败&#xff0c;不过ping 外网可以访问 关闭虚拟机&#xff0c;选择工具&#xff0c;网络查看ip 选择虚拟机&#…

【初中数学选讲】绝对值的几何意义例题(20240503-01)

初中数学选讲&#xff1a;绝对值的几何意义例题&#xff08;20240503-01&#xff09; 1. 练习题目1.1 题目描述1.2 分析 2 答题2.1 定义2.2 分段讨论2.2.1 情况1&#xff1a; x x x点在 a a a点左侧&#xff08; x < a , m ∣ x − a ∣ x<a,\ \ m\left|x-a\right| x<…

数字时代的影像奇迹:专业照片处理软件的创新功能与视觉盛宴

大家好&#xff01;随着时间的流逝&#xff0c;一些珍贵的照片可能会因各种原因而变得模糊不清&#xff0c;但幸运的是&#xff0c;现代科技的发展为我们提供了一种解决方案——专业的照片处理软件。这类软件具备强大的功能&#xff0c;能够将照片高清修复并赋予特效变化&#…

Elasticsearch:Runtime fields - 运行时字段(二)

这是继上一篇文章 “Elasticsearch&#xff1a;Runtime fields - 运行时字段&#xff08;一&#xff09;” 的续篇。 目录 在查询时覆盖字段值 检索运行时字段 定义运行时字段以计算星期几 提取一些数据 搜索计算出的星期几 从相关索引中检索字段 索引运行时字段 使用运…

在门店里造绿色氧吧!康养行业也这么卷了?

拼啥不如拼健康&#xff0c;现在的人算是活明白了&#xff0c;不但中老年人这样想&#xff0c;年轻人也这样干。你可能不知道&#xff0c;现在众多健康养生门店&#xff0c;逐渐成了年轻人“组团养生”的好去处&#xff0c;也是他们吃喝玩乐之外的新兴消费趋势。 而在看得见的…

L04_MySQL知识图谱

这些知识点你都掌握了吗&#xff1f;大家可以对着问题看下自己掌握程度如何&#xff1f;对于没掌握的知识点&#xff0c;大家自行网上搜索&#xff0c;都会有对应答案&#xff0c;本文不做知识点详细说明&#xff0c;只做简要文字或图示引导。 1 基础 1.1内部组件结构 1.2 数据…

自注意力机制和多头注意力机制区别

Ref&#xff1a;小白看得懂的 Transformer (图解) Ref&#xff1a;一文彻底搞懂 Transformer&#xff08;图解手撕&#xff09; 多头注意力机制&#xff08;Multi-Head Attention&#xff09;和自注意力机制&#xff08;Self-Attention&#xff09;是现代深度学习模型&#x…

刷题之买股票的最佳时机(leetcode)

买股票的最佳时机 动态规划入门题。 最简单的模拟式解法&#xff1a; class Solution { public:int maxProfit(vector<int>& prices) {//也可以换一种思路&#xff0c;因为只交易一次&#xff0c;那么找出股票最便宜的时候买入&#xff0c;最贵的时候卖出&#xff…

网页生成二维码、在线演示

https://andi.cn/page/621504.html

Meerkat:第一个统一视听空间和时间定位的MLLM

大型语言模型&#xff08;LLMs&#xff09;在各种自然语言处理任务中表现出色&#xff0c;达到了理解和推理能力的人类水平精度。此外&#xff0c;借助新兴的指令微调范式&#xff0c;这些语言模型可以被赋予遵循开放式自然语言指令的能力&#xff0c;甚至可以与其他模态&#…

基于CentOS Stream 9平台搭建MinIO以及开机自启

1. 官网 https://min.io/download?licenseagpl&platformlinux 1.1 下载二进制包 指定目录下载 cd /opt/coisini/ wget https://dl.min.io/server/minio/release/linux-amd64/minio1.2 文件赋权 chmod x /opt/coisini/minio1.3 创建Minio存储数据目录&#xff1a; mkdi…

并发编程-05AQS原理

并发编程-深入理解AQS之ReentrantLock 一 认识AQS 在讲解AQS原理以及相关同步器之前&#xff0c;我们需要对AQS有一些基本的认识&#xff0c;了解下它有什么样的机制&#xff0c;这样追踪源码的时候就不会太过于迷茫&#xff01; 1.1 什么是AQS java.util.concurrent包中的大…

【Java探索之旅】多态:重写、动静态绑定

文章目录 &#x1f4d1;前言一、重写1.1 概念1.2 方法重写的规则1.3 重写和重载的区别1.4 重写的设计原则 二、动静态绑定2.1 静态绑定&#xff1a;2.2 动态绑定&#xff1a; &#x1f324;️全篇总结 &#x1f4d1;前言 在面向对象编程中&#xff0c;重写和动静态绑定是重要的…

如何利用Github Action实现自动Merge PR

我是蚂蚁背大象(Apache EventMesh PMC&Committer)&#xff0c;文章对你有帮助给项目rocketmq-rust star,关注我GitHub:mxsm&#xff0c;文章有不正确的地方请您斧正,创建ISSUE提交PR~谢谢! Emal:mxsmapache.com 1. 引言 GitHub Actions 是 GitHub 提供的一种强大而灵活的自…

VMware虚拟机搭建CentOS7环境

相关资料 安装VMware 双击VMware-workstation(16.1.1软件安装包.exe安装文件,点下一步 激活码文件复制激活码激活安装linux 1、点击创建虚拟机

python等级考试——一级知识点汇总(turtle画图部分)

&#xff08;本篇文章是针对中国电子学会青少年编程等级考试的&#xff0c;适合初学者以及青少年编程学习者&#xff09; 本篇文章主要介绍turtle画图部分&#xff0c;其他一级考试知识点请移步下方链接&#xff1a;python等级考试——一级知识点汇总&#xff08;不包含turtle…

【JAVA多线程】线程池概论

目录 1.概述 2.ThreadPoolExector 2.1.参数 2.2.新任务提交流程 2.3.拒绝策略 2.4.代码示例 1.概述 线程池的核心&#xff1a; 线程池的实现原理是个标准的生产消费者模型&#xff0c;调用方不停向线程池中写数据&#xff0c;线程池中的线程组不停从队列中取任务。 实现…