【Vue】从 MVC 到 MVVM:前端架构演变与 Vue 的实践之路

个人博客:haichenyi.com。感谢关注

一. 目录

二. 架构模式的演变背景​

  在软件开发中,架构模式是解决代码组织,职责分离和可维护行的核心方案。一个"好"的架构可以少很多不必要的麻烦。这个"好"就很关键,虽然架构模式经历了从MVC——>MVP——>MVVM的演变,但是,不一定后者比前者好。比方说:你一个小项目,MVC就够用了,非要去使用MVP,MVVM,就会多写很多无用代码。要结合多方面去考虑。软件开发的终极目标是高类聚,低耦合 。但是,还是需要结合实际项目去选择。18年MVVM刚兴起的时候,就写过一篇三者的区别的文章
MVC、MVP、MVVM比较。回顾了一下,感觉还是适用的。

三. MVC:经典的分层起点

  1. 核心思想
  • Model:管理数据和业务逻辑
  • View:呈现给用户看的界面
  • Controller:接收用户输入,协调Model和View
  1. 典型流程
    2.1 用户点击按钮触发点击事件,传递到Controller
    2.2 Controller触发Model的事件去拿数据
    2.2 Model数据更新之后,通知Controller更新view
//伪代码如下
let tvContent = document.getElementById("tv_content")
document.getElementById("btn").onclick = function () {
    axios.post().then(res=>{
        let data = res.data
        tvContent.textContent = data
    })
}
//当前页面获取view的某个组件的引用
//用户点击按钮,触发网络请求,拿到数据
//拿到数据之后,更新tvContent的内容
//当前页面就是Controller,网络请求包装的类就是Model,组件就是view
//Controller控制Model,model拿到数据之后控制view刷新界面
//代码少,逻辑简单,看不出来啥问题。挺好用的。但是,代码多,逻辑复杂呢?
//(前面就说了架构要根据实际项目情况来看)
  1. 局限性
  • View和Model直接交互,耦合度高
  • Controller臃肿:业务代码全都写在Controller中

四. MVP:面向接口的解耦尝试​

  1. 核心改进
  • Presenter(协调者):取代Controller,通过接口与view通信
  • View被动化,只负责页面更新,逻辑由Presenter处理
  1. 典型流程
    2.1 View接收用户操作,调用Presenter接口
    2.2 Presenter操作Model处理数据
    2.3 Model返回结果后,Presenter通过View接口更新页面
//伪代码如下,H5没有接口的概念,我都不知道怎么举例子
//这么理解,接口就是定义了一个一个的功能,需要有实现类去实现。
//还是上面的例子:用户点击按钮更新页面
interface IHome {
    update(content)
}

class HomeImpl implements IHome {
    tvContent;
    constructor() {
        this.tvContent = document.getElementById("tv_content")
    }
    update(content: string) {
        this.tvContent.textContent = content
    }
}

class HomePresenter {
    iHome: IHome
    constructor(iHome: IHome) {
        this.iHome = iHome
    }
    getData() {
        Axions.post().then(res=>{
            this.iHome.update(res.data)
        })
    }
}
let homePresenter = new HomePresenter(new HomeImpl())
document.getElementById("btn")?.onclick = function () {
    homePresenter.getData()
}
//上面的IHome接口,就是页面更新的定义,HomeImpl就是这个接口的实现
//如果,页面有很多种显示需要处理,就需要定义多个方法,需要多个实现,以便于后面P层调用
//HomePresenter就是P层,负责数据处理,和使用接口的引用更新页面
//按钮点击,触发P层逻辑。
//这只是最简单的写法,有很多优化点。Android里面用的比较多
  1. 优势与不足
  • 优势:View和Model完全解耦
  • 不足:需要手动维护大量的接口,代码冗余

五. MVVM:数据驱动的终极形态

  1. 核心思想
  • ViewModel:作为view和model的桥梁,通过数据绑定实现自动同步
  • 双向绑定:view的输入自动更新model,model数据变化自动刷新view
  1. 典型流程
    2.1 view通过模板语法(如{{data}}),绑定到ViewModel
    2.2 ViewModel监听到Model变化,并转换为View所需要的数据格式
    2.3 用户操作触发ViewModel的方法,更新Model
//这么举例子呢,其实,万变不离其中,你不做的事情,都是框架帮你做的。
//比方说,Vue的响应式编程,你只用改变数据,页面自动更新。啥都不干,页面怎么可能知道数据遍了,从而自动更新呢?
//实际上就是发布订阅者模式触发更新,那么,是谁发布,谁订阅呢?放到后面第六部分讲
  1. 优势
  • 开发高效​​:减少手动 DOM 操作,聚焦数据逻辑
  • 动态更新​​:适合实时数据展示
  • 缺点:学习成本高,上手慢。原理复杂,遇到问题,不容易定位

六. Vue:MVVM 的现代化实践

先说概念:Vue.js是mvvm模式集大成者,通过响应式系统和申明试模板,简化了数据绑定。并引入组件化解决复杂场景的问题。
6.1 Vue中MVVM的映射关系

MVVM层Vue实现
Modeldata属性
View模板(templtate)和样式(style)
ViewModelVue组件实例(暴露:data,computed,methods)

6.2 双向绑定的原理:Vue 的响应式系统通过以下步骤实现:

  • 数据劫持
    • vue2是使用 Object.defineProperty 监听对象属性。
    • vue3是使用Proxy代理对象,支持深层次监听
// Vue 2 数据劫持示例
function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    get() {
      dep.depend(); // 收集依赖
      return val;
    },
    set(newVal) {
      val = newVal;
      dep.notify(); // 触发更新
    }
  });
}	
//这里插一句题外话,这个响应式的设计模式,就是观察者模式
//数据本身就是被观察者,get方法里面收集依赖,就是收集观察者,set方法里面触发更新,就是通知观察者数据发生了变化
//vuex使用的是发布订阅者模式。
//下一篇文章。就讲讲这两种设计模式吧。
  • 依赖收集
    • 每个组件对应一个 ​​Watcher​​,在渲染时访问数据属性,触发 getter 收集依赖。
  • 分发更新
    • 数据修改时触发 setter,通知所有关联的 Watcher 重新渲染(通过虚拟 DOM 对比更新真实 DOM,Diff算法)。

6.3 示例:数据变化驱动视图更新​

<template>
  <div>
    <!-- 双向绑定:v-model 语法糖 -->
    <input v-model="message" />
    <p>{{ reversedMessage }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return { message: "Hello Vue!" }; // Model
  },
  computed: {
    reversedMessage() { // ViewModel 逻辑
      return this.message.split('').reverse().join('');
    }
  }
}
</script>

Vue 的双向绑定​,解析如下:

HTML 模板

<div id="app">
  <input v-model="message" />
  <p>{{ reversedMessage }}</p>
</div>

​JavaScript 逻辑​

// 模拟 Vue 实例
const data = { message: "Hello" };

// 1. 数据劫持
defineReactive(data, "message", data.message);

// 2. 计算属性(依赖 message)
const computed = {
  reversedMessage: () => data.message.split("").reverse().join("")
};

// 3. 模板渲染 Watcher
new Watcher(() => {
	//归根结底,页面更新的方法,还是这个。我们前面说的mvc,mvp页面更新也是这个方法
	//所以,你以为它很神奇,其实,底层原理都是一样的。主要还是看每个人的思路。
  document.querySelector("p").textContent = computed.reversedMessage();
});

// 4. 双向绑定(输入框 → 数据)
document.querySelector("input").addEventListener("input", (e) => {
  data.message = e.target.value; // 修改数据 → 触发 setter → 更新视图
});

效果​​:

  1. 输入框内容变化时,data.message 更新。
  2. reversedMessage 自动重新计算,p标签内容实时更新。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

海晨忆

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值