一,基本用法
插件通常为 Vue 来添加全局功能。而插件的应用范围没有明确规定,一般包括:
添加全局方法或属性。例如:vue-custom-element
添加全局资源:指令/过滤器/过渡等。如:v-touch
通过全局混入来添加一些组件选项。如:vue-router
添加 Vue 实例方法,通过把他们添加到 Vue.prototype 上实现
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如:vue-router
开发一个插件需要暴露一个 install方法。这个方法的第一个参数是 Vue 构造器,第二个参数一个可选的选项对象。
编写一个组件:
import LoadingComponent from './LoadingComponent.vue';
const MyPlugin = {};
MyPlugin.install = function(Vue, options) {
// 1, 添加全局方法或属性
Vue.myGlobalVariable = "hahha";
Vue.myGlobalMethods = function(){ //... }
// 2, 添加全局资源
// 注册一个全局组件 `loading`, 不用 `import` 可以在任何位置使用
Vue.component('Loading', LoadingComponent)
Vue.directive('my-directive', {
bind(el, binding, vnode, oldnode){ // logic }
})
// 3, mixins
Vue.mixin({
created(){ // logic },
mounted(){ // logic }
})
// 4, 在原型上添加实例方法,被各个实例继承
Vue.prototype.$MyMethods = function(mOptions) { // logic }
}
把 Vue 引到一个新的位置,添加全局方法,组件,属性等内容,其实这些内容也可以通过其他方式写,那么这个API的用处在哪儿
如何使用:
// MyPlugin.vue
// 2, 添加全局资源 (无需引入,可全局使用的组件)
export default {
// other logic
mounted(){
// 1, 添加全局方法或属性 (TODO: 这种方式似乎不能访问 ??????)
const myGlobalVariable = this.myGlobalVariable; // undefined
this.myGlobalMethods(); // "this.myGlobalMethods is not a function"
// 3, mixins
// 4, 在原型上添加实例方法,被各个实例继承
this.$MyMethods(); // run logic
}
}
二,源码解析
// ./vue/src/core/global-api/use.js
import { toArray } from "../utils/index";
/* @flow */
import { toArray } from '../util/index'
export function initUse (Vue: GlobalAPI) {
// `plugin: Function | Object` 这种使FLOW写法,facebook 出品的一款Javascript静态类型检查工具。
// use 方法接受的参数为 function 或 object, 若为function, 默认为 install 方法,若为object, 里面必 须含有一个install 方法。
Vue.use = function (plugin: Function | Object) {
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 插件只能注册一次
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// 处理附加参数
// `toArray`: arguments是参数列表的一个类数组(不知道是不是这么说),toArray 将它转变成一个真的数组,截取除第一以外的所有参数。 ① 这些参数会在调用install 方法时传入;
const args = toArray(arguments, 1)
args.unshift(this)
if (typeof plugin.install === 'function') {
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
installedPlugins.push(plugin)
return this
}
}
所以我们总结下,Vue.use 的好处:
统一在一个位置注册全局内容,避免main.js过度臃肿;
全局注册,避免重复引入问题;
避免重复注册组件。
三,常见的几种用法
mixins
// 一个简单的栗子,可以用于 validation
const myMixin = {
install(Vue, options) {
// 这里 this 指向 myMixin
Vue.mixin({
// 每个组件创建都会执行 created 方法
created(){
const rules = this.$options.rules || null;
if(rules) {
Object.keys(rules).forEach(key => {
const ruleItem = rules[key];
// $watch 接受两个参数:监测的属性名,callback(变化后的值)
this.$watch(key, newValue => {
const isValid = ruleItem.validate(newValue);
if(!isValid) {
console.log(ruleItem.message)
}
})
})
}
}
})
}
}
const vue = new Vue({
data: {
foo: 10
},
// 检验规则 ③
rules: {
foo: {
validate: value => value > 1,
message: "foo 必须大于 1"
},
// more rules
}
})
// * 需要注意的是, ③处的 rules 只能在根组件通过 this.options.rules 访问,子组件的话得看嵌套关系,可能是 this.$options.parent.$options....parent.$options.rules 这种方式访问该属性
directives
// 注册一个全局自定义指令:`v-focus`
// plugin 内部
function Myplugin(Vue){
Vue.directive('focus', {
// 当被绑定元素插入到DOM中时,自动聚焦
inserted(el){
el.focus();
}
})
}
// template 内部
// 自动聚焦
axios
axios 不能直接用Vue.use, 以为它本身没有暴露 install 方法。所以使用axios只能是下面两种方法:
使用的地方进行 import:
import axios from 'axios';
export const login = params => {
return axios
.post(`localhost:3000/login`, params)
.then(res => res)
}
使用 vue-axios :
// npm i -S axios vue-axios
// main.js
import Vue from 'vue';
import axios from 'axios';
import VueAxios from 'vue-axios';
// 由之前的 `vue.use` 用法我们知道,它可以接收第二个 `options` 参数,所以这里本质是将 `axios` 通过 `VueAxios` 包装以符合 "install 规范"
Vue.use(VueAxios, axios);
// usage
this.axios.get(params).then(res => res);
Vue.axios.get(params).then(res => res);
this.$http.get(params).then(res => res);
vue-router
// ./route/index.js
import Router from 'vue-router';
Vue.use(Router);
let router = new Router({
routes: [
{
path: "./home",
component: homeComp
}
]
})
export default router;
// main.js
import router from ....;
const vue = new Vue({
// other logic
router
})
Vuex
用法与vue-router 等同,不赘述
四,常见问题
为什么要先 Vue.use 再 new Vue() ?
首先回顾下 Vue.use 做了什么:
检测是否注册过此插件
插件注入到 Vue 构造函数 (TODO:不知道是不是这么说)
存储注册过的插件
而在 new Vue(options) 时,会执行 Vue.prototype._init 进行初始化,将Vue 本身属性与options合并,然后进行事件,生命周期初始化。(比如:vue-router, vuex 等需要将 store, router 选项插入options)
所以,如果 Vue.use 在 new Vue() 之前执行,那么调用_init()时,插件中使用的内容还没有注入到 Vue.options.component, Vue.options.directives 等属性中,初始化的实例也无从访问。