文章首次发表在 个人博客
vue 提供了 Vue.use 的全局 API 来注册插件, 了解它的内部实现, 无论是看插件的源码, 还是自己写插件, 都会轻松很多.
Vue.use 用法
vue 提供了 Vue.use 的全局 API 来注册插件, 比如 vuex,vue-router 等
用法
Vue.use(plugin)
参数如果是一个对象, 必须提供 install 方法
参数如果是一个函数, 自身会被当做 install 方法, 方法调用的时候, 会将 vue 作为参数传入
Vue.use(plugin) 调用之后, 插件会的 install 方法会默认接受第一个参数, 这个参数是 vue
这个方法需要在 new vue() 之前调用.
Vue.use 会自动阻止多次注册相同插件, 即使调用多次也只会注册一次.
我们可以从源码入手分析一下, 基于 vue 2.6.11 版本, 源码地址为: src/core/global-API/use.JS
- export function initUse (Vue: GlobalAPI) {
- // 接受一个 plugin 参数, 限制为 Function | Object 两种类型
- Vue.use = function (plugin: Function | Object) {
- // _installedPlugins 存储所有注册过的 plugin
- const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
- // 保存注册组件的数组, 不存在则创建, 存在则直接返回, 不允许重复注册
- if (installedPlugins.indexOf(plugin)> -1) {
- return this
- }
- // additional parameters
- // 将传入的参数转换成数组
- const args = toArray(arguments, 1)
- // 将 Vue 对象拼接到数组头部
- args.unshift(this)
- // 如果提供了 install 方法, 则直接调用
- if (typeof plugin.install === 'function') {
- // 如果组件是对象, 且提供 install 方法, 调用 install 方法将参数数组传入, 改变 `this` 指针为该组件
- plugin.install.apply(plugin, args)
- } else if (typeof plugin === 'function') {
- // 否则直接执行
- plugin.apply(null, args)
- }
- // 将 plugin 存储到 installedPlugins, 表示 y 已经注册过
- installedPlugins.push(plugin)
- return this
- }
- }
- /**
- * Convert an Array-like object to a real Array.
- */
- export function toArray (list: any, start?: number): Array<any> {
- start = start || 0
- let i = list.length - start
- const ret: Array<any> = new Array(i)
- while (i--) {
- ret[i] = list[i + start]
- }
- return ret
- }
Vue.use 主要做了两件事
检查插件是否已经注册, 注册过得不需要重复注册
没有注册的, 调用插件的 install 方法(参数是对象, 则调用对象的 install 方法, 如果是函数, 则直接当做 install 方法调用), 同时将 Vue 作为第一个参数传入
Vue-Router 中的 install
基于 vue-router3.1.6 版本, 源码位置: src/install.JS
- import View from './components/view'
- import Link from './components/link'
- /*
- export 一个 Vue 引用, 在打包的时候不希望把 Vue 作为依赖包打进去, 但是又希望可以使用 Vue 提供的一些方法,
- */
- export let _Vue
- // Vue.use 安装插件时候需要暴露的 install 方法
- export function install (Vue) {
- // 判断是否已安装过
- if (install.installed && _Vue === Vue) return
- install.installed = true
- _Vue = Vue
- const isDef = v => v !== undefined
- const registerInstance = (vm, callVal) => {
- let i = vm.$options._parentVnode
- if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
- i(vm, callVal)
- }
- }
- // 混入 beforeCreate
- Vue.mixin({
- beforeCreate () {
- // 在 option 上面存在 router 则代表是根组件
- if (isDef(this.$options.router)) {
- this._routerRoot = this
- // 根组件的_router 属性为, new Vue 传进去的 router
- this._router = this.$options.router
- // 执行 init 方法
- this._router.init(this)
- // 通过 defineReactive 方法, 来把 this._router.history.current 变成响应式的, 这个方法的底层就是 object.defineProperty
- Vue.util.defineReactive(this, '_route', this._router.history.current)
- } else {
- // 如果该组件不是根组件, 那么递归往上找, 直到找到根组件的.
- this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
- }
- registerInstance(this, this)
- },
- destroyed () {
- registerInstance(this)
- }
- })
- /*
- 下面通过给 Vue.prototype 定义 $router,$route 属性后, 所有的 Vue 实例 (组件) 都可以直接访问到
- */
- // 设置代理, 访问 this.$router 时直接代理到 this._routerRoot._router
- Object.defineProperty(Vue.prototype, '$router', {
- get () { return this._routerRoot._router }
- })
- // 设置代理, 访问 this.$route 时直接代理到 this._routerRoot._route
- Object.defineProperty(Vue.prototype, '$route', {
- get () { return this._routerRoot._route }
- })
- // 注册 router-view 和 router-link 组件
- Vue.component('RouterView', View)
- Vue.component('RouterLink', Link)
- const strats = Vue.config.optionMergeStrategies
- // use the same hook merging strategy for route hooks
- strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
- }
vue-router 中的 install 方法主要做了以下几件事:
通过 mixin 混入的方式, 如果是根组件, 则直接把根组件的 _router 设置为 this.$options.router
如果不是根组件, 那么递归往上找, 直到找到根组件的, 使用_routerRoot 标记
通过给 Vue.prototype 定义 $router,$route 属性后, 使得所有的 Vue 实例 (组件) 都可以直接访问到 $router,$route 属性
注册 < router-link>,<router-view > 组件
参考
vue 官方文档 - 插件
前端路由简介以及 vue-router 实现原理
Vue.use(plugin)详解
其他
来源: http://www.jianshu.com/p/97b8bd1fa82d