vue-typescript-admin-template中的架构设计模式:MVVM与Clean Architecture
引言:前端架构的痛点与解决方案
在现代前端开发中,随着应用规模的扩大和复杂度的提升,架构设计变得越来越重要。你是否还在为以下问题困扰?代码耦合严重,修改一个功能牵一发而动全身;业务逻辑与UI操作混杂,难以维护;测试困难,组件与数据层纠缠不清。本文将深入剖析vue-typescript-admin-template项目如何融合MVVM(Model-View-ViewModel)模式与Clean Architecture架构思想,为你提供一套清晰、可维护、可扩展的前端架构解决方案。
读完本文,你将能够:
- 理解MVVM模式在Vue项目中的具体实现
- 掌握Clean Architecture在前端的应用方法
- 学会如何设计松耦合、高内聚的前端架构
- 提升代码的可维护性和可测试性
项目架构概览
vue-typescript-admin-template是一个基于Vue CLI 3.0和TypeScript的最小化管理系统模板。项目采用了MVVM架构模式,并融入了Clean Architecture的思想,形成了清晰的分层结构。
整体架构图
目录结构解析
项目的目录结构如下:
src/
├── api/ # API调用(基础设施层)
├── assets/ # 静态资源
├── components/ # 通用组件(表现层)
├── directives/ # 自定义指令
├── filters/ # 过滤器
├── icons/ # 图标
├── lang/ # 国际化
├── layout/ # 布局组件
├── router/ # 路由配置(应用层)
├── store/ # Vuex状态管理(应用层)
├── styles/ # 样式
├── utils/ # 工具函数(基础设施层)
├── views/ # 页面组件(表现层)
├── App.vue # 根组件
├── main.ts # 入口文件
└── permission.ts # 权限控制
MVVM模式在项目中的实现
MVVM(Model-View-ViewModel)是一种软件架构模式,它将应用程序分为三个主要部分:模型(Model)、视图(View)和视图模型(ViewModel)。
MVVM架构图
View层:Vue组件
在本项目中,View层由Vue组件构成,包括页面组件(views/)和通用组件(components/)。
以Dashboard页面为例:
<template>
<div class="dashboard-container">
<component :is="currentRole" />
</div>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import { UserModule } from '@/store/modules/user'
import AdminDashboard from './admin/index.vue'
import EditorDashboard from './editor/index.vue'
@Component({
name: 'Dashboard',
components: {
AdminDashboard,
EditorDashboard
}
})
export default class extends Vue {
private currentRole = 'admin-dashboard'
get roles() {
return UserModule.roles
}
created() {
if (!this.roles.includes('admin')) {
this.currentRole = 'editor-dashboard'
}
}
}
</script>
这个组件根据用户角色动态渲染不同的仪表盘组件,体现了View层的职责:展示数据和处理用户交互。
ViewModel层:Vuex Store
Vuex Store充当了ViewModel的角色,它连接View和Model,负责数据的管理和业务逻辑的处理。
store/index.ts:
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'
// ...其他导入
Vue.use(Vuex)
export interface IRootState {
app: IAppState
user: IUserState
// ...其他状态
}
export default new Vuex.Store<IRootState>({})
以用户模块为例,展示ViewModel如何处理业务逻辑:
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public token = getToken() || ''
public name = ''
public avatar = ''
public roles: string[] = []
@Mutation
private SET_TOKEN(token: string) {
this.token = token
}
@Action
public async Login(userInfo: { username: string, password: string}) {
let { username, password } = userInfo
username = username.trim()
const { data } = await login({ username, password })
setToken(data.accessToken)
this.SET_TOKEN(data.accessToken)
}
@Action
public async GetUserInfo() {
if (this.token === '') {
throw Error('GetUserInfo: token is undefined!')
}
const { data } = await getUserInfo({ /* 参数 */ })
const { roles, name, avatar } = data.user
this.SET_ROLES(roles)
this.SET_NAME(name)
this.SET_AVATAR(avatar)
}
// ...其他Action和Mutation
}
Model层:数据与业务逻辑
Model层包括两部分:
- 服务端返回的数据模型
- 封装的业务逻辑
在本项目中,Model层的实现较为分散,主要体现在:
- API调用(api/目录)
- 工具函数(utils/目录)
- Vuex模块中的业务逻辑
以用户API为例:
// api/users.ts
import request from '@/utils/request'
export const getUsers = (params: any) =>
request({
url: '/users',
method: 'get',
params
})
export const login = (data: any) =>
request({
url: '/users/login',
method: 'post',
data
})
// ...其他API
数据绑定:Vue的响应式系统
Vue的响应式系统实现了View和ViewModel之间的双向绑定。当ViewModel中的数据发生变化时,View会自动更新;反之,用户在View上的操作也会同步到ViewModel中。
以AdminDashboard组件为例:
<template>
<div class="dashboard-editor-container">
<line-chart :chart-data="lineChartData" />
<panel-group @handle-set-line-chart-data="handleSetLineChartData" />
</div>
</template>
<script lang="ts">
// ...导入
@Component({
components: { LineChart, PanelGroup }
})
export default class extends Vue {
private lineChartData = lineChartData.newVisitis
private handleSetLineChartData(type: string) {
this.lineChartData = lineChartData[type]
}
}
</script>
当用户点击PanelGroup组件中的按钮时,会触发handleSetLineChartData方法,更新lineChartData,从而自动更新LineChart组件的展示。
Clean Architecture在项目中的体现
Clean Architecture(整洁架构)强调依赖规则:内层不依赖外层,外层依赖内层。架构从内到外分为:实体(Entities)、用例(Use Cases)、接口适配器(Interface Adapters)和外部接口(Frameworks & Drivers)。
Clean Architecture分层图
实体层(Entities)
实体层包含业务实体和核心业务规则。在本项目中,实体层的体现较弱,主要包括:
- TypeScript类型定义
- 核心业务逻辑(如权限控制)
用例层(Use Cases)
用例层包含应用程序的具体业务流程。在本项目中,用例层主要由Vuex的Action实现:
// store/modules/user.ts 中的Action
@Action
public async Login(userInfo: { username: string, password: string}) {
// 登录用例
let { username, password } = userInfo
username = username.trim()
const { data } = await login({ username, password })
setToken(data.accessToken)
this.SET_TOKEN(data.accessToken)
}
@Action
public async LogOut() {
// 登出用例
if (this.token === '') {
throw Error('LogOut: token is undefined!')
}
await logout()
removeToken()
resetRouter()
this.SET_TOKEN('')
this.SET_ROLES([])
}
接口适配器层(Interface Adapters)
接口适配器层负责将内层与外层进行适配。在本项目中,这一层包括:
- Vuex的Mutation(将用例结果适配到状态)
- 组件(将状态适配到视图)
- API调用函数(将外部API适配到用例)
以API请求适配器为例:
// utils/request.ts
import axios from 'axios'
import { Message, MessageBox } from 'element-ui'
import { UserModule } from '@/store/modules/user'
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API,
timeout: 5000
})
// 请求拦截器
service.interceptors.request.use(
(config) => {
if (UserModule.token) {
config.headers['X-Access-Token'] = UserModule.token
}
return config
},
(error) => {
Promise.reject(error)
}
)
// 响应拦截器
service.interceptors.response.use(
(response) => {
const res = response.data
if (res.code !== 20000) {
Message({
message: res.message || 'Error',
type: 'error',
duration: 5 * 1000
})
// 错误处理逻辑
return Promise.reject(new Error(res.message || 'Error'))
} else {
return response.data
}
},
(error) => {
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
外部接口层(Frameworks & Drivers)
外部接口层包括框架、库、UI组件等。本项目使用的外部资源有:
- Vue框架
- Element UI组件库
- Axios网络请求库
- ECharts图表库
MVVM与Clean Architecture的结合
架构映射关系
MVVM和Clean Architecture并不是互斥的,它们可以相互补充:
数据流向
在结合了MVVM和Clean Architecture的项目中,数据流向如下:
以用户登录为例:
实际应用与最佳实践
状态管理最佳实践
- 模块化:按业务领域划分Vuex模块
// store/index.ts
export default new Vuex.Store<IRootState>({})
// store/modules/app.ts
@Module({ dynamic: true, store, name: 'app' })
export class App extends VuexModule implements IAppState {
// ...
}
// store/modules/user.ts
@Module({ dynamic: true, store, name: 'user' })
export class User extends VuexModule implements IUserState {
// ...
}
- 严格类型检查:使用TypeScript定义状态接口
export interface IUserState {
token: string
name: string
avatar: string
introduction: string
roles: string[]
email: string
}
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public token = getToken() || ''
public name = ''
public avatar = ''
public introduction = ''
public roles: string[] = []
public email = ''
// ...
}
组件设计最佳实践
- 容器组件与展示组件分离
容器组件:负责数据获取和状态管理 展示组件:负责UI渲染和用户交互
以Dashboard为例:
views/dashboard/
├── index.vue # 容器组件
├── admin/ # Admin视图
│ ├── index.vue # 容器组件
│ └── components/ # 展示组件
└── editor/ # Editor视图
├── index.vue # 容器组件
└── components/ # 展示组件
- 组件通信
- 父子组件:props和事件
- 跨组件:Vuex或EventBus
- 路由间通信:路由参数或Vuex
权限控制实现
项目中的权限控制是一个很好的用例实现:
// permission.ts
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/cookies'
const whiteList = ['/login', '/auth-redirect']
router.beforeEach(async(to, from, next) => {
NProgress.start()
if (getToken()) {
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else {
if (store.getters.roles.length === 0) {
try {
await store.dispatch('user/GetUserInfo')
const roles = store.getters.roles
store.dispatch('permission/GenerateRoutes', { roles }).then(() => {
router.addRoutes(store.getters.dynamicRoutes)
next({ ...to, replace: true })
})
} catch (err) {
store.dispatch('user/ResetToken').then(() => {
Message.error(err || 'Has Error')
next(`/login?redirect=${to.path}`)
NProgress.done()
})
}
} else {
next()
}
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
next()
} else {
next(`/login?redirect=${to.path}`)
NProgress.done()
}
}
})
router.afterEach(() => {
NProgress.done()
})
项目架构的优缺点分析
优点
- 清晰的职责分离:MVVM模式使UI、业务逻辑和数据模型分离
- 可测试性:Vuex的Action和Mutation易于单元测试
- 松耦合:Clean Architecture的依赖规则减少了组件间的耦合
- 可维护性:模块化和分层设计使代码更易于维护
- TypeScript支持:强类型提高了代码质量和开发效率
缺点
- 学习曲线陡峭:同时理解MVVM和Clean Architecture有一定难度
- 样板代码较多:Vuex的使用需要编写Action、Mutation等
- 状态管理复杂度:对于小型应用,Vuex可能显得过重
- Model层弱化:业务逻辑分散在Vuex中,缺乏集中的领域模型
改进建议
- 引入领域模型:将分散的业务逻辑封装到专门的领域模型中
- 使用Composition API:Vue 3的Composition API可以更好地组织代码
- 引入数据仓库模式:统一管理数据获取和缓存
- 加强TypeScript类型定义:为API响应和业务实体创建更完善的类型
总结与展望
vue-typescript-admin-template项目融合了MVVM和Clean Architecture的思想,构建了一个结构清晰、可维护的前端架构。通过Vuex实现的ViewModel层有效地连接了View和Model,同时遵循了Clean Architecture的依赖规则。
随着前端技术的发展,未来可以考虑:
- 微前端架构:将大型应用拆分为更小的、自治的前端应用
- 状态管理方案演进:探索Pinia等新一代状态管理库
- 更好的领域驱动设计:引入更完善的领域模型和用例设计
- 服务端渲染/静态生成:提升性能和SEO
通过不断优化架构,我们可以构建出更健壮、更可维护的前端应用,为用户提供更好的体验。
参考资源
- Vue官方文档: https://vuejs.org/
- Vuex官方文档: https://vuex.vuejs.org/
- Clean Architecture by Robert C. Martin
- Vue-TypeScript-Admin-Template项目源码
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



