Code前端首页关于Code前端联系我们

Vue3的MVVM架构到底有什么不一样?比Vue2好在哪?

terry 7天前 阅读数 980 #Vue

今天聊Vue3绕不开的核心就是MVVM,毕竟是框架的骨架嘛,很多刚从Vue2转过来的同学,或者刚学前端准备碰Vue的新手,可能只听过MVVM是“Model-View-ViewModel”的缩写,但不太清楚它具体在Vue3里是怎么跑的,更搞不懂Vue3这次重构,对MVVM的改动到底带来了多大的好处,别着急,下面咱们拆碎了慢慢说。

什么是真正的MVVM?先把骨架认清楚

别一开始就去背三个单词的定义,那样没用,咱们换个生活化的场景:假设你是开奶茶店的老板。

  • Model(模型) 就是你的奶茶配方库、库存表、顾客点单记录——这些是真实的数据来源,不会因为客人看不见或者杯子里没装就变。
  • View(视图) 就是店里的菜单墙、给客人装的奶茶杯、前台的收银小票——这些是数据给客人看的“面子”,只负责展示或者偶尔收集客人的点单反馈(比如客人说“三分糖”,收银员要记下来,但不能直接改配方库的糖度默认值)。
  • ViewModel(视图模型) 就是你雇的全能店长——客人在菜单墙上指了一杯芋泥啵啵奶绿,全糖去冰(View的操作),店长会核对配方库有没有原材料(Model的数据读取),然后告诉后厨做;客人拿到奶茶喝了一口,给小票上写了“加更多芋泥”的好评建议(View的另一种反馈),店长会整理到“新品调整库”(可能属于Model的扩展);后厨做好了,配方库存剩的芋泥少了半斤(Model的数据更新),店长会立刻让菜单墙旁边的电子屏更新“芋泥余量提醒”,也会让你手机上的库存APP弹通知(View的同步更新)。

懂了吧?MVVM的核心就是Model和View完全分离,所有的交互、数据转换都由ViewModel扛着,而且ViewModel会让Model和View自动同步——不用你老板(开发者)天天盯着后厨改菜单、天天核对库存贴提示。

之前很多前端框架虽然也提MVVM,但要么是Model和View有耦合(比如早期的JQuery插件模式,有时候你得直接去操作DOM也就是View来处理数据),要么是同步做得不够灵活,那Vue2呢?其实Vue2已经把这个全能店长做得不错了,但Vue3这次直接给店长“升级了大脑和四肢”。

Vue3的MVVM比Vue2强在哪?核心看三个升级点

先回忆下Vue2的MVVM是怎么干活的:Model的数据是用Object.defineProperty来“监听”变化的——就像店长原来只在配方库的每一页纸的每一行字旁边贴了个便签,如果有人改了那一行字(比如糖度默认值从全糖改成三分),便签会亮,然后店长去改对应的菜单墙,那这个方式有什么问题?

  • 只能监听已经写在便签旁边的属性——要是你突然在配方库里加了一行“加珍珠免费试吃3颗”的新内容,店长原来没贴便签,根本不知道;
  • 要是你改的是配方库的整本书索引或者库存表的整个分组(比如数组的索引、对象的新增/删除属性),便签亮不了;
  • 便签太多了,店长的脑子(性能)可能不够用——比如你的配方库有1000款奶茶,每款有20个属性,那就得贴20000张便签,Vue2每次组件更新都得把这些便签扫一遍,累不累?

Vue3这次重构,直接把监听方式从Object.defineProperty换成了Proxy(代理),这是第一个、也是最重要的升级。

大脑升级:用Proxy实现更完整、更轻量的响应式系统

还是那个奶茶店的例子,Vue3的全能店长不用再贴便签了,他直接站在配方库和库存表的门口当“代理”——不管是客人、后厨、还是你老板,要动这些数据(读取、修改、新增、删除、甚至查库存有没有空),都得先经过他。

  • 新增属性?没问题!有人递过来新的一页配方,他立刻就能记住,下次有人提的时候马上反应;
  • 修改数组索引、排序、删除?没问题!不管你是换了配方库的第3页,还是把库存表的“牛奶”分组挪到最前面,他都能第一时间知道;
  • 便签太多的问题?解决了!他不用扫所有的便签,只需要记住哪些菜单墙、哪些收银系统、哪些你的手机APP关注了哪些数据——比如只有菜单墙芋泥那一页关注了“芋泥余量”,那后厨用了芋泥,他只通知菜单墙,不用通知别的地方,效率高多了。

除了Proxy,Vue3还配合了Reflect(反射),这个东西就像店长手里的“标准操作手册”——不管是读取还是修改数据,都按照手册来,不会出错,兼容性也更好(Object.defineProperty在某些特殊对象上会有问题,Reflect就不会)。

四肢升级:用Composition API让ViewModel更灵活、更好复用

Vue2的全能店长虽然也能干活,但他的“工作流程”是按奶茶店的“区域”分的——菜单区的工作”“库存区的工作”“收银区的工作”,每个区域的工作都绑在同一个文件夹(Vue组件的options选项)里,要是你想把“核对芋泥余量”的工作同时用到“菜单区”和“后厨备料区”,就得把那个文件夹里的内容复制粘贴过去,或者用Mixin(混入)——但Mixin有个大问题:如果两个Mixin里都有“checkTaros”这个函数,或者都有“taroCount”这个变量,它们会打架,你根本不知道最后生效的是哪个。

Vue3的Composition API(组合式API)就不一样了——它把全能店长的“工作流程”拆成了一个个独立的“任务包”——checkTaros任务包”“updateMenu任务包”“handleOrder任务包”,每个任务包里只有和这个任务相关的变量和函数,要是你想把“checkTaros”用到两个地方,直接把这个任务包拿过去就行,变量和函数都是独立的,不会打架;而且你可以把相关的任务包放在一起,代码看起来更清晰——备料相关的任务包”放在一块,“收银相关的”放在另一块,不像Vue2那样,备料的变量在data里,函数在methods里,监听的逻辑在watch里,分散得乱七八糟。

举个简单的例子,比如Vue2里你要写一个“记录用户输入的搜索关键词,并且当关键词超过3个字时自动搜索奶茶”的功能,代码可能是这样的:

// Vue2 options API
export default {
  data() {
    return {
      searchKeyword: '',
      searchResults: []
    }
  },
  methods: {
    async searchMilktea() {
      // 调用搜索接口
      const res = await fetchMilktea(this.searchKeyword)
      this.searchResults = res.data
    }
  },
  watch: {
    searchKeyword(newVal) {
      if (newVal.length > 3) {
        this.searchMilktea()
      }
    }
  }
}

看起来还行,但要是你有十个组件都需要这个功能呢?复制粘贴十次?太麻烦了,用Vue3的Composition API,你可以把这个功能封装成一个“useSearchMilktea”的任务包:

// Vue3 composable(任务包)
import { ref, watch } from 'vue'
export function useSearchMilktea() {
  const searchKeyword = ref('')
  const searchResults = ref([])
  const searchMilktea = async () => {
    const res = await fetchMilktea(searchKeyword.value)
    searchResults.value = res.data
  }
  watch(searchKeyword, (newVal) => {
    if (newVal.length > 3) {
      searchMilktea()
    }
  })
  return { searchKeyword, searchResults, searchMilktea }
}

然后在任何组件里,只要调用这个任务包就行:

// Vue3 组件中使用
import { useSearchMilktea } from './composables/useSearchMilktea'
export default {
  setup() {
    const { searchKeyword, searchResults, searchMilktea } = useSearchMilktea()
    return { searchKeyword, searchResults, searchMilktea }
  }
}

是不是清晰多了?而且复用起来也方便,再也不用担心Mixin打架了。

整体架构升级:Fragment、Teleport、Suspense让View更自由

说完了Model和ViewModel的升级,再说说View的变化——虽然View在MVVM里只是“面子”,但Vue3这次给了“面子”更多的自由。

  • Fragment(碎片):Vue2里的组件必须有一个根元素——就像菜单墙必须有一个大框框,不能随便贴,Vue3里不需要了,你可以直接贴多个菜单块,不用再套一个没用的div,代码更简洁,DOM结构也更干净;
  • Teleport(传送门):假设你有一个“全局提示框”(奶茶已下单”的弹窗),Vue2里你得把这个提示框放在App.vue的最外层,然后通过props或者Vuex来控制显示隐藏——要是提示框很多,App.vue的代码会很乱,Vue3里的Teleport可以直接把提示框“传送”到你指定的DOM位置(比如body的最后面),但提示框的逻辑还是在原来的组件里,不用套到App.vue里,代码更清晰;
  • Suspense(悬念):假设你有一个“热门奶茶推荐”的组件,需要从后台加载数据——Vue2里你得自己写一个loading的状态,数据加载完了再显示推荐内容,数据加载失败了再显示错误提示,代码很啰嗦,Vue3里的Suspense可以帮你自动处理这些——你只需要把推荐组件放在Suspense的default插槽里,loading放在fallback插槽里,要是数据加载失败了,还可以配合onErrorCaptured钩子处理,代码简洁多了。

Vue3的MVVM到底香不香?

香,太香了——尤其是对大项目来说。

  • 响应式系统更完整、更轻量,大项目的性能会提升很多;
  • Composition API让代码更灵活、更好复用,团队协作也更方便;
  • Fragment、Teleport、Suspense让View的开发更自由,体验也更好。

如果你刚学前端,或者只做一些小项目,可能暂时感觉不到太大的区别,但要是你准备长期做前端,或者准备做大项目,Vue3的MVVM架构绝对是必学的。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

热门