vue 项目优化记录

1,370 阅读8分钟

前言:最近整理一下手里项目中使用的优化方案,故写下此篇内容。

当前项目背景:此项目是一个新创建的项目,但有着需求变动频繁、需求量大、只有原型稿、开发时间紧张等等的问题。在赶工作量的情况下,随之而来的问题就产生了,代码风格不统一、大量冗余代码,页面加载缓慢,整体的体验欠优雅的。

针对我们原有的问题,整理出优化方案如下:

1. 引入eslint统一代码风格;

2. 借鉴vue的代码风格指南,做进一步优化;

3. 优化路由的懒加载;

4. 减少请求,对静态资源进行缓存

5. 超过一个以上的UI组件使用按需引入;

6. 清除组件内销毁未清理的方法;

7. 将css文件引入前置,避免因网络缓慢,出现白屏;

8. 删除掉项目中未使用的库、文件、未使用的冗余代码。

一、引入eslint统一代码风格

eslintECMAScript/JavaScript 语法规则和代码风格的检查工具,它的目标是保证代码的一致性和避免错误。从而实现辅助编码规范的执行,有效控制项目代码的质量。提起eslint不免要提TSlint,但是由于性能问题,TypeScript官方决定全面采用ESLint,甚至把仓库(Repository)作为测试平台,而 ESLintTypeScript解析器也成为独立项目,专注解决双方兼容性问题。所以果断引入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/exportCommonJSrequire/exports)

这些都不是

翻译:

您的项目使用哪个框架?

React

Vue.js

Not of these

翻译:

您的项目有没有使用TypeScript?

翻译:

您的代码在哪里运行?

 Browser

 Node

翻译:

您希望如何为项目定义样式?(使用箭头键)

使用流行的风格指南

回答关于你的风格的问题

检查您的javascript文件

翻译:

您要遵循哪种样式指南?

Airbnbhttps://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

在使用eslinthusky能够保证代码的质量问题,但是在实际工程中却还必须面临一个问题。现实情况下,一个应用一般是多个开发参与,并且在应用的生命周期中还涉及到人员变更,针对这些历史代码时,如果提交代码时,对其他未修改文件都进行检查,那就得不偿失了。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代码风格

此项在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组件销毁未清理的方法

我们在项目中有用到dom2addEventListener绑定事件或者是计时器来进行某些操作,当我们切换页面时,虽然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);
    }
});

八、删除掉项目中未使用到的库、文件、注释许久的代码

项目前期的考虑应对不同场景引入了许多工具库和组件,后期发现并没有用到以及未来得及删的无效代码。

  1. 项目初期有考虑到使用vuex来传输通信,可实际到项目上,并没有多到需要统一处理状态的情况,大部分只有父子的通信,那用busprops就够解决了,那么我们就可以把没有用到到库删除掉。
  2. 还有些文件也是初期创建完,后期因需求变动就废弃了,没有及时清理,以及一些注释了许久未删掉的业务代码。

总结:对于每一位开发来说,优化一直是一个需要持续化进行的动作。一方面是为了产品有更好的交互体验,另一方面也是提高自己的能力,加油吧!