前端业务开发的那些事儿:从项目实战中总结的宝贵经验

289 阅读9分钟

引言

在前端开发的旅程中,我完整参与了一个基于 Vue2 + Element UI 技术栈的大型管理系统项目。项目规模较大,同时启动了四个前端工程,并分配给不同开发人员独立负责。然而,由于项目工期紧张,团队在开发过程中省略了一些关键的开发流程,例如代码规范、代码审查(Code Review)以及文件目录结构规范等。这种做法虽然在短期内加快了开发进度,但为后续的维护带来了巨大的挑战。

当项目上线后,我接手了后续的迭代和维护工作。面对四个前端工程,我深感头痛。每个项目都有独特的编码风格、文件组织结构,甚至组件拆分的颗粒度也各不相同。这种混乱的状况让我深刻体会到,缺乏统一规范的开发流程会给项目带来多么严重的后果。

这段经历让我意识到,良好的开发规范不仅是代码质量的保障,更是项目长期稳定运行的关键。因此,我将通过本文分享我的经验和教训,希望能为正在使用 Vue 技术栈的小伙伴们提供一些参考。

Vue 风格指南

Vue 官方提供的风格指南是开发 Vue 项目时的重要参考。它能帮助我们避免常见的错误和反模式,提升代码的可读性和一致性。对于正在使用 Vue 技术栈的小伙伴们,我强烈建议大家花时间阅读一下 Vue2 风格指南,建议团队在遵循官方指南的基础上,结合自身经验和技术背景,制定适合自己团队的代码规范。

常见 vue 开发问题

  1. v-for 没有使用 key 或者 key 使用 index 做值
  2. 编写 vue 文件,不习惯声明 name 属性或者 name 是一个单词

Views 目录结构

在前端业务开发中,尤其是管理系统项目,views 文件夹是核心区域,它承载着具体业务功能的实现。与前端脚手架、工程化和基础设施建设这些底层工作不同,views 目录结构直接关系到业务逻辑的组织和代码的可维护性。因此,专注于优化 views 文件夹的结构,对于提升开发效率和代码质量至关重要。

目录结构设计原则:

  1. 模块化:每个业务模块(如用户管理、订单管理)独立成文件夹,或根据具体的路由划分文件夹,便于管理和扩展。
  2. 层次清晰:通过合理的层级结构,减少嵌套深度,便于快速定位代码。
  3. 通用性与专用性分离:将通用组件和工具函数放在 componentsutils 文件夹中,而业务专用组件放在模块内部。

个人的实践总结

1. 主文件作为模块核心入口

每个业务模块必须有一个主文件(如 index.vue),作为该模块的核心入口。主文件应包含完整的布局和主要业务流程逻辑,确保模块的独立性和完整性。 主文件负责组织页面的整体结构,如布局、路由嵌套等,同时集中管理模块的核心逻辑。

2. 弹窗组件独立化

对于业务模块中需要弹窗展示的功能,单独拆分出一个独立的弹窗组件(如 XXXModal.vue)。这种方式不仅提高了代码的复用性,还便于对弹窗逻辑进行集中管理。项目中,我基于 el-dialog 封装了函数式调用的 dialog 组件,使用函数式 dialog 让业务逻辑保持连贯性。

项目中我已经基于 el-dialog 封装了函数式调用的 dialog 组件,使用函数式 dialog 让业务逻辑保持连贯性

标签式 el-dialog(传统方式)
  1. 如果页面有多个弹窗,导致代码中出现多个 <el-dialog> 标签和多个变量来控制它们的显示与隐藏。这种做法不仅会使代码冗长,还容易导致维护困难
  2. 在使用标签式弹窗时,弹窗的显示逻辑通常通过改变变量(如 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)粒度过粗的弊端

  • 复用性差:大型组件难以在不同场景中复用,导致代码重复。
  • 维护困难:组件功能过于复杂,修改一处可能影响多个功能点。
  • 灵活性低:难以适应需求变化,扩展功能时可能需要重构

在实际开发中,建议根据以下原则平衡组件拆分的粒度:

  1. 功能独立性:每个组件应该有明确的功能边界,不依赖外部状态。
  2. 复用性:尽量将通用功能拆分为独立组件,便于复用。
  3. 可维护性:组件的复杂度应适中,避免过大或过小。
  4. 团队习惯:与团队成员协商一致,制定统一的拆分规范。

后记

在前端开发的道路上,我深知自己还有很长的路要走。未来的工作中,我将继续深入学习新技术,结合项目需求和团队特点,不断优化开发流程和代码质量。我也会更加注重团队协作,与同事们共同探讨更好的解决方案,提升整个团队的开发效率。


感谢阅读,敬请斧正!