专栏首页艺述思维Vue 全家桶、原理及优化简议

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

不少互联网公司都在使用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全家桶包括什么

vue-router路由

vuex

vue-resource

构建工具vue-cli

调度工具Devtools

关于UI组件库

二、vue工程目录结构

编辑器

三、vue使用简介

数据代理

vue实例生命周期图解

四、vue的运行原理

双向绑定图解

模板是如何解析的

五、发布前优化

UI组件按需加载

路由懒加载

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

图片压缩与合并

使用CDN加速vue类库

压缩代码

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

使用Object.freeze冻结大数据

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

使用Set

在scope中少用元素选择器

关于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等。

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

有两种模式:

  • hash 模式
  • history 模式

vuex

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

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

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

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

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

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

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

由于 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。

在发送请求后,使用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对象的一些信息。

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

关于UI组件库

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

二、vue工程目录结构

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

  • 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

三、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) { Object.defineProperty(this, key, { // this.title, 即vm.title enumerable: false, configurable: true, get: function getter () { return self.data[key]; //触发对应data[key]的getter }, set: function setter (newVal) { self.data[key] = newVal; //触发对应data[key]的setter } }); }

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

vm.$data === data // -> true vm.$el === document.getElementById('example') // -> true

vue实例生命周期图解

四、vue的运行原理

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

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

双向绑定图解

一般说的双向绑定,指:

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

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

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

如上所求,每个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函数的最近的上级作用域中,如下面实例代码所示。

Object.keys(this.data).forEach(function(key) { var subs = []; // 在这里放置添加所有订阅者的数组 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log('访问数据啦啦啦') return this.data[key]; //返回对应数据的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果数据没有变动,函数结束,不执行下面的代码 } this.data[key] = newVal; //数据重新赋值 subs.forEach(function () { // 通知subs里面的所有的订阅者 }) } }); }

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

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

伪代码如下:

target = changeTitle ... Object.keys(this.data).forEach(function(key) { var subs = []; // 在这里放置添加所有订阅者的数组 Object.defineProperty(this.data, key, { // this.data.title enumerable: false, configurable: true, get: function getter () { console.log('访问数据啦啦啦') if (target) { subs.push(target); } return this.data[key]; //返回对应数据的值 }, set: function setter (newVal) { if (newVal === this.data[key]) { return; // 如果数据没有变动,函数结束,不执行下面的代码 } this.data[key] = newVal; //数据重新赋值 subs.forEach(function () { // 通知subs里面的所有的订阅者 }) } }); }

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

模板是如何解析的

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

<input v-model="title"> <h1>{{title}}</h1> <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 修改为:

{ "presets": [["es2015", { "modules": false }]], "plugins": [ [ "component", { "libraryName": "element-ui", "styleLibraryName": "theme-chalk" } ] ] }

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

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

注: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 的代码分割功能,轻松实现路由组件的懒加载。

// router.js const login = () => import('@/components/login') const router = new VueRouter({ routes: [ { path: '/login', component: login } ] })

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

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

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

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

// template <test v-if="showTest"></test> // script components: { test: () => import('./test') // 将组件异步引入,告诉webpack,将该部分代码分割打包 }, methods:{ clickTest () { this.showTest = !this.showTest } }

图片压缩与合并

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

使用CDN加速vue类库

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

类库文件使用cdn加速

<!-- built files will be auto injected --> <script src="https://cdn.bootcss.com/vue/2.5.2/vue.min.js"></script> <script src="https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js"></script> <script src="https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js"></script> <script src="https://cdn.bootcss.com/vue-resource/1.5.1/vue-resource.min.js"></script>

修改 build/webpack.base.conf.js

module.exports = { context: path.resolve(__dirname, '../'), entry: { app: './src/main.js' }, externals:{ 'vue': 'Vue', 'vue-router': 'VueRouter', 'vuex':'Vuex', 'vue-resource': 'VueResource' }

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

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

// import Vue from 'vue' // import VueResource from 'vue-resource' // Vue.use(VueResource)

上面已经引用过。

压缩代码

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

new webpack.optimize.UglifyJsPlugin({ compress: { warnings: false, drop_console: true, pure_funcs: ['console.log'] }, sourceMap: false })

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

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

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

使用Object.freeze冻结大数据

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

使用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日

--------------------------------------------------------

参考资料:

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

本文分享自微信公众号 - 用故事讲技术(ygsjjs),作者:石桥码农

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-02-14

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • vue 开发常用工具及配置一

    访问 nodejs.org 下载。这是必不可缺的环境之一。下载最新的 LTS 版本。LTS 代表长期维护,通常比较稳定。

    李艺
  • 12 手写配置启动一个 vue2 项目

    2019年10月5日,vue 团队发布了 Vue3.0 预览版源码,预计到 2020 年第一季度将发布 3.0 正式版。3.0 包涵了许多激动人心的新特性。

    李艺
  • vue 开发常用工具及配置三

    在现在的前端开发中,前后分离、模块化、版本控制、文件合并与压缩、mock数据等,是在大前端开发避不开的概念。在开发的时候,以组件的方式分别开发,在部署的时候又将...

    李艺
  • 二、Vue 页面渲染过程

    上篇博文我们依葫芦画瓢已经将hello world 展现在界面上啦,但是是不是感觉心虚虚的,总觉得这么多文件,项目怎么就启动起来了呢?怎么访问到8080 端口就...

    程序员爱酸奶
  • 超简单入门Vuex小示例

    本文转载自掘金上面的一篇博客,原文地址为: 超简单入门Vuex小示例,项目源代码我已经托管到github上面,源代码地址为:https://github.com...

    ccf19881030
  • 使用Golang的Gin框架和vue编写web应用

    使用vue-cli脚手架快速构建一个vue项目。 注意:前提是需要node环境,并且有可用的npm源

    BGBiao
  • Vue2 全家桶仿 微信App 项目,支持多人在线聊天和机器人聊天

    前言 这个项目是利用工作之余写的一个模仿微信app的单页面应用,整个项目包含27个页面,涉及实时群聊,机器人聊天,同学录,朋友圈等等,后续页面还是开发中。写这个...

    IMWeb前端团队
  • Vue2 全家桶仿 微信App 项目,支持多人在线聊天和机器人聊天

    这个项目是利用工作之余写的一个模仿微信app的单页面应用,整个项目包含27个页面,涉及实时群聊,机器人聊天,同学录,朋友圈等等,后续页面还是开发中。写这个项目主...

    IMWeb前端团队
  • Vue 面试题汇总

    (1)、active-class 是 vue-router 模块的 router-link 组件的属性   (2)、使用 children 定义嵌套路由

    LittlePanger
  • vue 开发常用工具及配置一

    访问 nodejs.org 下载。这是必不可缺的环境之一。下载最新的 LTS 版本。LTS 代表长期维护,通常比较稳定。

    李艺

扫码关注云+社区

领取腾讯云代金券