前言:最近整理一下手里项目中使用的优化方案,故写下此篇内容。
当前项目背景:此项目是一个新创建的项目,但有着需求变动频繁、需求量大、只有原型稿、开发时间紧张等等的问题。在赶工作量的情况下,随之而来的问题就产生了,代码风格不统一、大量冗余代码,页面加载缓慢,整体的体验欠优雅的。
针对我们原有的问题,整理出优化方案如下:
1. 引入eslint统一代码风格;
2. 借鉴vue的代码风格指南,做进一步优化;
3. 优化路由的懒加载;
4. 减少请求,对静态资源进行缓存
5. 超过一个以上的UI组件使用按需引入;
6. 清除组件内销毁未清理的方法;
7. 将css文件引入前置,避免因网络缓慢,出现白屏;
8. 删除掉项目中未使用的库、文件、未使用的冗余代码。
一、引入eslint统一代码风格
eslint是ECMAScript/JavaScript语法规则和代码风格的检查工具,它的目标是保证代码的一致性和避免错误。从而实现辅助编码规范的执行,有效控制项目代码的质量。提起eslint不免要提TSlint,但是由于性能问题,TypeScript官方决定全面采用ESLint,甚至把仓库(Repository)作为测试平台,而ESLint的TypeScript解析器也成为独立项目,专注解决双方兼容性问题。所以果断引入eslint。
参考文档:
【eslint && tslint】
【ESlint 中文官方网站】
一引入就看到轰轰轰轰一堆红色警告,说实话,刚开始使用起来还是非常不习惯的,eslint的规则是默认都开启的状态,如果想修改规则,打开eslintrc.js 这个文件,在如下rules里面就可以根据实际项目需要修改规则:
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": [
"airbnb-base",
"plugin:vue/essential"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"parser": "babel-eslint"
},
"plugins": [
"vue"
],
"rules": {
"no-console": "off",
"no-debugger": "off",
"no-alert":"off",
}
};
请参考:eslint官方规则
引入方式如下:
下载eslint:
cnpm install eslint --save-dev
eslint配置文件:
./node_modules/.bin/eslint --init
接下来会出现以下几个小问题,根据项目实际情况选就好:
翻译:
您想如何使用eslint?(使用箭头键上下选择)
仅检查语法
检查语法并发现问题
检查语法、发现问题和强制代码样式
翻译:
您的项目使用什么类型的模块?
javascript模块(import/export)
CommonJS(require/exports)
这些都不是
翻译:
您的项目使用哪个框架?
React
Vue.js
Not of these
翻译:
您的项目有没有使用TypeScript?
翻译:
您的代码在哪里运行?
Browser
Node
翻译:
您希望如何为项目定义样式?(使用箭头键)
使用流行的风格指南
回答关于你的风格的问题
检查您的javascript文件
翻译:
您要遵循哪种样式指南?
Airbnb(https://github.com/airbnb/javascript)
Standard(https://github.com/standard/standard)
Google(https://github.com/google/eslint-config-google)
翻译:
您希望配置文件采用什么格式?
JavaScript
YAML
JSON
翻译:
您想现在用NPM安装它们吗?
husky
为了保证每次提交的
git代码是正确的,我们可以使用eslint配合husky, 在进行git commit的时候验证eslint规范,如果eslint验证不通过,则不能提交,确保本地的代码已经通过检查才能push到远程,这样才能从一定程度上确保应用的线上质量。
cnpm install husky
接下来在package.json中添加 husky 的配置:
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "lint-staged"
}
},
lint-staged
在使用
eslint和husky能够保证代码的质量问题,但是在实际工程中却还必须面临一个问题。现实情况下,一个应用一般是多个开发参与,并且在应用的生命周期中还涉及到人员变更,针对这些历史代码时,如果提交代码时,对其他未修改文件都进行检查,那就得不偿失了。lint-staged帮我们实现了每次只对当前修改后的文件进行扫描,即进行git add加入到stage区的文件进行扫描即可,只对增量代码进行检查。
cnpm install lint-staged
配合husky完成配置:
"lint-staged": {
"src/**/*.{js,json,vue}": [
"eslint --fix",
"git add"
]
},
配置完成的.eslintrc.js 文件:
module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": [
"airbnb-base",
"plugin:vue/essential",
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"parser": "babel-eslint",
},
"plugins": [
"vue",
],
"rules": {module.exports = {
"env": {
"browser": true,
"es6": true
},
"extends": [
"airbnb-base",
"plugin:vue/essential",
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module",
"parser": "babel-eslint",
},
"plugins": [
"vue",
],
"rules": {
// 此处的规则大家按需配置即可
}
}
package.json 文件配置要跟随eslint下载的依赖包:
"dependencies":{
"eslint": "^5.16.0",
"babel-eslint": "^10.0.1",
"eslint-config-airbnb-base": "^13.1.0",
"eslint-loader": "^2.1.2",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-vue": "^5.2.2",
"husky": "^2.3.0",
"lint-staged": "^8.1.7",
},
"husky": {
"hooks": {
"pre-commit": "lint-staged",
"pre-push": "lint-staged"
}
},
"lint-staged": {
"src/**/*.{js,json,vue}": [
"eslint --fix",
"git add"
]
}
此时的代码看起来舒服多了😌
二、借鉴vue的代码风格指南,做进一步优化
此项在
vue的官网已经解释的非常详细了,我就不在这一一赘述了。做到这里时,整体的代码风格,可读性大大提高。
三、优化路由的懒加载
项目在建立初期已经使用了路由懒加载的方式,本次优化按照业务的模块给路由添加了分组,当不出发对应当分组时,不提前预加载资源,也增加代码的可读性。
懒加载之前:
import Vue from 'vue';
import Router from 'vue-router';
import HelloWorld from '@/components/HelloWorld';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: HelloWorld
}
]
});
异步组件实现懒加载
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
const router = new Router({
routes: [
{
path: '/',
name: 'HelloWorld',
component: resolve => require(['@/components/HelloWorld'], resolve)
},
{
path: '/home',
name: 'home',
component: resolve => require(['@/components/home'], resolve)
}, {
path: '/report',
name: 'report',
component: resolve => require(['@/components/report'], resolve)
}
]
});
export default router;
在eslint的规则中也不推荐使用require,因为 require() 是同步加载的,在其它地方使用时,会导致性能问题。参考地址
推荐Es6的import的懒加载
这里的import()方法由es6提出,import()方法是动态加载,返回一个Promise对象,then方法的参数是加载到的模块。类似于Node.js的require方法,主要import()方法是异步加载的。
import Vue from 'vue';
import Router from 'vue-router';
Vue.use(Router);
const HelloWorld = () => import('@/components/HelloWorld');
const home = () => import('@/components/home');
const report = () => import('@/components/report');
const ErrorPage = () => import('@/components/ErrorPage');
const routerList = {
path: '/',
name: 'HelloWorld',
component: HelloWorld,
children: [
{
path: '/home',
name: 'home',
component: home
},
{
path: '/report',
name: 'report',
component: report
},
{
path: '/error',
name: 'errorpage',
component: ErrorPage
}
]
};
const router = new Router({
routes: [
routerList,
{
path: '*',
redirect: '/'
},
{
path: '/error',
name: 'errorpage',
component: ErrorPage
}
]
});
export default router;
此处推荐使用Es6的import的懒加载方法
优化的路由分组如下:
// 概览
const Home = () => import('@/views/overview/Index');
// 外部概览iframe
const Outer = () => import('@/views/overview/Iframe');
// 菜单总列表
const MenuRouter = () => import('@/views/menu/MenuRouter');
// A分组
const TraitRouter = () => import('@/views/data/XXX/Router');
// B分组
const CrowdRouter = () => import('@/views/user/XXX/Router');
正常的引入子组件
<template>
<div>
report页面
<test></test>
</div>
</template>
<script>
import test from './test';
export default {
props: {},
data() {
return {};
},
computed: {},
components: {
test
},
mounted() {},
methods: {},
watch: {},
destroyed() {}
};
</script>
将我们的子组件使用变量保存
<template>
<div>
report页面
<test></test>
</div>
</template>
<script>
const test = () => import('./test');
export default {
props: {},
data() {
return {};
},
computed: {},
components: {
test
},
mounted() {},
methods: {},
watch: {},
destroyed() {}
};
</script>
进一步优化
<template>
<div>
report页面
<test></test>
</div>
</template>
<script>
export default {
props: {},
data() {
return {};
},
computed: {},
components: {
test: () => import('@/components/test')
},
mounted() {},
methods: {},
watch: {},
destroyed() {}
};
</script>
添加全局路由守卫,保证权限的控制
在官网有详细的添加方法:vue路由守卫
四、减少请求,对静态资源进行缓存
在考虑到项目的一些页面是静态的,在切换时不需重新请求,我们对这类组件添加了
keep-alive来进行缓存处理,从而节省性能 使用方法,在这篇文中有讲到:跳转
五、超过一个以上的UI组件使用按需引入
通常的项目中,我们都有引入到一个主要的UI组件来支持,但我们可能需要在这个库中并没有,那么我们就需要找其他的组件来支持,但如果因为一个组件把整个库引入到项目中有点大材小用,那我们就要考虑按需引入。
例如:element-ui 按需引入
import { Select, Option, OptionGroup } from 'element-ui';
// 按需引入element组件
Vue.prototype.$ELEMENT = { size: 'small' };
Vue.use(Select);
Vue.use(Option);
Vue.use(OptionGroup);
六、在beforeDestroy时清除vue组件销毁未清理的方法
我们在项目中有用到
dom2级addEventListener绑定事件或者是计时器来进行某些操作,当我们切换页面时,虽然vue组件被销毁,但我们添加但addeventListener不会被销毁,所以我们需要手动销毁。
类似这样的情况还有:
addEventListener;
setTimeout;
setInterval;
this.bus.$emit()
解决:而在离开是的时候需要销毁监听: 在
beforeDestroyvue组件销毁前进行清除, 否则监听会一直存在, 因为这是单页面应用, 页面并未关闭。
beforeDestroy() {
this.bus.$off('name');
window.removeEventListener('name', this.xxx);
}
计时器的清除处理方式:参考我的上次文章
七、将css文件引入前置,避免因网络缓慢,出现白屏
这个改变起源于一次在入口文件
main.js中引入的js工具有debugger阻塞,导致页面卡住、白屏,并不是我们添加的加载动画。后经排查发现,在入口文件中,我们将css文件的引入放到了js后面,所以并没有加载到我们预先添加的动画。
分享一个自建项目的排序:
// 引入基础组件
import Vue from 'vue';
import Vuex from 'vuex';
import iView from 'iview';
import echarts from 'echarts';
import lodash from 'lodash';
import { Select, Option, OptionGroup } from 'element-ui';
// 引入css文件
import 'iview/dist/styles/iview.css';
import 'font-awesome/css/font-awesome.min.css';
import '@/assets/iconfont/iconfont.css';
import '@/assets/styles/common.css';
import '../static/css/error_page.css';
// 引入基础配置
import store from './store';
import axios from './utils/axios';
import router from './router';
import App from './App';
// 引入js文件
import tools from '@/utils/common';
import config from '@/utils/config';
import api from '@/api';
import './directive';
import './filter';
Vue.use(iView);
Vue.use(Vuex);
// 按需引入element组件
Vue.prototype.$ELEMENT = { size: 'small' };
Vue.use(Select);
Vue.use(Option);
Vue.use(OptionGroup);
// vue全局配置
Vue.config.productionTip = false;
// 挂载在全局
Vue.prototype.echarts = echarts;
Vue.prototype.$axios = axios;
Vue.prototype.$tools = tools;
Vue.prototype.$config = global.$config = config;
Vue.prototype.$lodash = lodash;
Vue.prototype.$api = api;
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: {
App
},
template: '<App/>',
render(createElement) {
return createElement(App);
}
});
八、删除掉项目中未使用到的库、文件、注释许久的代码
项目前期的考虑应对不同场景引入了许多工具库和组件,后期发现并没有用到以及未来得及删的无效代码。
- 项目初期有考虑到使用
vuex来传输通信,可实际到项目上,并没有多到需要统一处理状态的情况,大部分只有父子的通信,那用bus、props就够解决了,那么我们就可以把没有用到到库删除掉。 - 还有些文件也是初期创建完,后期因需求变动就废弃了,没有及时清理,以及一些注释了许久未删掉的业务代码。