美文网首页大前端前端Web前端之路
Vue入坑史,插件系统详解

Vue入坑史,插件系统详解

作者: 颜漠笑年 | 来源:发表于2017-05-04 09:54 被阅读775次

    越是让你感觉到害怕的事情,就越要去面对它。

    在谈主题插件之前,我想先引出两个关于Vue的问题,这也是我存在的两个疑问,希望有人能够帮忙解答。

    如果你比较心急,可以直接跳到Vue.use源码解读

    有几个疑问

    1. 如果子组件没有在html文档中通过{{ value }}形式使用父组件传递的props参数,那么就算父组件改变了props参数,子组件也不会自动更新。

      比如子组件通过props参数只是用来控制自己的CSS样式的时候:

      <!-- 示例1 -->
      <template>
          <!-- 没有在html中使用 height -->
          <div class="wrap"></div>
      </template>
      <script>
      export default {
          props: ['height'],
          mounted () {
              this.$el.style.height = this.height + 'px'
          },
          updated () {
              this.$el.style.height = this.height + 'px'
          }
      }
      </script>
      
      <!-- 示例2 -->
      <template>
          <!-- 在自定义指令使用 height -->
          <div class="wrap" v-style="height"></div>
      </template>
      <script>
      export default {
          props: ['height'],
          directives: {
              style: {
                  bind (el, { value }) {
                      el.style.height = value + 'px'
                  },
                  update (el, { value, oldValue}) {
                      if (value === oldValue) {
                          return
                      }
                      el.style.height = value + 'px'
                  }
              }
          }
      }
      </script>
      

      我不知道是我使用的姿势不对,还是我忽略了什么东西。我的解决办法是父组件调用this.$forceUpdate()来强制更新,但总感觉这样会影响性能。

    2. 针对上面的示例2,自定义局部指令该如何简写?

      我们知道,为了简单,自定义全局指令时可以使用简写(只关心bindupdate钩子函数时):

      Vue.directive('style', function (el, { value }) {
          el.style.height = value + 'px'
      })
      

      那么,自定义局部指令时该如何简写呢?还是说Vue本身就不支持局部指令简写?因为很多时候,bindupdate钩子函数中的代码都是一样的。

    这两个是我最近在开发过程中遇到的问题,也没有找到相关的答案,希望有谁能够帮忙解答,在此先行谢过了。

    什么是插件

    Vue的插件一般就是用来扩展Vue的功能。比如,当需要Vue实现Ajax请求功能,我们希望通过this.$get(url)的形式就可以发送一个get请求。那么,我们就需要给Vue的实例添加一个$get方法,Vue实例本身是没有这个方法的。

    Vue的一些插件:

    • vuex:官方状态管理插件;
    • vue-router:官方路由插件;
    • vue-resource:Ajax请求插件;
    • vue-element:饿了么出品的组件库。

    如何使用插件

    在创建Vue实例之前,通过全局方法Vue.use()来使用插件:

    // 使用 MyPlugin 插件
    Vue.use(MyPlugin)
    
    // 或 传入一个选项参数,options 一般是一个对象
    Vue.use(MyPlugin, options)
    

    是不是很简单,好像也没有什么好说的。

    有时候,我们看到某些插件使用起来可能有些不一样。比如使用vuex

    // store.js 文件中
    import Vuex from 'vuex'
    import Vue from 'vue'
    import state from './state'
    import mutations from './mutations'
    import actions from './actions'
    
    // 注册插件
    Vue.use(Vuex)
    
    const store = new Vuex.Store({
        state,
        mutations,
        actions
    })
    
    export default store
    
    // main.js 文件中
    import Vue form 'vue'
    import App from './App'
    import store from './store'
    
    new Vue({
        el: '#app',
        store,
        render: h => h(App)
    })
    

    其实本质上还是一样的,也是通过Vue.use()方法注册插件。只不过它有一个store对象,然后并将store对象作为Vue根实例的属性,以便组件通过this.$store这种形式来访问。

    自定义插件

    其实当通过Vue.use()注册插件时,内部会自动调用插件的install()方法。也就是说,插件必须要提供一个公开的install()方法,作为接口。该方法第一个参数是Vue,第二个参数是可选的options对象。

    总结起来说,插件是一个对象。该对象要有一个公开的install()方法,那么写起来可能是这样的:

    const MyPlugin = {}
    MyPlugin.install = function (Vue, options) {
        // ...
    }
    
    export default MyPlugin
    

    install()方法中,我们通过参数可以拿到Vue对象,那么,我们就可以对它做很多事情。

    MyPlugin.install = function (Vue, options) {
        // 1. 添加全局方法和属性
        Vue.myProperty = 'Hello Vue',
        
        // 2. 添加全局的自定义指令
        Vue.directive('name', function (el, binding) {
            // ...
        })
        
        // 3. 混合
        Vue.mixin({
            created () {
                // ...
            }
        })
        
        // 4. 添加实例方法
        // 通过原型为一个对象添加实例方法
        // 在 Vue 实例中,通过 this.$get() 就可以调用该方法
        Vue.prototype.$get = function () {
            // ...
        }
    }
    

    插件的几种写法

    这里直接就看几个插件的源码吧,看看他们是怎么写的,其实我也是参照了这些源码才真正弄明白了插件是怎么一回事。源码很长,这里只说一些关键点。

    vue-touch

    // 外面是一个立即执行函数,控制作用域
    // 前面的分号应该是为了更好的兼容其他js源码吧
    ;(function () {
        var vueTouch = {}
        
        // 这里没有接收第二个参数
        vueTouch.install = function (Vue) {
            // ...
        }
        
        // 导出 vueTouch 插件
        if (typeof exports == "object") {
            module.exports = vueTouch
        } else if (typeof define == "function" && define.amd) {
            define([], function(){ return vueTouch })
        } else if (window.Vue) {
            // 如果 Vue 作为全局对象,则自动使用插件
            // 也就是说,当我们在HTML文档中通过script直接引用 vue 和 vueTouch 时,不需要手动注册
            window.VueTouch = vueTouch
            Vue.use(vueTouch)
        }
    })()
    

    vue-router

    import { install } from './install'
    
    export default class VueRouter {
        // 定义了一个静态方法, ES6 新增的语法
        // 用其他语言理解也就相当于一个类方法,通过类名调用
        static install: () => void
    }
    
    // 这里 install 是一个从外部引入的函数
    VueRouter.install = install
    
    // 自动注册插件
    if (inBrowser && window.Vue) {
        window.Vue.use(VueRouter)
    }
    

    vuex

    import { Store, install } from './store'
    
    export default {
        install,
        // ...
    }
    // 这个最简单直观,没啥好说的
    

    vue-resource(重点)

    // 这个。。。
    // 我也不知道如何解释
    // 这里好像直接用 plugin 代替了 install 方法
    // 当使用 Vue.use(vueResource) 时,会调用该函数 ???
    // 先暂且这么认为吧
    function plugin(Vue) {
        // 避免重复注册
        if (plugin.installed) {
            return
        }
        
        // ...
    }
    
    // 自动注册插件
    if (typeof window !== 'undefined' && window.Vue) {
        window.Vue.use(plugin);
    }
    
    export default plugin;
    

    <span id="use">Vue.use源码解读(一定要看)</span>

    针对vue-resource插件问题,我查看了一下vue的源码,它的源码是这样的:

    Vue.use = function (plugin: Function | Object) {
        /* istanbul ignore if */
        // 保证同一个插件只安装一次
        if (plugin.installed) {
            return
        }
        // additional parameters
        // 这句的作用应该是去掉第一个参数,然后转换成数组
        const args = toArray(arguments, 1)
        // 将Vue作为数组的第一个元素,以便传入插件中
        args.unshift(this)
        // 插件有install接口,并且是一个函数
        if (typeof plugin.install === 'function') {
            // 在plugin作用域下调用 install ,并传入拼接好的参数
            // 在 install 中,this 指向 plugin
            plugin.install.apply(plugin, args)
            
        // 插件本身是一个函数
        // 解释vue-resource写法的关键点在这
        } else if (typeof plugin === 'function') {
            // 在全局作用域下调用 plugin 函数
            // plugin 中,this 指向 window
            plugin.apply(null, args)
        }
        plugin.installed = true
        return this
    }
    

    通过源码,我们知道,Vue插件可以是一个对象或者是一个函数。只有当插件实现了 install 接口时(有install这个函数时),才会调用插件的install方法;否则再判断插件本身是否是一个函数,如果是,就直接调用它。

    现在就能很好的解释vue-resource插件的写法了。好吧,我也是刚刚得知,又长了一点见识。

    其实官网也有说明

    Vue.use(plugin)

    后记

    写一篇文章,对别人来说是一种分享,其实对自己来说更是一种提高。因为你要写好一篇文章,一方面你得尽量保证其正确性,有时候你不得不亲自去验证,另一方面也是对自己所学的知识进行一遍系统的复习和整理。

    如果你有时间,我建议你多写一些技术类文章;如果你实在没时间写,那也要多读读别人写的文章。

    相关文章

      网友评论

      本文标题:Vue入坑史,插件系统详解

      本文链接:https://www.haomeiwen.com/subject/bhjrtxtx.html