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

萌新.子硕 阅读 40

最近在重构一个 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>
我来解答 赞 8 收藏
二维码
手机扫码查看
2 条解答
W″心虹
MVP 模式在前端框架里用起来确实有点别扭,尤其是 Vue 这种已经自带 MVVM 架构的框架。不过,如果你硬是要用 MVP 来重构项目,那也可以试试看。你遇到的问题主要是因为 Vue 的 data 和 methods 很难完全剥离,这是因为 Vue 组件本身就需要管理一些状态和行为。

在你的例子中,Presenter 应该只负责获取和处理数据,然后将数据传递给 View。但是直接在 created 钩子里赋值给 this.userInfo 并不是一个好方法,因为这样 Vue 的响应式系统可能无法正确追踪到数据的变化。

你可以通过一个中间层来解决这个问题,比如使用一个观察者模式或者简单的回调函数,让 Presenter 在数据更新时通知 View。下面是一个简单的实现思路:

首先,定义你的 Presenter:

class UserPresenter {
constructor(view) {
this.view = view;
}

getUserInfo() {
// 模拟异步请求
setTimeout(() => {
const userInfo = { name: 'John Doe' };
this.view.updateUserInfo(userInfo);
}, 1000);
}
}

export default UserPresenter;


然后在你的 Vue 组件中,你可以这样写:

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

<script>
import UserPresenter from './UserPresenter'

export default {
data() {
return { userInfo: { name: '' } }
},
created() {
this.presenter = new UserPresenter(this);
this.presenter.getUserInfo();
},
methods: {
updateUserInfo(userInfo) {
this.userInfo = userInfo;
}
}
}
</script>


这里的关键在于,我们在 Vue 组件中创建了一个 presenter 实例,并且把 this(即组件实例)传给了 Presenter。这样,Presenter 就可以通过调用 view.updateUserInfo 方法来更新组件的数据。这样做既能保持 MVP 的结构,又能利用 Vue 的响应式特性。希望对你有帮助。
点赞
2026-03-24 09:21
文明 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 更新。
点赞 3
2026-03-02 14:00