手摸手,带你用vue撸后台 系列五(v4.0新版本)

前言

vue-element-admin2017.04.17提交第一个 commit 以来,维护至今已经有两年多的时间了了,发布了四十多个版本,收获了三万多的 stars,远远的超出了本身的预期。距离上次手摸手系列教程也已通过去了好久,主要由于:做为一个我的开源项目,维持它已经很难了,因此真的没啥时间写详细的教程了,光是维护项目 文档 就让我很头疼了。也有很多人建议我出付费教学视频,但我我的仍是更愿意把这个时间投入到维护开源项目之中吧。css

本篇教程主要是趁着vue-element-admin发布了 v4.0 新版本,首先来简单说一下4.0版本作了哪些改动和优化。后半部分则会分享一些新的思考和一些小技巧吧。以前几篇手摸手文章都差很少两年前的了,但随着技术的不断发展迭代,不少以前的不能解决的问题也是都是有了新的解决方案的,同时也会出现一些新的问题和挑战。html

4.0 作了什么

首先大概说一下4.0版本作了些什么,经过 pull request 能够看出这是一次比较大的升级,有大概 170 屡次的 commits,200 多个文件的改动。其中最大的改变是接轨 vue 社区,直接经过 vue-cli来进行构建,省去了不少额外繁琐的配置(下文会介绍),并修改了以前 mock 数据的方案,本地改用 mock-server 来解决以前mockjs带来的各类问题。同时增长了 jest 单元测试,使用了async/await,增长了可视化配置权限,增长了自定义布局等等,优化了原先addRoutes的权限方案,支持不刷新页面更新路由等等功能。具体的可看 github release。接下来咱们着重来分析一下这几个功能。前端

vue-cli@3

自己配置方面没有啥特别好说的,官方文档已经写得很详细了。此次更新基本上就是基于 webpack-chain 把以前的 webpack 配置迁移了一遍,由于vue-cli帮你作了不少默认配置,全部能够省去一些代码。固然这种out-of-the-box的工具利弊也很明显,它能快速上手,大部分简单场景无需任何额外配置基本就能用了。但对于复杂度高的或者自定义性强的项目来讲,配置复杂度可能没有减小太多。它要求你不只要对 webpack 或者相关工程化的东西很很熟悉,你还要对vue-cli作的一些默认配置和参数也有有必定了解,时不时要去看一下源码它到底干了啥,有的时候它的一些 plugin 出现了问题还不太好解决。并且说实话 webpack-chain 的书写也是有些门槛的,大部分状况下我也很难保证本身的配置写对的,还好官方提供了inspec功能,能让配置简单了很多。当你想知道本身的 vue-config.js 里的配置到底对不对的时候,你能够在命令行里执行vue inspect > output.js,它会将你最终生成的config展示在output.js之中,不过它默认显示的是开发环境的配置。若是你想查看其它环境的配置能够经过vue inspect --mode production > output.js。在写构建配置的时候这个功能颇有帮助,同时也能帮助你了解vue-cli在构建时到底帮你作了些什么。vue

其它还有些须要注意的如:环境变量 必须以VUE_APP_开头啊,怎么设置polyfill啊,怎么配置各类各样的loader啊,就不展开了,文档或者社区都有不少文章了。具体配置能够参考 vue.config.jsnode

这里还有一个黑科技,看过我以前文章的小伙伴应该还有印象,我通常在开发环境是不使用路由懒加载的,由于这样会致使热更新速度变慢,具体的能够看以前的 文章,在vue-cli@3中能够更简单的实现,你只要在.env.development环境变量配置文件中设置VUE_CLI_BABEL_TRANSPILE_MODULES:true就能够了。它的实现逻辑和原理与以前仍是同样的,仍是基于 plugins babel-plugin-dynamic-import-node 来实现的。之因此在vue-cli中只须要设置一个变量就能够了,是借用了vue-cli它的默认配置,它帮你代码都写好了。经过阅读 源码 可知,vue-cli会经过VUE_CLI_BABEL_TRANSPILE_MODULES这个环境变量来区分是否使用babel-plugin-dynamic-import-node,因此咱们只要开其它就能够。虽然它的初衷是为了单元测试的,但正好知足了咱们的需求。react

总的来讲,vue-cli对于大部分用户来讲仍是省去了一些繁琐的配置的。若是你使用本项目的话,基本也不须要作其它过多的额外配置的。webpack

redirect 刷新页面

在不刷新页面的状况下,更新页面。这个 issue 两年前就提出来了,以前的文章里面也提供了一个 解决方案。在这里分享一下,我目前使用的新方案。git

// 先注册一个名为 `redirect` 的路由
<script>
export default {
  beforeCreate() {
    const { params, query } = this.$route
    const { path } = params
    this.$router.replace({ path: '/' + path, query })
  },
  render: function(h) {
    return h() // avoid warning message
  }
}
</script>
// 手动重定向页面到 '/redirect' 页面
const { fullPath } = this.$route
this.$router.replace({
  path: '/redirect' + fullPath
})

当遇到你须要刷新页面的状况,你就手动重定向页面到redirect页面,它会将页面从新redirect重定向回来,因为页面的 key 发生了变化,从而间接实现了刷新页面组件的效果。程序员

addRoutes && removeRoutes

看过我以前文章的人确定知道,我目前 vue 项目的权限控制都是经过 addRoutes来实现的。简单说就是:用户登陆以后会返回一个权限凭证Token,用户在根据这个Token去问服务端询问本身的权限,辟如服务端返回权限是['editor'],前端再根据这个权限动态生成他能访问的路由,再经过addRoutes进行动态的路由挂载。具体的代码可见 permission.jsgithub

但这个方案一直是有一个弊端的。那就是动态添加的路由,并不能动态的删除。这就是致使一个问题,当用户权限发生变化的时候,或者说用户登出的时候,咱们只能经过刷新页面的方式,才能清空咱们以前注册的路由。以前老版本的 vue-element-admin就一直采用的是这种方式。虽然能用,但做为一个 spa,刷新页面实际上是一种很糟糕的用户体验。可是官方也迟迟没有出相关的 remove api,相关 issue

后来发现了一种 hack 的方法,能很好的动态清除注册的路由。先看代码:

它的原理其实很简单,全部的 vue-router 注册的路由信息都是存放在matcher之中的,因此当咱们想清空路由的时候,咱们只要新建一个空的Router实例,将它的matcher从新赋值给咱们以前定义的路由就能够了。巧妙的实现了动态路由的清除。
如今咱们只须要调用resetRouter,就能获得一个空的路有实例,以后你就能够从新addRoutes你想要的路由了。完整的代码实例 router.jsresetRouter

Mock 数据

若是你在实际开发中,最理想的先后端交互方式固然是后端先帮咱们 mock 数据,而后前端开发。但现实很骨感,总会由于种种缘由,前端须要本身来 mock 假数据。尤为是个人几个开源项目,都是纯前端项目,根本没有后端服务。
在以前的文章中也介绍过,vue-element-adminvue-admin-template 使用的是 MockJSeasy-mock 这两个库。但实际用下来二者都有一些问题。

  • MockJs

    它的原理是: 拦截了全部的请求并代理到本地,而后进行数据模拟,因此你会发现 network 中没有发出任何的请求。但它的最大的问题是就是它的实现机制。它会重写浏览器的XMLHttpRequest对象,从而才能拦截全部请求,代理到本地。大部分状况下用起来仍是蛮方便的,但就由于它重写了XMLHttpRequest对象,因此好比progress方法,或者一些底层依赖XMLHttpRequest的库都会和它发生不兼容,能够看一下我项目的 issues,就知道多少人被坑了。

    它还有一个问题:由于是它是本地模拟数据,实际上不会走任何网络请求。因此本地调试起来很蛋疼,只能经过console.log来调试。就拿vue-element-admin来讲,想搞清楚 getInfo()接口返回了什么数据,只能经过看源码或者手动 Debug 才能知道。

  • Easy-Mock

    这个项目刚出的时候用的人比较少,还真的挺好用的。自然支持跨域,仍是支持MockJs的全部语法,我在以前也推荐过。但由于用的人多了,它的免费服务会常常的挂,能够说每天挂。。。但毕竟人家这是免费的服务,也不能苛求什么,官方的建议是本身搭建服务。若是你的公司总体搭建一个这样的 mock 服务的话也是一个不错的选择。但大部分人可能仍是没有这个技术条件的。

新方案

因此我一直在寻求一个更好的解决方案,我也去体验了其它不少 mock api 服务,如 mockapiMocky 等等。总之体验都不能知足个人需求。

v4.0版本以后,在本地会启动一个mock-server来模拟数据,线上环境仍是继续使用mockjs来进行模拟(由于本项目是一个纯前端项目,你也能够本身搭建一个线上 server 来提供数据)。无论是本地仍是线上因此的数据模拟都是基于mockjs生成的,因此只要写一套 mock 数据,就能够在多环境中使用。

该方案的好处是,在保留 mockjs的优点的同时,解决以前的痛点。因为咱们的 mock 是彻底基于webpack-dev-serve来实现的,因此在你启动前端服务的同时,mock-server就会自动启动,这里还经过 chokidar 来观察 mock 文件夹内容的变化。在发生变化时会清除以前注册的mock-api接口,从新动态挂载新的接口,从而支持热更新。有兴趣的能够本身看一下代码 mock-server.js。因为是一个真正的server,因此你能够经过控制台中的network,清楚的知道接口返回的数据结构。而且同时解决了以前mockjs会重写 XMLHttpRequest对象,致使不少第三方库失效的问题。

在本地开发环境中基于webpack-dev-serveafter这个middleware中间件,在这里自动读取你的 mock文件,模拟出 REST API,它最大的好处是,彻底不须要什么额外的工做,彻底基于webpack-dev-serve就能实现。若是你仍是想单独启动一个serve也是能够的,彻底能够引入一个express或者其它插件来启动一个 mock-serve。

咱们模拟数据有了,如今要作的事情就是,将咱们的接口代理到咱们的 mock 服务上就行了,这里咱们使用webpack-dev-serve自带的 proxy进行接口代理。

proxy: {
      // xxx-api/login => mock/login
      [process.env.VUE_APP_BASE_API]: {
        target: `http://localhost:${port}/mock`,
        changeOrigin: true,
        pathRewrite: {
          ['^' + process.env.VUE_APP_BASE_API]: ''
        }
      }
    }

snippets 自动生成代码片断

平时平常工做中,作最多的就是写业务模块和组件。当每次新开一个view或者component的时候都须要手动建立一个新.vue文件,而后再建立<template><script><style>这些标签,仍是有些麻烦的。

因此在新版本中,基于plop,提供了几个基础模板,方便建立新的view或者component
执行以下命令:

npm run new

plop

如上面 gif 所示,如今只要轻松的点几回回车就能够轻松生成我要的基础代码片断。这里只是一个 demo,你彻底能够按照本身需求定制模板。老版本的vue-cli实现逻辑和它相似。

若是你以为配置太复杂,我推荐你能够安装如 Vue 2 Snippets VS Code插件。 这种代码片断在平时工做中仍是能提高很多开发效率的。

async/await or promise

本次更新中,我也将部分代码用了async/await的方式替代了原有的 promise方式,主要是 @/src/permission.js。有兴趣的你们本身能够经过 git-history 本身对比下,能够发现代码阅读性高了很多。 不过本项目中也并无把全部promiseasync/await替代。我来简单说一下个人见解。

6 个 Async/Await 优于 Promise 的方面,这篇文章不少人应该都看过,里面大部分观点我都是赞成的,大部分复杂场景下async/await的确是更优解。但相对的也不是全部的状况下都是async/await写起来让我更爽的。先说说我最不爽的地方是它的错误处理,try catch让这个代码结构看起来就很奇怪(固然也有不少人很喜欢这种错误处理形式。社区也是相对的解决方案相似go语言的风格,好比 await-to-js

[err, res] = await to(getInfo())
if(err) //do something

这个方案是不错,但还须要引入一个新的库,增长了学习成本,得不偿失。因此以我我的的习惯,当只有一个异步请求,且须要作错误处理的状况下,更倾向于使用 promise。好比

// promise
getInfo()
  .then(res => {
    //do somethings
  })
  .catch(err => {
    //do somethings
  })

// async/await
try {
  const res = await getInfo()
  //do somethings
} catch (error) {
  //do somethings
}

在有嵌套请求的状况下,确定是 async/await 更直观的。

// promise
a(() => {
  b(() => {
    c()
  })
})

// async/await
await a()
await b()
await c()

固然代码写的好与很差仍是取决于写代码的人的。好比一个常见的业务场景:有两个并发的异步请求,在都完成后do something。但不少人会错误的用串行的方式实现了。

//错误
await a()
await b()
//这样变成了 a().then(() => b() )
// a 好了才会执行 b
done()

//正确
await Promise.all([a(), b()])
done()

还有一个小细节async/await打包后的代码>)其实会比 promise 复杂不少, 固然这个是一个忽略不计得问题。

总结:我认为它们两我的并非or的关系,在特定的业务场景下,选择相对而言代码可读性更好地解决方案。

以上所述纯我的偏心,并不是什么最佳实现。具体该怎么选择仍是须要你们更具本身团队的风格或者本身的理解来判断。

命名规范

其实刚开始我写 vue 文件的时候也不注意,各类驼峰啊、大写开头 (PascalCase)仍是横线链接 (kebab-case)混着来,谁叫 vue 均可以,在 风格指南 中也没有定论。不过基于本项目我仍是整理了一套文件的命名规则。

Component

全部的Component文件都是以大写开头 (PascalCase),这也是官方所 推荐的

但除了 index.vue

例子:

  • @/src/components/BackToTop/index.vue
  • @/src/components/Charts/Line.vue
  • @/src/views/example/components/Button.vue

JS 文件

全部的.js文件都遵循横线链接 (kebab-case)。

例子:

  • @/src/utils/open-window.js
  • @/src/views/svg-icons/require-icons.js
  • @/src/components/MarkdownEditor/default-options.js

Views

views文件下,表明路由的.vue文件都使用横线链接 (kebab-case),表明路由的文件夹也是使用一样的规则。

例子:

  • @/src/views/svg-icons/index.vue
  • @/src/views/svg-icons/require-icons.js

使用横线链接 (kebab-case)来命名views主要是出于如下几个考虑。

  • 横线链接 (kebab-case) 也是官方推荐的命名规范之一 文档
  • views下的.vue文件表明的是一个路由,因此它须要和component进行区分(component 都是大写开头)
  • 页面的url 也都是横线链接的,好比https://www.xxx.admin/export-excel,因此路由对应的view应该要保持统一
  • 没有大小写敏感问题

CDN

你能够经过执行npm run preview -- --report来分析webpack打包以后的结果,观察各个静态资源的大小。你能够发现占用空间最多的是第三方依赖。如vueelement-uiECharts等。

你可使用 CDN 外链的方式引入第这些三方库,这样能大大增长构建的速度(经过 CDN 引入的资源不会经 webpack 打包)。若是你的项目没有本身的CDN服务的话,使用一些第三方的CDN服务,如 jsdelivrunpkg 等是一个很好的选择,它提供过了免费的资源加速,同时提供了缓存优化,因为你的第三方资源是在html中经过script引入的,它的缓存更新策略都是你本身手动来控制的,省去了你须要优化缓存策略功夫。

不少文章说使用 CDN 引入的方式能大大减少代码的体积,这是不可能的。虽然打包完的 bundle小了,但那部分代码只是被你拆出去,用CDN的方式引入罢了。你想减少体积,最高效的方案是启用GZIP

我我的暂时不使用CDN引入第三方依赖的缘由:

暂时构建速度尚未遇到什么瓶颈,全部没有必要单独剥离部分第三方依赖。使用CDN引入的方式等于一些第三方依赖的版本你是经过package.json来控制的,一些依赖则须要手动维护,增长了一些维护成本。目前基于 webpack 的optimization.splitChunks已经作了资源的缓存优化,静态资源的缓存已经作得很好了。而且目前全部的静态资源都会上传到本身的CDN服务,没有必要使用第三方的CDN服务。

固然全部的优化都是须要结合本身的具体业务来调整的! 以后可能会采用这种引入方式,或者使用webpack dll的方式进行优化。若是你以为CDN引入对于的项目有益处,你能够遵循以下方法进行修改:

使用方式

先找到 vue.config.js, 添加 externalswebpack 不打包 vueelement

externals: {
  vue: 'Vue',
  'element-ui':'ELEMENT'
}

而后配置那些第三方资源的CDN,请注意前后顺序。

const cdn = {
  css: [
    // element-ui css
    'https://unpkg.com/element-ui/lib/theme-chalk/index.css'
  ],
  js: [
    // vue must at first!
    'https://unpkg.com/vue/dist/vue.js',
    // element-ui js
    'https://unpkg.com/element-ui/lib/index.js'
  ]
}

以后经过 html-webpack-plugin注入到 index.html之中:

config.plugin('html').tap(args => {
  args[0].cdn = cdn
  return args
})

找到 public/index.html。经过你配置的CND Config 依次注入 css 和 js。

<head>
  <!-- 引入样式 -->
  <% for(var css of htmlWebpackPlugin.options.cdn.css) { %>
    <link rel="stylesheet" href="<%=css%>">
  <% } %>
</head>

<!-- 引入JS -->
<% for(var js of htmlWebpackPlugin.options.cdn.js) { %>
  <script src="<%=js%>"></script>
<% } %>

完整的 代码修改

最终你可使用 npm run preview -- --report 查看效果 如图:

同理,其它第三方依赖均可以使用相同的方式处理(好比vuexvue-router等)。固然你也能够选择使用 DLLPlugin的方式来处理第三方依赖,从而来优化构建。

小技巧与建议

Watch immediate

这个已经算是一个比较常见的技巧了,这里就简单说一下。当 watch 一个变量的时候,初始化时并不会执行,以下面的例子,你须要在created的时候手动调用一次。

// bad
created() {
  this.fetchUserList();
},
watch: {
  searchText: 'fetchUserList',
}

你能够添加immediate属性,这样初始化的时候也会触发,而后上面的代码就能简化为:

// good
watch: {
  searchText: {
    handler: 'fetchUserList',
    immediate: true,
  }
}

ps: watch 还有一个容易被你们忽略的属性deep。当设置为true时,它会进行深度监听。简而言之就是你有一个 const obj={a:1,b:2},里面任意一个 key 的 value 发生变化的时候都会触发watch。应用场景:好比我有一个列表,它有一堆query筛选项,这时候你就能deep watch它,只有任何一个筛序项改变的时候,就自动请求新的数据。或者你能够deep watch一个 form 表单,当任何一个字段内容发生变化的时候,你就帮它作自动保存等等。

Attrs 和 Listeners

这两个属性是 vue 2.4 版本以后提供的,它简直是二次封装组件或者说写高阶组件的神器。在咱们平时写业务的时候免不了须要对一些第三方组件进行二次封装。好比咱们须要基于el-select分装一个带有业务特性的组件,根据输入的 name 搜索用户,并将一些业务逻辑分装在其中。但el-select这个第三方组件支持几十个配置参数,咱们固然能够适当的挑选几个参数经过 props 来传递,但万一哪天别人用你的业务组件的时候以为你的参数少了,那你只能改你封装的组件了,亦或是哪天第三方组件加入了新参数,你该怎么办?

其实咱们的这个组件只是基于el-select作了一些业务的封装,好比添加了默认的placeholder,封装了远程 ajax 搜索请求等等,总的来讲它就是一个中间人组件,只负责传递数据而已。

这时候咱们就可使用v-bind="$attrs":传递全部属性、v-on="$listeners"传递全部方法。以下图所示:

这样,咱们没有在$props中声明的方法和属性,会经过$attrs$listeners直接传递下去。这两个属性在咱们平时分装第三方组件的时候很是有用!

.sync

这个也是 vue 2.3 以后新加的一个语法糖。这也是平时在分装组件的时候很好用的一个语法糖,它的实现机制和v-model是同样的。

当你有须要在子组件修改父组件值的时候这个方法很好用。
线上例子

Computed 的 get 和 set

computed 你们确定都用过,它除了能够缓存计算属性外,它在处理传入数据和目标数据格式不一致的时候也是颇有用的。set、get 文档

上面说的可能仍是是有点抽象,举一个简单的的例子:咱们有一个 form 表单,from 里面有一个记录建立时间的字段create_at。咱们知道前端的时间戳都是 13 位的,但不少后端默认时间戳是 10 位的,这就很蛋疼了。前端和后端的时间戳位数不一致。最多见的作法以下:

上面的代码主要作的是:在拿到数据的时候将后端 10 位时间戳转化为 13 位时间戳,以后再向服务端发送数据的时候再转化回 10 位时间戳传给后端。目前这种作法固然是可行的,但以后可能不只只有建立接口,还有更新接口的时候,你还须要在update的接口里在作一遍一样数据转化的操做么?并且这只是一个最简单的例子,真实的 form 表单会复杂的多,须要处理的数据也更为的多。这时候代码就会变得很难维护。

这时候就可使用 computed 的 set 和 get 方法了。

经过上面的代码能够看到,咱们把须要作先后端兼容的数据,放在了 computed 中,从 getDatasubmit中隔离了数据处理的部分。

固然上面说的方案还不是最好的方案,你其实应该利用以前所说的v-bind="$attrs"v-on="$listeners"对时间选择器组件进行二次封装。例如这样<date-time v-model="postForm.create_at" /> 外部无需作任何数据处理,直接传入一个 10 位的时间戳,内部进行转化。当日期发生变化的时候,自动经过emit触发input使v-model发生变化,把全部脏活累活都放在组件内部完成,保持外部业务代码的相对干净。具体 v-model 语法糖原理能够见官方 文档

set 和 get 处理能够作上面说的进行一些数据处理以外,你也能够把它当作一个 watch的升级版。它能够监听数据的变化,当发生变化时,作一些额外的操做。最经典的用法就是v-model上绑定一个 vuex 值的时候,input 发生变化时,经过 commit更新存在 vuex 里面的值。

具体的解释你也能够见官方 文档

Object.freeze

这算是一个性能优化的小技巧吧。在咱们遇到一些 big data的业务场景,它就颇有用了。尤为是作管理后台的时候,常常会有一些超大数据量的 table,或者一个含有 n 多数据的图表,这种数据量很大的东西使用起来最明显的感觉就是卡。但其实不少时候其实这些数据其实并不须要响应式变化,这时候你就可使用 Object.freeze 方法了,它能够冻结一个对象(注意它不并是 vue 特有的 api)。

当你把一个普通的 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象全部的属性,并使用 Object.defineProperty 把这些属性所有转为 getter/setter,它们让 Vue 能进行追踪依赖,在属性被访问和修改时通知变化。
使用了 Object.freeze 以后,不只能够减小 observer 的开销,还能减小很多内存开销。相关 issue

使用方式:this.item = Object.freeze(Object.assign({}, this.item))

这里我提供了一个在线测速 demo,点我

经过测速能够发现正常状况下1000 x 10 rerender 都稳定在 1000ms-2000ms 之间,而开启了Object.freeze的状况下,rerender 都稳住在 100ms-200ms 之间。有接近 10 倍的差距。因此能肯定不须要变化检测的状况下,big data 仍是要优化一下的。

Functional

函数式组件 这个是文档里就写的内容,但在其实不多人会刻意的去使用。由于你不用它,代码也不会有任何问题,用了到可能会出现 bug。

咱们先看一个例子:点我测试性能 肉眼可见的性能差距。固然不少人会以为个人项目中也没有这种变化量级,但我以为这是一个程序员的自我修养问题吧。,好比能用v-show的地方就不要用v-if,善用keep-alivev-onceObject.freeze()处理 vue big data 问题等。虽然都是一些小细节,但对性能和体验都是有很多的提高的。更多的性能优化技巧请查看该文章 vue-9-perf-secrets

减小全局操做

这其实并不仅是针对 vue 项目的一个建议,咱们平时写代码的时候必定要尽可能避免一些全局的操做。若是必需要用到的时候,必定要本身检查,会不会产生一些全局的污染或者反作用。

举几个简单例子:

  1. 咱们如今虽然用 vue 写代码了,核心思想转变为用数据驱动 view,不用像jQuery时代那样,频繁的操做 DOM 节点。但仍是免不了有些场景仍是要操做 DOM 的。咱们在组件内选择节点的时候必定要切记避免使用 document.querySelector()等一系列的全局选择器。你应该使用this.$el或者this.refs.xxx.$el的方式来选择 DOM。这样就能将你的操做局限在当前的组件内,能避免不少问题。
  2. 咱们常常会不可避免的须要注册一些全局性的事件,好比监听页面窗口的变化window.addEventListener('resize', this.__resizeHandler),但再声明了以后必定要在 beforeDestroy或者destroyed生命周期注销它。window.removeEventListener('resize', this.__resizeHandler)避免形成没必要要的消耗。
  3. 避免过多的全局状态,不是全部的状态都须要存在 vuex 中的,应该根据业务进行合理的进行取舍。若是不可避免有不少的值须要存在 vuex 中,建议使用动态注册的方式。相关文档。只是部分业务须要的状态处理,建议使用 Event Bus或者使用 简单的 store 模式
  4. css 也应该尽可能避免写太多的全局性的样式。除了一些全局公用的样式外,因此针对业务的或者组件的样式都应该使用命名空间的方式或者直接使用 vue-loader 提供的 scoped写法,避免一些全局冲突。文档

Sass 和 Js 之间变量共享

这个需求可能有些人没有遇到过,举个实际例子来讲明一下。


如上面要实现一个动态的换肤,就须要将用户选择的 theme 主题色传递给 css。但同时初始化的时候 css 又须要将一个默认主题色传递给 js。因此下面咱们就分两块来说解。

  • js 将变量传递给 sass
    这部分是相对简单就能够实现的,实现方案也不少。最简单的方法就是经过 在模板里面写 style 标签来实现,就是俗话所说的内联标签。

    <div :style="{'background-color':color}" ></div>

    或者使用 css var(),在线 demo,还有用 less 的话modifyVars,等等方案都能实现 js 与 css 的变量传递。

  • sass 将变量给 js

仍是那前面那个换肤来举例子,咱们页面初始化的时候,总须要一个默认主题色吧,假设咱们在 var.scss中声明了一个 theme:blue,咱们在 js 中该怎么获取这个变量呢?咱们能够经过 css-modules :export来实现。更具体的解释-How to Share Variables Between Javascript and Sass

// var.scss
$theme: blue;

:export {
  theme: $theme;
}
// test.js
import variables from '@/styles/var.scss'
console.log(variables.theme) // blue

当 js 和 css 共享一个变量的时候这个方案仍是很实用的。vue-element-admin 中的侧边栏的宽度,颜色等等变量都是经过这种方案来实现共享的。

其它换肤方案能够参考 聊一聊前端换肤

自动注册全局组件

个人业务场景大部分是中后台,虽然封装和使用了不少第三方组件,但仍是免不了须要本身封装和使用不少业务组件。但每次用的时候还须要手动引入,真的是有些麻烦的。

咱们其实能够基于 webpack 的require.context来实现自动加载组件并注册的全局的功能。相关原理在以前的文章中已经阐述过了。具体代码以下

咱们能够建立一个GlobalComponents文件夹,将你想要注册到全局的组件都放在这个文件夹里,在index.js里面放上如上代码。以后只要在入口文件main.js中引入便可。

//main.js
import './components/Table/index' // 自动注册全局业务组件

这样咱们能够在模板中直接使用这些全局组建了。不须要再繁琐的手动引入了。

<template>
  <div>
    <user-select/>
    <status-button/>
  </div>
</template>

固然你也不要为了省事,啥组件都往全局注册,这样会让你初始化页面的时候你的初始init bundle很大。你应该就注册那些你常用且体积不大的组件。那些体积大的组件,如编辑器或者图表组件仍是按需加载比较合理。并且你最好声明这些全局组件的时候有一个统一的命名规范好比:globel-user-select这样的,指定一个团队规范,否则人家看到你这个全局组件会一脸懵逼,这个组件是哪来的。

Lint

这又是一个老生常谈的问题了
vue 的一些最佳实践什么的话,这里不讨论了,我以为看官方的 风格指南 差很少就够了。好比避免避免 v-if 和 v-for 用在一块儿元素特性的顺序这些等等规则,几十条规则,说真的写了这么久 vue,我也只能记住一些常规的。什么属性的顺序啊,不太可能记住的。这种东西仍是交给程序来自动优化才是更合理的选择。强烈推荐配置编辑器自动化处理。具体配置见 文档。同时建议结合 Git Hooks 配合在每次提交代码时对代码进行 lint 校验,确保全部提交到远程仓库的代码都符合团队的规范。它主要使用到的工具是huskylint-staged,详细文档见 Git Hooks

Hook

这个是一个文档里没有写的 api,但我以为是一个颇有用的 api。好比咱们平时使用一些第三方组件,或者注册一些全局事件的时候,都须要在mounted中声明,在destroyed中销毁。但因为这个是写在两个生命周期内的,很容易忘记,并且大部分在建立阶段声明的内容都会有反作用,若是你在组件摧毁阶段忘记移除的话,会形成内存的泄漏,并且都不太容易发现。以下代码:

react 在新版本中也加入了useEffect,将之前的多个 life-cycles 合并、重组,使逻辑更加清晰,这里就不展开了。那 vue 是否是也能够这样作?我去了看了一下官方的 vue-hooks源码 发现了一个新的 api:$on('hook:xxx')。有了它,咱们就能将以前的代码用更简单和清楚地方式实现了。

和 react 的useEffect有殊途同归之妙。

并且咱们有了这个 api 以后,能干的事情还不止这个。有时候咱们会用一些第三方组件,好比咱们有一个编辑器组件(加载比较慢,会有白屏),因此咱们在它渲染完成以前须要给它一个占位符,但可能这个组件并无暴露给咱们这个接口,固然咱们须要修改这个组件,在它建立的时候手动 emit 一个事件出去,而后在组件上监听它,好比:

固然这也是可行的,但万一还要监听一个更新或者摧毁的生命周期呢?其实利用 hook能够很方便的实现这个效果。

固然在 vue 3.0 版本中可能会有新的写法,就不以下面的讨论: Dynamic Lifecycle Injection。有兴趣的能够自行去研究,这里就不展开了。当 3.0 正式发布以后再来讨论吧。

RoadMap

最后来讲一下,以后须要作的事情吧:

  • 更好的多级页面缓存:目前页面的缓存基于keep-alive,但当三级路由嵌套的状况下,支持的并很差。以后探索一个更好的解决方案。
  • 单元测试:当项目大了以后,没有单元测试维护起来仍是有些吃力的。
    以后会慢慢补上unit-test 的测试用例。 酌情加上一些e2e-test的例子。
  • 去国际化:其实大部分人是不须要国际化的,默认状况下移除国际化。单独开一个国际化分支(v4.1 已完成)。
  • 适配 webpack5:webpack5 仍是解决了很多以前的痛点的,正式版发布以后会进行升级。
  • vue 3.0: 等官方发布以后会基于新版本进行重构(这个或许还有好久)
  • 适配 element-ui 3.0 以前官方发了 3.0 的打算(我也不知道会不会跳票)

总结

开源不易,且行且珍惜吧。

系列文章: