• 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等。

     
    image

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

     
    image

    有两种模式:

    • hash 模式

    • history 模式

    vuex

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

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

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

     
    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

    通过 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

    在发送请求后,使用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

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

     
    image

    关于UI组件库

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

    二、vue工程目录结构

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

     
    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

    三、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实例生命周期图解

     
    image

    四、vue的运行原理

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

     
    image

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

    双向绑定图解

    一般说的双向绑定,指:

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

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

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

     
    image

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

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

    > 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 放在同一个元素上使用会带来一些性能上的影响,在计算属性上过滤之后再进行遍历。反例:

     
    image

    使用Object.freeze冻结大数据

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

     
    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 的唯一性。

  • 相关阅读:
    JSP(一)
    设计模式之UML类图
    Servle原理
    Servlet 浅谈(三)
    Servlet 浅谈(二)
    Servlet 浅谈(一)
    闲聊
    设计模式之装饰器模式
    struts2源码调试环境的搭建
    Github学习
  • 原文地址:https://www.cnblogs.com/zhuxinpeng-looking/p/11233028.html
Copyright © 2020-2023  润新知