引言
在前端开发的旅程中,我完整参与了一个基于 Vue2 + Element UI 技术栈的大型管理系统项目。项目规模较大,同时启动了四个前端工程,并分配给不同开发人员独立负责。然而,由于项目工期紧张,团队在开发过程中省略了一些关键的开发流程,例如代码规范、代码审查(Code Review)以及文件目录结构规范等。这种做法虽然在短期内加快了开发进度,但为后续的维护带来了巨大的挑战。
当项目上线后,我接手了后续的迭代和维护工作。面对四个前端工程,我深感头痛。每个项目都有独特的编码风格、文件组织结构,甚至组件拆分的颗粒度也各不相同。这种混乱的状况让我深刻体会到,缺乏统一规范的开发流程会给项目带来多么严重的后果。
这段经历让我意识到,良好的开发规范不仅是代码质量的保障,更是项目长期稳定运行的关键。因此,我将通过本文分享我的经验和教训,希望能为正在使用 Vue 技术栈的小伙伴们提供一些参考。
Vue 风格指南
Vue 官方提供的风格指南是开发 Vue 项目时的重要参考。它能帮助我们避免常见的错误和反模式,提升代码的可读性和一致性。对于正在使用 Vue 技术栈的小伙伴们,我强烈建议大家花时间阅读一下 Vue2 风格指南,建议团队在遵循官方指南的基础上,结合自身经验和技术背景,制定适合自己团队的代码规范。
常见 vue 开发问题
v-for没有使用key或者key使用index做值- 编写
vue文件,不习惯声明name属性或者name是一个单词
Views 目录结构
在前端业务开发中,尤其是管理系统项目,views 文件夹是核心区域,它承载着具体业务功能的实现。与前端脚手架、工程化和基础设施建设这些底层工作不同,views 目录结构直接关系到业务逻辑的组织和代码的可维护性。因此,专注于优化 views 文件夹的结构,对于提升开发效率和代码质量至关重要。
目录结构设计原则:
- 模块化:每个业务模块(如用户管理、订单管理)独立成文件夹,或根据具体的路由划分文件夹,便于管理和扩展。
- 层次清晰:通过合理的层级结构,减少嵌套深度,便于快速定位代码。
- 通用性与专用性分离:将通用组件和工具函数放在
components和utils文件夹中,而业务专用组件放在模块内部。
个人的实践总结
1. 主文件作为模块核心入口
每个业务模块必须有一个主文件(如 index.vue),作为该模块的核心入口。主文件应包含完整的布局和主要业务流程逻辑,确保模块的独立性和完整性。
主文件负责组织页面的整体结构,如布局、路由嵌套等,同时集中管理模块的核心逻辑。
2. 弹窗组件独立化
对于业务模块中需要弹窗展示的功能,单独拆分出一个独立的弹窗组件(如 XXXModal.vue)。这种方式不仅提高了代码的复用性,还便于对弹窗逻辑进行集中管理。项目中,我基于 el-dialog 封装了函数式调用的 dialog 组件,使用函数式 dialog 让业务逻辑保持连贯性。
项目中我已经基于
el-dialog封装了函数式调用的dialog组件,使用函数式dialog让业务逻辑保持连贯性
标签式 el-dialog(传统方式)
- 如果页面有多个弹窗,导致代码中出现多个
<el-dialog>标签和多个变量来控制它们的显示与隐藏。这种做法不仅会使代码冗长,还容易导致维护困难- 在使用标签式弹窗时,弹窗的显示逻辑通常通过改变变量(如
visible)来控制,而关闭逻辑则需要单独编写一个关闭事件处理函数。这种做法会导致显示逻辑和关闭逻辑分散在不同位置。随着项目复杂度的增加,这种分割的逻辑会使代码难以理解和维护,后期维护成本大幅上升
// 使用标签 el-dialog
<el-dialog :visible.sync="visible" @close=handleClose"">
<formlModal></formlModal>
</el-dialog>
// 对于添加一个用户
handleAddUser(){
this.visible = true;
},
handleClose(){
this.visible = false;
// TODO
}
函数式 el-dialog
代码简洁:避免了多个
<el-dialog>标签的重复。
动态管理:通过函数式调用,灵活控制弹窗的显示和隐藏
逻辑集中: 弹窗关闭处理都可以在一个方法里集中处理
函数式弹窗组件不仅减少了模板代码的冗余,还通过异步加载组件提升了性能。
// 使用函数式 el-dialog
// 对于添加一个用户
handleAddUser(){
this.$dialog({
dialogProps:{},
component: ()=>import("./fromModal.vue"),
componentProps:{},
async close(type, self, done){
if(type==="confirm"){
const res = await self.addUser();
res && done();
}else{
done()
}
}
})
},
3. 常量文件集中管理
为每个业务模块创建一个 constants.js 文件,用于提取模块中的常量(如表头、枚举值等)。这种方式不仅让代码更加清晰,还便于后续的修改和维护。例如:
src/views/user/constants.js
src/views/order/constants.js
示例:
// 用户管理模块常量
export const userTableHeaders = [
{ prop: "id", label: "ID", width: "60" },
{ prop: "name", label: "姓名", width: "120" },
{ prop: "status", label: "状态", width: "100", formatter: (row) => row.status === 1 ? "启用" : "禁用" }
];
通过集中管理常量,可以在多个组件中复用,减少重复代码。
4. 具有清晰结构的 views 目录
以下是一个我推荐的 views 目录结构示例:
src/
├── layout/ # 布局(可以有多个布局页面)
│ ├── layout1.vue # 布局一(侧边栏固定,右侧 router-view)
│ └── layout2.vue # 布局二(只有 router-view,用于二三级路由的父级 components)
├── views/
│ ├── common/ # 部分业务通用代码(两三个业务共用组件或业务逻辑代码[为了保持业务逻辑独立性,我是不推荐有这层,有可能是前期类似,后续迭代升级万一某一个大改,成本和风险就很高,或者是否可以提取成通用组件跟业务逻辑无关])
│ ├── dashboard/ # 首页或仪表盘
│ │ ├── index.vue # 主页面
│ │ ├── constants.js # 首页常量文件(固定下拉选,枚举值)
│ │ └── components/ # 首页专用组件
│ ├── user/ # 用户管理模块
│ │ ├── index.vue # 用户列表页面
│ │ ├── formModal.vue # 用户表单页面(新增/编辑)
│ │ ├── detailModal.vue # 用户详情页面
│ │ ├── constants.js # 用户管理常量文件(用户列表表头定义,form 表单的 rules 定义)
│ │ ├── components/ # 用户管理专用组件
│ ├── order/ # 订单管理模块
│ │ ├── index.vue # 订单列表页面
│ │ ├── detailModal.vue # 订单详情页面
│ │ ├── constants.js # 订单管理常量文件
│ │ ├── components/ # 订单管理专用组件
├── constants/ # 全局常量
│ └── index.js # 全局常量定义
├── components/ # 全局通用组件
│ └── dialog-func # 函数式弹窗组件
│ │ ├── dialog.vue # 基础弹窗组件
│ │ └── baseModal.js # 函数式弹窗组件
├── utils/ # 业务逻辑工具函数
│ └── fileUtils.js # 文件操作相关工具函数
目录结构总结
一个清晰的 views 目录结构不仅能帮助开发人员快速定位问题,还能在多人协作时减少冲突和误解。通过模块化、层次清晰化以及通用性和专用性的分离,可以显著提升代码的可维护性和开发效率。希望这些实践总结能为你提供参考,帮助你在实际项目中更好地组织和管理代码。
组件拆分粒度重要性
对于组件的拆分,有的精细得像乐高零件,有的直接把航母当成组件
航母级组件
将整个业务模块的所有功能集中在一个主文件中,不进行任何细化或抽离。例如,所有的弹窗显示、表单逻辑、数据展示等功能都一股脑地写在主文件中。这种做法虽然在短期内减少了组件的数量,但会导致耦合度极高,维护起来非常困难
乐高零件级组件
与航母级组件相反,有些开发者会将组件拆分得非常细,甚至到了乐高零件的级别。例如,一个业务模块页面采用左右布局,左边显示菜单,右边显示结构化的列表内容。在这种情况下,开发者可能会将 el-menu 封装一层并使用 slot 的结构布局,同时将列表的 v-for 抽离成一个独立组件。最后在业务模块的主入口文件中引用这些组件。这种做法虽然降低了耦合度,但在维护时需要在多个文件间频繁切换,容易让维护者不知道干什么了,且过度拆分还增加了组件间的通信成本,会有很多额外的 emit 事件或 props 属性,未能充分发挥响应式数据的优势。
示例:
<!-- UserManagement.vue -->
<template>
<div>
<SideMenu />
<ContentList />
</div>
</template>
<script>
import SideMenu from "./components/SideMenu.vue";
import ContentList from "./components/ContentList.vue";
export default {
components: { SideMenu, ContentList }
};
</script>
<!-- SideMenu.vue -->
<template>
<el-menu v-for="menu in menus" key="menu.id" :default-active="activeIndex" @select="handleSelect">
<el-menu-item index="1">{{menu.label}}</el-menu-item>
</el-menu>
</template>
<script>
export default {
data() {
return {
activeIndex: "1",
menus: []
};
},
methods: {
handleSelect(index) {
this.activeIndex = index;
}
}
};
</script>
<!-- ContentList.vue -->
<template>
<el-card v-for="user in userData">
{{user.name}}
</el-card>
</template>
<script>
export default {
data() {
return {
userData: []
};
}
};
</script>
组件拆分总结
组件拆分的粒度直接影响代码的可维护性、复用性和开发效率。合理的拆分粒度可以让代码更加清晰、易于理解和扩展,而粒度过细或过粗则可能带来以下问题:
(1)粒度过细的弊端
- 管理复杂:过多的组件会增加项目结构的复杂性,开发人员需要花费更多时间去理解和管理这些组件。
- 性能问题:组件数量过多可能导致渲染性能下降,尤其是在复杂页面中。
- 过度拆分:可能引入不必要的抽象,增加代码的阅读难度。
(2)粒度过粗的弊端
- 复用性差:大型组件难以在不同场景中复用,导致代码重复。
- 维护困难:组件功能过于复杂,修改一处可能影响多个功能点。
- 灵活性低:难以适应需求变化,扩展功能时可能需要重构
在实际开发中,建议根据以下原则平衡组件拆分的粒度:
- 功能独立性:每个组件应该有明确的功能边界,不依赖外部状态。
- 复用性:尽量将通用功能拆分为独立组件,便于复用。
- 可维护性:组件的复杂度应适中,避免过大或过小。
- 团队习惯:与团队成员协商一致,制定统一的拆分规范。
后记
在前端开发的道路上,我深知自己还有很长的路要走。未来的工作中,我将继续深入学习新技术,结合项目需求和团队特点,不断优化开发流程和代码质量。我也会更加注重团队协作,与同事们共同探讨更好的解决方案,提升整个团队的开发效率。
感谢阅读,敬请斧正!