Vue 项目里怎么用 MVP 模式组织代码?

萌新.子硕 阅读 3

最近在重构一个 Vue 2 的老项目,想尝试用 MVP(Model-View-Presenter)模式来解耦逻辑,但有点搞不清怎么在 Vue 里合理划分 Presenter 和组件的关系。我试过把业务逻辑抽到单独的 presenter.js 文件里,然后在组件里调用,但感觉数据流变得很乱,响应式也失效了。

比如下面这个简单的用户信息展示组件,我原本希望 Presenter 负责获取和处理数据,View 只负责渲染,但实际写起来发现 Vue 的 data 和 methods 很难完全剥离。是我理解错了 MVP 在 Vue 中的用法吗?

<template>
  <div>{{ userInfo.name }}</div>
</template>

<script>
import UserPresenter from './UserPresenter'

export default {
  data() {
    return { userInfo: {} }
  },
  created() {
    this.userInfo = UserPresenter.getUserInfo()
  }
}
</script>
我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
文明 Dev
你这个问题的核心在于直接把 Presenter 当成普通工具类来用了,UserPresenter.getUserInfo() 这么一调用,拿到的就是个普通对象,跟 Vue 的响应式系统完全脱节,当然就乱了。

MVP 在 Vue 里要玩得优雅,关键在于让 Presenter 持有的状态本身是响应式的,或者让 Presenter 有能力触发 View 的更新。

给你一个更优雅的写法,用 Vue 的响应式能力来驱动 Presenter 层:

// UserPresenter.js
import Vue from 'vue'

class UserPresenter {
constructor() {
// 用一个 Vue 实例来托管响应式数据
this.vm = new Vue({
data: () => ({
userInfo: {},
loading: false,
error: null
})
})
}

// 暴露只读的状态引用
get state() {
return this.vm.$data
}

async fetchUserInfo(userId) {
this.vm.loading = true
this.vm.error = null

try {
// 这里替换成真实的 API 调用
const response = await fetch(/api/user/${userId})
const data = await response.json()
this.vm.userInfo = data
} catch (e) {
this.vm.error = e.message
} finally {
this.vm.loading = false
}
}

updateUserName(name) {
// 业务逻辑也可以放在这里
if (name && name.length > 0) {
this.vm.userInfo = { ...this.vm.userInfo, name }
}
}
}

// 导出单例
export default new UserPresenter()


组件这边就变得很干净了:






这样写有几个好处。数据流很清晰,Presenter 持有状态和业务逻辑,View 只负责渲染和响应用户操作。响应式也正常工作,因为状态本质上是 Vue 实例的 data。测试的时候可以单独测 Presenter,不用挂载组件。

不过说实话,Vue 2 里这么搞有点曲线救国的味道。如果你的项目升级到 Vue 3,用 reactivecomposition API 来实现 MVP 会更自然,或者干脆用 Pinia 这种状态管理库,本质上也是在干类似的事情,只是封装得更完善。

你原来的思路方向是对的,只是响应式这块没打通。Presenter 不应该是个纯函数集合,它得有能力管理状态并通知 View 更新。
点赞
2026-03-02 14:00