15 | 实战痛点1:复杂Vue项目的规范和基础库封装

你好,我是大圣,欢迎进入课程的第15讲。

在全家桶实战篇的前几讲里,我们学习了Vue 3本身的进阶内容。从今天开始,我们尝试着把这些技能都用在实际项目中,聊一下实战中常见的痛点。不过,既然是实际项目,那还是有很多库需要引入的,比如网络请求时用到的axios、时间处理时用到的Dayjs等等。今天我要跟你聊的,则是复杂 Vue 项目的规范和基础库的封装。

组件库

在项目开发中,我们首先需要一个组件库帮助我们快速搭建项目,组件库提供了各式各样的封装完备的组件。现在社区可选择的组件库有element-plus、antd-vue,Naive-UI、Element3等,我们选择Element3来搭建项目,首先我们来到项目目录下,执行下面的代码安装Element3。

npm install element3 --save

然后,我们在src/main.js中使用一下Element3。看下面的代码,我们在其中引入了Element3和主体对应的CSS,并使用use(Element3)加载组件库。

import { createApp } from 'vue'
import Element3 from 'element3'
import 'element3/lib/theme-chalk/index.css'
import store from './store/index'
import App from './App.vue'
import router from './router/index'

const app = createApp(App)

app.use(store)
    .use(router)
    .use(Element3)
    .mount('#app')

这样,项目的入口页面就注册好了Element3内置的组件。关于Element3的组件列表,你可以到Element3官网查看。接下来,我们就可以使用组件库去搭建我们的页面了。

首先,我们打开项目目录下的src/App.vue文件,把之前的学习清单应用时的测试代码移除,然后新增下面的代码。你能看到,我们在代码中使用Element3的Container布局组件实现管理系统的整体布局。

<template>

<el-container>
  <el-header>Header</el-header>
  <el-container>
    <el-aside width="200px">
      <div>
        <router-link to="/"> Home</router-link> 
      </div>
      <div>
        <router-link to="/about">About</router-link>
      </div>
    </el-aside>
    <el-container>
      <el-main>
        <router-view></router-view>
      </el-main>
    </el-container>
  </el-container>
</el-container>
</template>

<script setup>

</script>
<style>
  .el-header,
  .el-footer {
    background-color: #b3c0d1;
    color: #333;
    text-align: center;
  }

  .el-aside {
    background-color: #d3dce6;
    color: #333;
  }

  .el-main {
    background-color: #e9eef3;
    color: #333;
  }

  body > .el-container {
    margin-bottom: 40px;
  }
</style>

上面代码对应在前端的显示格局如下,代码上方的Header组件,承载着页面的头部信息,包括项目左上角的名字、右上角的用户信息、消息等等。代码中的aside对应了前端页面左侧的侧边栏,承载着页面主要的导航信息;main组件内部使用router-view渲染路由对应的组件,然后我们继续使用Element3逐渐丰富Header和导航信息。

图片

在Element3中,我们也可以很方便地找出我们需要的组件,就像Menu等等。在下面的代码中,我们使用el-menu组件渲染header组件,el-menu内部使用el-menu-item渲染导航组件。

<el-header>
<el-menu
  :default-active="1"
  class="el-menu-demo"
  mode="horizontal"
  background-color="#545c64"
  text-color="#fff"
  active-text-color="#ffd04b"
>
  <el-menu-item index="1">处理中心</el-menu-item>
  <el-submenu index="2">
    <template v-slot:title>我的工作台</template>
    <el-menu-item index="2-1">选项1</el-menu-item>
    <el-menu-item index="2-2">选项2</el-menu-item>
    <el-menu-item index="2-3">选项3</el-menu-item>
    <el-submenu index="2-4">
      <template v-slot:title>选项4</template>
      <el-menu-item index="2-4-1">选项1</el-menu-item>
      <el-menu-item index="2-4-2">选项2</el-menu-item>
      <el-menu-item index="2-4-3">选项3</el-menu-item>
    </el-submenu>
  </el-submenu>
  <el-menu-item index="3" disabled>消息中心</el-menu-item>
  <el-menu-item index="4"
    ><a href="https://element3-ui.com" target="_blank"
      >订单管理</a
    ></el-menu-item
  >
</el-menu>

  </el-header>

使用menu组件渲染header和aslide组件后,页面布局示意图如下,这样页面的基本结构就搭建完毕了。

图片

工具库

完成页面基本结构的搭建后,在我们获取后端数据时,需要使用axios发起网络请求。在项目的根目录下,打开命令行,执行下面的命令,这样我们就可以安装axios了(axios 跟 Vue 版本没有直接关系,安装最新即可)。

npm i axios --save

axios作为现在最流行的网络请求库,可以直接使用axios.get或者axios.post去获取数据。但是在项目开发中,业务逻辑有很多配置需要进行统一设置,所以安装完axios之后,我们需要做的就是封装项目中的业务逻辑

首先,在项目在登录成功之后,后端会返回一个token,用来存储用户的加密信息,我们把token放在每一次的http请求的header中,后端在收到请求之后,会对请求header中的token进行认证,然后解密出用户的信息,过期时间,并且查询用户的权限后,校验完毕才会返回对应的数据。

所以我们要对所有的http请求进行统一拦截,确保在请求发出之前,从本地存储中获取token,这样就不需要在每个发起请求的组件内去读取本地存储。后端数据如果出错的话,接口还要进行统一拦截,比如接口返回的错误是登录状态过期,那么就需要提示用户跳转到登录页面重新登录。

这样,我们就把网络接口中需要统一处理的内容都放在了拦截器中统一处理了。在下面的代码中,所有接口在请求发出之前,都会使用getToken获取token,然后放在header中。在接口返回报错信息的时候,会在调试窗口统一打印报错信息。在项目的组件中,我们只需要直接使用封装好的axios即可。

import axios from 'axios'
import { useMsgbox, Message } from 'element3'
import store from '@/store'
import { getToken } from '@/utils/auth'

const service = axios.create({
  baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
  timeout: 5000, // request timeout
})

service.interceptors.request.use(
  config => {
    if (store.getters.token) {
      config.headers['X-Token'] = getToken()
    }
    return config
  },
  error => {
    console.log(error) // for debug
    return Promise.reject(error)
  },
)

service.interceptors.response.use(
  response => {
    const res = response.data
    if (res.code !== 20000) {
      console.log('接口信息报错',res.message)
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },
  error => {
    console.log('接口信息报错' + error) 
    return Promise.reject(error)
  },
)

export default service

然后,我们在项目里集成CSS预编译器,CSS预编译器可以帮我们更快、更高效地管理和编写CSS代码。在这里,我们选择Sass作为CSS预处理语言,然后我们就进入项目根目录下执行下面代码安装Sass。

npm install -D sass

然后,我们进入src/components/Todolist.vue文件中。看下面的代码,我们直接在style标签上新增 lang=“scss”,这样就可以使用Sass的语法了。有了Sass之后,我们在CSS里使用了变量、嵌套、继承等逻辑,并定义了$padding和$white这两个变量。这样我们就可以嵌套书写CSS选择器,也就极大地提高我们写CSS的效率。

<style lang="scss" scoped>
$padding:10px;
$white:#fff;
ul {
  width:500px;
  margin:0 auto;
  padding: 0;
  li {
    &:hover {
      cursor: pointer;
    }
    list-style-type: none;
    margin-bottom: $padding;
    padding: $padding;
    background: $white;
    box-shadow: 1px 3px 5px rgba(0, 0, 0, 0.1);
  }
}
</style>

Sass让我们在CSS的世界里也拥有了编程的概念,在实际项目中可以使用变量和函数等概念优化CSS代码,这个你在Element3组件库的实现中也能看到。

在Element3的GitHub项目中,我们可以看到所有的Sass代码。以common/var.scss文件为例,在这个文件中,我们可以看到Element3中所有的变量,并且这个文件中的代码也对颜色、动画函数、边框,字体大小等等都做了统一的设置。

我们也可以修改这些变量,从而获得一个定制风格的Element3。所以项目在开始之初,我们就可以想一下整体设计风格,最好能够预先定义好整体的颜色,边框,字体大小等等,这能极大降低后续的css维护成本。

至此,一个基于 Vite + Vue 3 + Vue Router + Vuex + Element3 + Axios + Sass 的前端项目开发环境搭建完毕。下面,我们来打磨一下这个项目。简单来说,就是给项目增加代码规范约束、提交规范约束等,让其更完善、更健壮。

代码规范和提交规范

由于个人习惯的不同,每个人写代码的风格也略有不同。比如在写JavaScript代码中,有些人习惯在每行代码之后都写分号,有些人习惯不写分号。但是团队产出的项目就需要有一致的风格,这样代码在团队之间阅读起来时,也会更加流畅。ESLint就是专门用来做规范团队代码的一个库。

首先我们安装ESLint,进入到项目文件夹,使用下面的命令,我们就可以在全局或者本地安装ESLint了。

npm i eslint -D

ESLint 安装成功后,在项目根目录下执行 npx eslint --init,然后按照终端操作的提示完成一系列设置来创建配置文件。你可以按照下图所示的选择来始化ESLint。

图片

我们设置的是比较松散的校验规则,你可以根据团队风格去额外配置ESLint的插件。我们进入到项目目录下的eslintrc.json中,在rules中新增下面代码,也就是强制要求JavaScript的行尾不写分号。

    "rules": {
        "semi": ["warn","never"]
    }

然后,我们在命令行中执行npx eslint src,接着你就会看到下图所示的报错信息,其中详细告诉你了哪里的代码不合规范。根据报错信息的提示,我们进入到router/index.js文件后,删掉第15行代码结束的分号就可以解除这个警告。

图片

前面我们已经统一了代码规范,并且在提交代码时进行强约束来保证仓库代码的质量。多人协作的项目中,在提交代码这个环节,也存在一种情况:不能保证每个人对提交信息的准确描述,因此会出现提交信息紊乱、风格不一致的情况。

对于这种情况,一种比较好的解决方案是,在执行git commit命令的时候,同时执行ESLint。我们使用husky管理git的钩子函数,在每次代码提交至git之前去执行ESLint,只有ESLint的校验通过,commit才能执行成功。后面的进阶开发篇中,单元测试也会放在git的钩子函数中执行,确保提交到git中的代码都是测试通过的。

项目代码符合规范后,我们就可以把代码提交到代码仓库中,git允许我们在每次提交时,附带一个提交信息作为说明。我们在项目根目录执行下面的命令,提交了一个附带信息是commit的代码。

git add . 
git commit -m 'init commit'

然后我们需要再定义一下git的提交规范,描述信息精准的git提交日志,会让我们在后期维护和 处理Bug时有据可查。在项目开发周期内,我们还可以根据规范的提交信息,快速生成开发日志,从而方便我们追踪项目和把控进度。 如下图所示,我们可以看到Vue 3的代码提交日志。

图片

看了Vue 3代码日志提交的格式,初次接触的你可能会觉得复杂。其实不然,Vue 3在代码日志中,使用的是【类别: 信息】的格式,我们可以通过类别清晰地知道这次提交是代码修复,还是功能开发feat。冒号后面的信息是用来解释此次提交的内容,在修复bug时,还会带上issue中的编号。在现在的项目开发中,我们也会强制要求使用和Vue 3一样的git 日志格式。

总结

今天这一讲的内容到这就结束了,我们来复习一下今天学到的内容。首先我们引入了Element3组件库,在项目入口注册Element3后,你可以在项目的任意地方直接使用Element3首页的组件列表

这样,我们就可以很方便地使用layout和container布局实现页面的搭建,然后引入axios作为网络请求库,并且对接口统一做了全局拦截。下一讲中,项目权限管理也是在axios拦截函数里实现的。

当然,复杂的Vue项目更需要良好的规范,毕竟没有规矩不成方圆,为此,我们进一步规范了代码格式,使用ESLint统一JavaScript的代码风格,husky管理git的钩子函数,并且规定了git的提交日志格式,确保代码的可维护性。

思考题

关于Element3组件库布局和导航组件的使用,你有什么其他布局的建议呢?

欢迎在留言区发表你的看法,也欢迎你把这一讲的内容推荐给你的同事和朋友们,我们下一讲再见。

精选留言

  • icoolee

    2021-11-29 16:58:35

    代码规范这讲的感觉有点少,加餐讲一下全套吧tailwindcss、postcss、eslint、prettier、.editconfig、Commitizen、husky + commitlint、lint-staged、stylelint
    作者回复

    后面组件库加了husky,eslint prettier,并没有用tailwindcss等,不过这是个好建议

    2021-12-13 21:13:16

  • 椰__季

    2021-11-19 11:05:56

    老师,能发一下从实战到这节课的源代码么,我看自己的代码,缺胳膊少腿。配置了eslint 好多创建了没有使用的一些
  • 韩仕杰

    2021-11-19 14:32:00

    大圣老师,axios、sass 安装在开发环境 是不是不太好?(dependencies、devDependencies)
    作者回复

    提醒的有道理 我给忽略了 axios需要需要放在dep里

    2021-11-19 23:22:29

  • peterpc

    2021-11-19 09:23:39

    大圣,husky如何管理git的钩子函数?一笔带过?
    作者回复

    组件库搭建的时候详细介绍了husky

    2021-12-13 22:03:30

  • 轻度

    2021-11-19 08:36:07

    为了避免出现歧义等情况,不应该是强制javascript写分号吗
    作者回复

    我是不写分号党,vue源码也是没有分号

    2021-11-19 23:21:37

  • 关关君

    2021-11-19 16:47:56

    大圣老师为什么安装Axios的时候要加-D呢?Axios生产环境中不也会用到吗
    作者回复

    这个是我敲习惯了 需要吧-d删除

    2021-11-20 16:46:52

  • ll

    2021-11-19 11:55:10

    提纲挈领,总结加复习,跟着打一遍代码,然后再默写一遍。受益匪浅发,赞!
  • 嘿吼

    2021-11-26 16:47:47

    大圣老师,有没有什么比较全一点的axios的请求案例,比如像大公司的axios封装是什么样子的?
    作者回复

    其实这里介绍的一半项目就足够用了,axios主要要做的就是token的管理,错误信息的处理,不同环境的链接前缀管理

    2021-12-13 21:21:01

  • 南山

    2021-11-22 06:37:37

    配置 commit规范的工具一笔带过?
    作者回复

    后面不充了

    2021-12-07 21:06:55

  • 海阔天空

    2021-11-19 08:05:13

    项目前期的规范和基础库封装是非常重要的。统一的代码规范和提交规范,为项目的后期扩展和维护项目带来便利。在老师的课程中又学到了。
    1、简单来说,就是给项目增加代码规范约束、提交规范约束等,让其更完善、更健壮。
    2、git 的提交规范,描述信息精准的 git 提交日志,会让我们在后期维护和 处理 Bug 时有据可查。
  • 疯琴

    2021-12-10 15:51:52

    就喜欢这种走实际项目真实开发流程,代码实实在在的。
  • JIo

    2021-11-19 17:20:28

    为什么这样大段大段的代码 不能出视频课 边敲边讲解呢? 视频的效果肯定比音频文本的效果好很多啊
    作者回复

    这个专栏就是文字专栏 设计之初没有视频计划 囧

    2021-11-21 20:59:11

  • Joe

    2021-11-19 14:35:40

    大圣老师,单元测试什么时候开讲,期待中。。。
  • Ghoti

    2022-04-19 18:32:44

    导航一的代码不全啊
  • 陈豆

    2021-12-14 14:37:26

    大圣老师 能不能每一讲 写的完整代码 放到git上
    作者回复

    现在代码集中推送到https://github.com/shengxinjing/geektime-vue-course上汇总,可以看下README.md

    2022-01-16 22:21:04

  • 风一样

    2021-12-01 18:18:41

    请问 process.env.VUE_APP_BASE_API 这个变量怎么来的呢?
    作者回复

    vite设置环境变量的方法链接https://cn.vitejs.dev/guide/env-and-mode.html#production-replacement, 这样可以很方便的在dev和test以及线上环境使用不同的前缀

    2021-12-13 21:05:14

  • 阿阳

    2021-11-28 18:04:13

    在element3示例代码那部分,好像少了左边aside组件的布局,header组件的布局也对不上,希望老师再帮忙校准一下。谢谢。
    作者回复

    看下这里的显示是不是你需要的 https://github.com/shengxinjing/ailemente

    2021-12-13 21:17:20

  • 淡若清风过

    2021-11-20 05:50:32

    element3官网打不开
  • escray

    2023-11-09 10:59:24

    如果有时间整理一下第5节和本节内容,大致可以梳理出来一份 Vue3 项目的脚手架。

    本节内容中有关 axios 部分的代码是在 https://github.com/hug-sun/element3-admin-template/blob/master/src/utils/request.js 里面。

    如何使用 “husky如何管理git的钩子函数”,还需要在之后的课程里面学习。

    另外就是对于 element 3 和 element plus,还是更推荐后者,参考以下的知乎回答

    https://www.zhihu.com/question/432693338
  • 苍王

    2022-10-18 19:36:28

    老师, 看文档 axios 很简单但是 随便写了个简单的get请求就报错:
    api.get("http://www.baidu.com")
    .then(response => {
    console.log(response)
    }).catch( err =>{
    console.log(err)
    })

    报错信息:
    Access to XMLHttpRequest at 'https://www.baidu.com/' (redirected from 'http://www.baidu.com/') from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.