Vue入坑史,插件系统详解

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

在谈主题插件之前,我想先引出两个关于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)

后记

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容