Vue 全家桶、原理及优化简议

待我称王封你为后i 2022-02-21 00:15 256阅读 0赞

小编推荐:Fundebug提供JS错误监控、微信小程序错误监控、微信小游戏错误监控,Node.j错误监控s和Java错误监控。真的是一个很好用的错误监控费服务,众多大佬公司都在使用。

不少互联网公司都在使用vue技术栈,或称为vue全家桶。

使用过vue的程序员一般这样评价它,“vue.js兼具angular.js和react.js的优点”。Vue.js 是一个JavaScript MVVM(Model-View-ViewModel)库,用于渐近式构建用户界面。它以数据驱动和组件化思想构建,采用自底向上增量开发的设计思想。相比Angular.js,Vue.js API更加简洁;相比 React + Redux 复杂的架构,Vue.js 上手更加容易。

目录

一、vue全家桶包括什么

  1. vue-router路由
  2. vuex
  3. vue-resource
  4. 构建工具vue-cli
  5. 调度工具Devtools
  6. 关于UI组件库

二、vue工程目录结构

  1. 编辑器

三、vue使用简介

  1. 数据代理
  2. vue实例生命周期图解

四、vue的运行原理

  1. 双向绑定图解
  2. 模板是如何解析的

五、发布前优化

  1. UI组件按需加载
  2. 路由懒加载
  3. 使用异步组件(动态组件)
  4. 图片压缩与合并
  5. 使用CDN加速vue类库
  6. 压缩代码
  7. v-forv-if不要同时使用
  8. 使用Object.freeze冻结大数据
  9. 使用Keep-alive标签优化组件创建
  10. 使用Set
  11. scope中少用元素选择器
  12. 关于template的优化

一、vue全家桶包括什么

vue-router路由

网站:http://router.vuejs.org。使用npm工具来安装vue-router

npm install vue-router

通过import导入Vue模块、vue-router模块及其它组件。

import Vue from’vue’

importRouter from’vue-router’

在使用路由前,必须要通过 Vue.use() 明确地安装路由功能。

Vue.use(Router)

通过const router= new VueRouter()定义路由,并传入对应的配置,包括路径path和组件components等。

![Image 1][]

image

在使用newVue来创建和挂载vue根实例的时候,记得要通过 router配置参数注入路由。使用router-link:

![Image 1][]

image

有两种模式:

  • hash 模式
  • history 模式

vuex

网站:http://vuex.vuejs.org

在vue开发实战中,多个组件共享数据时,单向数据流的简洁性很容易被破坏。为解决多个视图使用同一数据及多个视图驱动同一数据更新的问题,vuex应运而生。

当网站足够大时,一个状态树下,根的部分字段繁多,解决这个问题就要模块化 vuex,官网提供了模块化方案,允许我们在初始化 vuex 的时候配置 modules。每一个 module 里面又分别包含 state 、action 等,看似是多个状态树,其实还是基于 rootState 的子树。细分后整个 state 结构就清晰了,管理起来也方便许多。

![Image 1][]

image

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 的四个核心概念是:

  • The state tree:Vuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个唯一数据源(SSOT)而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。
  • Getters:用来从 store 获取 Vue 组件数据。
  • Mutators:事件处理器用来驱动状态的变化。
  • Actions:可以给组件使用的函数,以此用来驱动事件处理器 mutations。(注:此许或许称之为EventHandler更为恰当。)

Vuex和简单的全局对象是不同的。当Vuex从store中读取状态值的时候,若状态发生了变化,那么相应的组件也会更新。并且改变store中状态的唯一途径就是提交commit mutations。只要发生了状态的变化,一定伴随着mutation的提交。 例如:

![Image 1][]

image

通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更:

image

由于 vuex 的灵活性,带来了编码不统一的情况,完整的闭环是 store.dispatch(‘action’) -> action -> commit -> mutation -> getter -> computed,实际上中间的环节有的可以省略,因为 API 文档提供了以下几个方法 mapState、mapGetters、mapActions、mapMutations,然后在组件里可以直接调取任何一步,还是项目小想怎么调用都可以,项目大的时候,就要考虑 vuex 使用的统一性,有人建议是不论多简单的流程都跑完整个闭环,形成代码的统一,方便后期管理,在组件里只允许出现 dispatch 和 mapGetters,其余的流程都在名为 store 的 vuex 文件夹里进行。

注:mapGetters 工具函数会将 store 中的 getter 映射到局部计算属性中。它的功能和 mapState 非常类似。

vue-resource

网站:https://github.com/pagekit/vue-resource

使用npm来安装Vue-resource:

$ npm install vue-resource

在安装并引入vue-resource后,可以基于全局的Vue对象使用http,也可以基于某个Vue实例使用http。

![Image 1][]

image

在发送请求后,使用then方法来处理响应结果,then方法有两个参数,第一个参数是响应成功时的回调函数,第二个参数是响应失败时的回调函数。

vue-resource的请求API是按照REST风格设计的,它提供了7种请求API:

· get(url,[options])

· head(url,[options])

· delete(url,[options])

· jsonp(url,[options])

· post(url,[body], [options])

· put(url, [body],[options])

· patch(url,[body], [options])

构建工具vue-cli

vue-cli是vue标准的开发工具。网站:https://cli.vuejs.org/

安装

npm install -g @vue/cli

最新版本为3.4.0。

创建项目

vue create my-project

以上是命令行创建。也可以通过 vue ui 命令以图形化界面创建和管理项目:

vue ui

运行

npm run serve

调度工具Devtools

vue在调试方面,可以选择安装chrome插件vue Devtools。打开vue项目,在调试vue应用的时候,chrome开发者工具中会看一个vue的一栏,点击之后就可以看见当前页面vue对象的一些信息。

![Image 1][]

image

在Devtools工具中,可以选择组件,查看对应组件内的数据信息。也可以选择Vuex选项,查看该项目内Vuex的状态变量信息。

![Image 1][]

image

关于UI组件库

可以自己写,为提高开发效率也可以复用第三方组件库。element(https://github.com/ElemeFE/element)是一个最好支持vue2.0的UI组件库。

二、vue工程目录结构

这是一个简单的vue项目的大概结构:

![Image 1][]

image

  • components/文件夹:用来存放Vue 组件。个人建议,把每一个组件中使用到的image图片放置到对应的组件子文件目录下,便于统一的管理
  • Node_modules/:npm安装的该项目的依赖库
  • vuex/文件夹:存放的是和 Vuex store 相关的东西(state对象,actions,mutations)
  • router/文件夹:存放的是跟vue-router相关的路由配置项
  • build/文件:是 webpack 的打包编译配置文件
  • static/文件夹:存放一些静态的、较少变动的image或者css文件
  • config/文件夹:存放的是一些配置项,比如服务器访问的端口配置等
  • dist/该文件夹:一开始是不存在,在我们的项目经过 build 之后才会产出
  • App.vue根组件,所有的子组件都将在这里被引用
  • index.html整个项目的入口文件,将会引用我们的根组件 App.vue
  • main.js入口文件的 js 逻辑,在webpack 打包之后将被注入到 index.html 中

编辑器

VSCode with Vetur

![Image 1][]

image

三、vue使用简介

数据代理

每个 Vue.js 应用都是通过构造函数 Vue 创建一个 Vue 的根实例 启动的。每个 Vue 实例都会代理其 data 对象里所有的属性:

var data = { a: 1 }

var vm = new Vue({

data: data

})

vm.a === data.a // -> true

设置新值也会同步影响:

vm.a = 2

data.a // -> 2

// … 反之亦然

data.a = 3

vm.a // -> 3

实现数据代理的伪代码如下:

var self = this; // this为vue实例, 即vm

Object.keys(this.data).forEach(function(key) {

  1. Object.defineProperty(this, key, { // this.title, 即vm.title
  2. enumerable: false,
  3. configurable: true,
  4. get: function getter () {
  5. return self.data[key]; //触发对应data[key]的getter
  6. },
  7. set: function setter (newVal) {
  8. self.data[key] = newVal; //触发对应data[key]的setter
  9. }
  10. });

}

Vue 实例暴露了一些有用的实例属性与方法。这些属性与方法都有前缀 $,以便与代理的 data 属性区分。例如:

vm.$data === data // -> true

vm.$el === document.getElementById(‘example’) // -> true

vue实例生命周期图解

![Image 1][]

image

四、vue的运行原理

Vue采用简洁的模板语法,以声明的方式将数据渲染进 DOM。vue代码是没有办法直接被浏览器解析的,必须经过“编译”,变为浏览器可以识别为html、js与css代码。这种声明式开发方式把方便留给了程序员,转换工作交给了自动化工具。

![Image 1][]

image

注:el是element的缩写,指Vue实例挂载的元素节点。

双向绑定图解

一般说的双向绑定,指:

  • 数据变动 —> 视图更新
  • 视图更新 —> 数据变动

视图更新 —> 数据变动,这个方向的绑定比较简单。主要通过事件监听来改变数据,比如input控件可以监听input事件,一旦事件触发,调用JS改变data。

![Image 1][]

image

模型层(model)只是普通 JavaScript 对象,修改它,DOM本是不能更新的。当程序员把一个普通 JavaScript 对象传给 Vue 实例的 data 选项,Vue 将遍历此对象所有的属性,并使用 Object.defineProperty 把这些属性全部转为 getter/setter。在每个setter中,可以做许多事件,使表面看起来数据变了,视图就更新了。并且这种数据更新,和原来一样,只是 vm.a=123 这样的简单更新。

![Image 1][]

image

如上所求,每个vue组件实例都有相应的 watcher 实例对象,它会在vue组件渲染的过程中把需要用到的属性(getter)记录为依赖。之后,当依赖项的 setter 被(其它JS代码)调用时,setter 会通知 watcher 重新计算,从而致使它关联的组件得以更新。

此处实现的是一个观察者模式。

通过object.defineProperty遍历设置this.data里面所有属性,在每个属性的setter里面去通知对应的回调函数,这里的回调函数包括dom视图重新渲染的函数、使用$watch添加的回调函数等,这样我们就通过object.defineProperty劫持了数据,当我们对数据重新赋值时,如this.title = ‘hello vue’,就会触发setter函数,从而触发dom视图重新渲染的函数,实现数据变动,对应视图更新。

那么,如何在setter里面触发所有绑定该数据的回调函数呢?

既然绑定该数据的回调函数不止一个,我们就把所有的回调函数放在一个数组里面,一旦触发该数据的setter,就遍历数组触发里面所有的回调函数,我们把这些回调函数称为订阅者。数组最好就定义在setter函数的最近的上级作用域中,如下面实例代码所示。

  1. > Object.keys(this.data).forEach(function(key) {
  2. >
  3. > var subs = []; // 在这里放置添加所有订阅者的数组
  4. >
  5. > Object.defineProperty(this.data, key, { // this.data.title
  6. >
  7. > enumerable: false,
  8. >
  9. > configurable: true,
  10. >
  11. > get: function getter () {
  12. >
  13. > console.log('访问数据啦啦啦')
  14. >
  15. > return this.data[key]; //返回对应数据的值
  16. >
  17. > },
  18. >
  19. > set: function setter (newVal) {
  20. >
  21. > if (newVal === this.data[key]) {
  22. >
  23. > return; // 如果数据没有变动,函数结束,不执行下面的代码
  24. >
  25. > }
  26. >
  27. > this.data[key] = newVal; //数据重新赋值
  28. >
  29. > subs.forEach(function () {
  30. >
  31. > // 通知subs里面的所有的订阅者
  32. >
  33. > })
  34. >
  35. > }
  36. >
  37. > });
  38. >
  39. > }

那么,怎么把绑定数据的所有回调函数放到一个数组里面呢?这是通过gettter内部的代码完成的。

我们知道只要访问数据就会触发对应数据的getter,那我们可以先设置一个全局变量target,如果我们要在data里面title属性添加一个订阅者(changeTitle函数),我们可以先设置target = changeTitle,把changeTitle函数缓存在target中,然后访问this.title去触发title的getter,在getter里面把target这个全局变量的值添加到subs数组里面,添加完成后再把全局变量target设置为null,以便添加其他订阅者。

伪代码如下:

  1. > target = changeTitle
  2. >
  3. > ...
  4. >
  5. > Object.keys(this.data).forEach(function(key) {
  6. >
  7. > var subs = []; // 在这里放置添加所有订阅者的数组
  8. >
  9. > Object.defineProperty(this.data, key, { // this.data.title
  10. >
  11. > enumerable: false,
  12. >
  13. > configurable: true,
  14. >
  15. > get: function getter () {
  16. >
  17. > console.log('访问数据啦啦啦')
  18. >
  19. > if (target) {
  20. >
  21. > subs.push(target);
  22. >
  23. > }
  24. >
  25. > return this.data[key]; //返回对应数据的值
  26. >
  27. > },
  28. >
  29. > set: function setter (newVal) {
  30. >
  31. > if (newVal === this.data[key]) {
  32. >
  33. > return; // 如果数据没有变动,函数结束,不执行下面的代码
  34. >
  35. > }
  36. >
  37. > this.data[key] = newVal; //数据重新赋值
  38. >
  39. > subs.forEach(function () {
  40. >
  41. > // 通知subs里面的所有的订阅者
  42. >
  43. > })
  44. >
  45. > }
  46. >
  47. > });
  48. >
  49. > }

上面代码中提到的changeTitle,即是上面最近一张图解中的watcher。vue通过getter收集watcher集合。因为vue充许在运行时添加代码,所以该收集行为不能仅限制于模板“编译”之前。(注:vue中是不存在严格的编译的,js是解析执行型语言,像C、Go等语言将源码编译为目标平台的二进制文件,才是真的编译。)

模板是如何解析的

假如说有下面这一段代码,我们怎么把它解析成对应的html呢?

  1. > <input v-model="title">
  2. >
  3. > <h1>{
  4. {title}}</h1>
  5. >
  6. > <button v-on:click="changeTitle">change title<button>

注:该示例实现的效果是,在input输入框内输入任何内容,下方h1文本同步更新。

先简单介绍视图更新函数的用途,比如解析指令v-model=”title”,v-on:click=”changeTitle”,还有把{ {title}}替换为对应的数据等。

回到上面那个问题,如何解析模板?我们只要去遍历所有dom节点包括其子节点:

  • 如果节点属性含有v-model,视图更新函数就为把input的value设置为title的值
  • 如果节点为文本节点,视图更新函数就为先用正则表达式取出大括号里面的值’title’,再设置文本节点的值为data[‘title’]
  • 如果节点属性含有v-on:xxxx,视图更新函数就为先用正则获取事件类型为click,然后获取该属性的值为changeTitle,则事件的回调函数为this.methods[‘changeTitle’],接着用addEventListener监听节点click事件。

五、发布前优化

使用vue-cli部署生产包时,发现资源包很大,打包后的vendor.js达到了1M+。

UI组件按需加载

如果使用了第三方组件/UI库,如element-ui, mint-ui,echarts等,如果全部引入,项目体积非常大,这时可以按需引入组件。

安装 babel-plugin-component

npm install babel-plugin-component -D

然后,将.babelrc 修改为:

  1. > {
  2. >
  3. > "presets": [["es2015", { "modules": false }]],
  4. > "plugins": [
  5. > [
  6. > "component",
  7. > {
  8. > "libraryName": "element-ui",
  9. > "styleLibraryName": "theme-chalk"
  10. > }
  11. > ]
  12. > ]
  13. > }

然后引入部分组件,这样一来,就不需要引入样式了,插件会帮我们处理。

  1. > // main.js
  2. > import Vue from 'vue'
  3. > import { Dialog, Loading } from 'element-ui'
  4. > Vue.use(Dialog)
  5. > Vue.use(Loading.directive)
  6. > Vue.prototype.$loading = Loading.service
  7. > // 然后正常使用组件

注:Babel是一个广泛使用的转码器,可以将ES6代码转为ES5代码。让不支持ES6的宿主环境,支持使用一套源码开发。

mint-ui是element-ui的移动端组件,所以它的使用和引入几乎和element-ui一样。

路由懒加载

vue-router官方推荐syntax-dynamic-import插件,不过它要求同时安装@bable/core^7.0.0,如果你安装了babel-core6,可能有版本冲突的,解决方法如下:

npm install babel-plugin-syntax-dynamic-import —save-dev(^6.18.0)

当打包构建应用时,Javascript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。结合 Vue 的异步组件和 Webpack 的代码分割功能,轻松实现路由组件的懒加载。

  1. > // router.js
  2. > const login = () => import('@/components/login')
  3. > const router = new VueRouter({
  4. > routes: [
  5. > { path: '/login', component: login }
  6. > ]
  7. > })

还有一种魔法注释用法,不推荐使用。

使用异步组件(动态组件)

app bundle 文件过大,可以尝试通过组件懒加载优化。

动态组件主页面加载是不会加载,等到触发条件时才加载该组件,并且加载一次后就有缓存。如果组件在页面加载时不需要,只在调用时用到,这时可以使用异步组件的写法。仅仅是引入和组件注册写法不同:

  1. > // template
  2. > <test v-if="showTest"></test>
  3. >
  4. > // script
  5. >
  6. > components: {
  7. > test: () => import('./test') // 将组件异步引入,告诉webpack,将该部分代码分割打包
  8. > },
  9. >
  10. > methods:{
  11. > clickTest () {
  12. > this.showTest = !this.showTest
  13. > }
  14. > }

图片压缩与合并

无损压缩图片:https://tinypng.com/。可以将图片制成雪碧精灵图。

使用CDN加速vue类库

一般项目里用到的第三方js库主要有:vue、vue-router、vuex、vue-resource、axio、qiniu等。这些依赖库的js文件被一起打包到vender那个js文件里面,导致vender这个文件很大,那首屏加载速度肯定会被拖慢。

类库文件使用cdn加速

  1. > <!-- built files will be auto injected -->
  2. >
  3. > <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script>
  4. >
  5. > <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script>
  6. >
  7. > <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script>
  8. >
  9. > <script src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.min.js"></script>

修改 build/webpack.base.conf.js

  1. > module.exports = {
  2. >
  3. > context: path.resolve(__dirname, '../'),
  4. >
  5. > entry: {
  6. >
  7. > app: './src/main.js'
  8. >
  9. > },
  10. >
  11. > externals:{
  12. >
  13. > 'vue': 'Vue',
  14. >
  15. > 'vue-router': 'VueRouter',
  16. >
  17. > 'vuex':'Vuex',
  18. >
  19. > 'vue-resource': 'VueResource'
  20. >
  21. > }

排除已经手动收入的js文件

利用webpack的externals。具体做法就是在 build/webpack.base.conf.js文件的module里面与rules同层加入externals。具体做法,修改src/main.js src/router/index.js 注释掉import引入的vue,vue-resource等:

  1. > // import Vue from 'vue'
  2. >
  3. > // import VueResource from 'vue-resource'
  4. >
  5. > // Vue.use(VueResource)

上面已经引用过。

压缩代码

vue-cli已经使用UglifyJsPlugin 插件来压缩代码,可以设置成如下配置:

  1. > new webpack.optimize.UglifyJsPlugin({
  2. >
  3. > compress: {
  4. >
  5. > warnings: false,
  6. >
  7. > drop_console: true,
  8. >
  9. > pure_funcs: ['console.log']
  10. >
  11. > },
  12. >
  13. > sourceMap: false
  14. >
  15. > })

其中sourceMap: false是禁用除错功能。如果设为true,在部署包中会生成.map结尾的js文件。它用于在代码混淆压缩的情况下仍可进行调试。这个功能虽好,但会大大增加整体资源包的体积,所以将其禁用。

v-for和v-if不要同时使用

在vue中v-for和v-if不要放在同一个元素上使用。由于 v-for 和 v-if 放在同一个元素上使用会带来一些性能上的影响,在计算属性上过滤之后再进行遍历。反例:

![Image 1][]

image

使用Object.freeze冻结大数据

对于前端纯大数据展示(纯大数据指:拿到数据就是直接用于展示的,不需要做修改其中字段等处理的,而且数据量比较大)的情况下,使用Object.freeze方法来包裹变量,那边vue内部不会使用defineproperty去监听数据内部的变化,只有本身变化时才会触发,在大量数据的情况下,vue内部不在去监听数据的变化会提高性能。使用demo如下:

![Image 1][]

image

使用Keep-alive标签优化组件创建

vue提供了keep-alive标签来存储缓存,对于一些视频控件object或图表类的使用,我们经常会使用v-if指令,而v-if是会创建和销毁的,如果频繁操作在ie下的内存会持续上升,而keep-alive可以有效的缓存,抑制内存的持续上升。

见:https://cn.vuejs.org/v2/api/#keep-alive

使用Set

Es6集合Set()可优化遍历速度,set集合是可用于查找该集合内是否存在某个元素。但如果使用了Bable自动转化,该优化无效。

在scope中少用元素选择器

scope中元素选择器尽量少用。在 scoped 样式中,类选择器比元素选择器更好,因为大量使用元素选择器是很慢的。

为了给样式设置作用域,Vue 会为元素添加一个独一无二的特性,例如 data-v-f3f3eg9。然后修改选择器,使得在匹配选择器的元素中,只有带这个特性才会真正生效 (比如 button[data-v-f3f3eg9])。问题在于大量的元素和特性组合的选择器 (比如 button[data-v-f3f3eg9]) 会比类和特性组合的选择器 慢,所以应该尽可能选用类选择器。

关于template的优化

v-show,v-if 用哪个?在我来看要分两个维度去思考问题,第一个维度是权限问题,只要涉及到权限相关的展示无疑要用 v-if,第二个维度在没有权限限制下根据用户点击的频次选择,频繁切换的使用 v-show,不频繁切换的使用 v-if,这里要说的优化点在于减少页面中 dom 总数,我比较倾向于使用 v-if,因为减少了 dom 数量,加快首屏渲染,至于性能方面我感觉肉眼看不出来切换的渲染过程,也不会影响用户的体验。

不要在模板里面写过多的表达式与判断 v-if=”isShow && isAdmin && (a || b)”,这种表达式虽说可以识别,但是不是长久之计,当看着不舒服时,适当的写到 methods 和 computed 里面封装成一个方法,这样的好处是方便我们在多处判断相同的表达式,其他权限相同的元素再判断展示的时候调用同一个方法即可。

循环调用子组件时添加 key,key 可以唯一标识一个循环个体,可以使用例如 item.id 作为 key,假如数组数据是这样的 [‘a’ , ‘b’, ‘c’, ‘a’],使用 :key=”item” 显然没有意义,更好的办法就是在循环的时候 (item, index) in arr,然后 :key=”index”来确保 key 的唯一性。

2019年2月14日

写在最后:通篇文章完整读下来可以学到蛮多知识点的 值得学习以下
此外 对于最后一段对于v-for的循环来说 :key的值最好不要为index 作为唯一性的key值如果变动以后 框架会认为替换了内容而对改变的index项之后的循环都重新渲染了. 正确的做法应该是数据中包含唯一的key来作为值来绑定. (推荐这么做 因为这时插入一条数据 只是在index添加一条数据 之后的数据不会重新渲染 实际情况应项目而异)


参考资料:

  • https://juejin.im/entry/5982e16a6fb9a03c50227ef5
  • https://segmentfault.com/a/1190000016558958
  • https://segmentfault.com/a/1190000011602774
  • https://segmentfault.com/a/1190000009443366

[Image 1]:

发表评论

表情:
评论列表 (有 0 条评论,256人围观)

还没有评论,来说两句吧...

相关阅读

    相关 vue全家——vuex

    > 本文主要介绍vuex【状态管理模式】,在介绍vuex前,需要重点说下一些概念 > vue最大的特点之一。数据驱动视图,可以吧数据理解成状态,视图-view可以理解成用

    相关 vue全家初步了解

    vue全家桶初步了解 1、Vue-cli Vue-cli是vue官方出品的快速构建单页应用的脚手架,这里牵扯的东西很多,有webpack,npm,nodejs,ba

    相关 vue全家

    Vue全家桶实践项目总结 从前端的角度看,Vue可以说是目前最理想的前端MVVM框架,一切为界面服务,上手难度低,本文就将记录使用Vue全家桶(Vue+Vue-