技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTVUE

Vue.use(plugin) 详解

所属馆藏
Vue
文件格式
Markdown
原始路径
Vue/14-Vue use(plugin)详解
本文目录11 个章节
  1. 前言
  2. 什么是 Vue.use(plugin)
  3. 为什么 Vue Router、Vuex、Element UI 需要 Vue.use,而 axios 不需要?
  4. Vue 2 中 Vue.use 的源码逻辑
  5. Element UI 的 install 做了什么?
  6. Vue Router 3 的 install 做了什么?
  7. Vue Router 4 的当前写法
  8. 为什么 axios 可以开箱即用?
  9. 如何自己编写一个插件?
  10. 总结
  11. 官方参考

Vue.use(plugin) 详解

本文原文是 Vue 2、Vue Router 3、Vuex 3 和 Element UI 时代的文章。相关历史 API 和源码仍值得学习,但 Vue 3 应使用 createApp()app.use()app.config.globalPropertiesapp.provide()。Vue 2 已结束维护,下面按版本保留并整理原文内容。

前言

阅读本文,可以了解到:

  • Vue.use(plugin) 的基础概念和简单用法。
  • 为什么 Vue Router、Vuex、Element UI 需要安装,而 axios 通常不需要。
  • Vue Router 3、Element UI 的 install 在 Vue 2 中做了什么。
  • Vue 2 插件的核心源码逻辑。
  • 如何编写 Vue 2 插件,以及如何迁移到 Vue 3。

什么是 Vue.use(plugin)

Vue.use 是 Vue 2 用来安装插件的全局方法。

Vue.use(plugin)
Vue.use(plugin, options)

Vue 2 插件的公共约定是:

  • 如果插件是对象,应提供 install 方法。
  • 如果插件本身是函数,就把它作为安装函数。
  • 安装函数的第一个参数是 Vue 构造函数,后面是 Vue.use() 传入的其他参数。
  • 同一个插件对象在同一个 Vue 构造函数上重复调用时,只安装一次。
  • 通常应在 new Vue() 启动根实例之前安装。
import Vue from 'vue'
import ElementUI from 'element-ui'
import Vuex from 'vuex'
import VueRouter from 'vue-router'

Vue.use(ElementUI)
Vue.use(Vuex)
Vue.use(VueRouter)

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

安装完成后,Vue Router 3 可以提供 this.$routerthis.$route,Vuex 可以提供 this.$store,Element UI 可以提供全局组件或 this.$message 等实例方法。具体提供哪些能力取决于插件自己的 install 实现。

原文中的 Vue-cli 3.0、Element UI 和 Vue Router 示例保留为 Vue 2 历史语境,不能直接当作 Vue 3 项目入口代码。

为什么 Vue Router、Vuex、Element UI 需要 Vue.use,而 axios 不需要?

Vue Router、Vuex 和 Element UI 都需要在安装阶段执行一些 Vue 集成操作,例如:

  • 注册全局组件或指令。
  • 向组件实例提供 $router$route$store 等属性。
  • 添加全局混入或注入依赖。
  • 根据选项初始化插件内部状态。

axios 本身是独立的 Promise 请求库,不需要修改 Vue 构造函数就能工作:

import axios from 'axios'

axios.get('/api/users')

如果项目希望统一通过组件实例访问,也可以自己封装一个插件:

Vue.prototype.$axios = axios // Vue 2 历史写法

因此,“不需要 Vue.use()”不等于“axios 不能作为插件安装”,而是它的基本功能不依赖 Vue 的安装生命周期。

Vue 2 中 Vue.use 的源码逻辑

下面是 Vue 2.7 src/core/global-api/use.ts 的核心逻辑整理:

export function initUse(Vue) {
  Vue.use = function (plugin) {
    const installedPlugins =
      this._installedPlugins || (this._installedPlugins = [])

    // 同一个插件对象只安装一次
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // 取得 Vue.use(plugin, option1, option2) 的额外参数
    const args = toArray(arguments, 1)
    args.unshift(this) // install(Vue, ...options)

    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }

    installedPlugins.push(plugin)
    return this
  }
}

这里的 Function | Object 是 API 约定,不应理解为源码会对所有非法参数做完整运行时类型校验。核心步骤是:去重、准备参数、调用 install 或函数插件、记录插件并返回 Vue 构造函数。

Element UI 的 install 做了什么?

下面保留原文的 Vue 2 / Element UI 历史思路,并将被抓取到同一行的代码整理为可读形式:

const install = function (Vue, options = {}) {
  locale.use(options.locale)
  locale.i18n(options.i18n)

  // components 中包含 Dialog、Input 等组件
  components.forEach(component => {
    Vue.component(component.name, component)
  })

  Vue.use(Loading.directive)

  Vue.prototype.$ELEMENT = {
    size: options.size || '',
    zIndex: options.zIndex || 2000
  }

  Vue.prototype.$loading = Loading.service
  Vue.prototype.$msgbox = MessageBox
  Vue.prototype.$alert = MessageBox.alert
  Vue.prototype.$confirm = MessageBox.confirm
  Vue.prototype.$prompt = MessageBox.prompt
  Vue.prototype.$notify = Notification
  Vue.prototype.$message = Message
}

export default { install }

这段代码说明了插件安装函数可以集中完成全局组件注册、指令注册和实例方法注入。它是 Element UI 的历史实现示意,不是 Element Plus 的 Vue 3 安装源码。

Vue Router 3 的 install 做了什么?

Vue Router 3 的 install 是 Vue 2 插件集成的典型案例。下面只保留核心逻辑,属于历史源码摘录,Vue.util.defineReactive 是非公共 API,不应在业务插件中使用:

import View from './components/view'
import Link from './components/link'

export let _Vue

export function install(Vue) {
  _Vue = Vue

  const isDef = value => value !== undefined

  const registerInstance = (vm, callVal) => {
    let vnode = vm.$options._parentVnode
    if (
      isDef(vnode) &&
      isDef(vnode = vnode.data) &&
      isDef(vnode = vnode.registerRouteInstance)
    ) {
      vnode(vm, callVal)
    }
  }

  Vue.mixin({
    beforeCreate() {
      if (isDef(this.$options.router)) {
        // 根组件保存 new Vue({ router }) 中的 router
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)

        // Vue Router 3 历史实现使用 Vue 的内部工具建立响应式 _route
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        // 子组件沿父链找到根组件
        this._routerRoot =
          (this.$parent && this.$parent._routerRoot) || this
      }

      registerInstance(this, this)
    },
    destroyed() {
      registerInstance(this)
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get() {
      return this._routerRoot._router
    }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get() {
      return this._routerRoot._route
    }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  strats.beforeRouteEnter = strats.beforeRouteLeave =
    strats.beforeRouteUpdate = strats.created
}

因此,Vue Router 3 的插件安装大致完成了以下工作:

  1. 通过全局混入让根组件保存 router,并让子组件找到根组件。
  2. 让路由 current route 参与 Vue 2 的响应式更新。
  3. $router$route 代理到组件实例。
  4. 注册 <router-link><router-view>
  5. 配置路由组件守卫的选项合并策略。

这也是 Vue Router 3 必须安装的原因:它不只是一个普通 JavaScript 工具,而是需要与 Vue 2 的组件树和响应式系统集成。

Vue Router 4 的当前写法

Vue Router 4 面向 Vue 3,不再使用 new VueRouter({ mode: 'history' })Vue.use(Router)

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'

const router = createRouter({
  history: createWebHistory(),
  routes: [
    {
      name: 'user',
      path: '/users/:id',
      component: () => import('./views/UsersView.vue')
    }
  ]
})

const app = createApp(App)
app.use(router)
app.mount('#app')

Router 4 的 install(app) 会把路由器接入当前 app,注册 RouterLinkRouterView,提供路由依赖,并为 Options API 组件提供 $router$route。在 Composition API 中更推荐:

import { useRoute, useRouter } from 'vue-router'

const router = useRouter()
const route = useRoute()

router.push({ name: 'user', params: { id: '1' } })
console.log(route.params.id)

为什么 axios 可以开箱即用?

axios 的核心功能只依赖自身模块和浏览器/Node.js 的请求能力,不需要 Vue 构造函数或组件树:

import axios from 'axios'

export async function getUsers() {
  const { data } = await axios.get('/api/users')
  return data
}

如果使用插件注入 axios,Vue 2 和 Vue 3 的差异如下:

// Vue 2
Vue.prototype.$axios = axios

// Vue 3
app.config.globalProperties.$axios = axios
app.provide('axios', axios)

globalProperties 只影响当前 app 的组件实例。普通模块不应依赖一个隐式的全局 $axios,更清晰的方式通常是直接导入请求封装函数。

如何自己编写一个插件?

Vue 2 历史写法

// api-plugin.js
import login from './login'
import home from './home'
import detail from './detail'

const apiList = {
  ...login,
  ...home,
  ...detail
}

const install = Vue => {
  if (install.installed) return
  install.installed = true

  Object.defineProperty(Vue.prototype, '$api', {
    get() {
      return apiList
    }
  })
}

export default { install }
import Vue from 'vue'
import apiPlugin from './api-plugin'

Vue.use(apiPlugin)
new Vue({ render: h => h(App) }).$mount('#app')

install.installed 可以作为插件自己的保护,但 Vue 2 的 Vue.use() 本身也会按插件对象去重,通常不需要重复实现复杂的去重逻辑。

Vue 3 当前写法

// api-plugin.js
export default {
  install(app) {
    app.config.globalProperties.$api = apiList
    app.provide('api', apiList)
  }
}
<script setup>
import { inject } from 'vue'

const api = inject('api')
</script>

插件可以根据需要选择 app.component()app.directive()app.provide()app.config.globalProperties,不应为了“全局可用”而把所有状态都挂到全局属性上。

总结

  • Vue 2 的 Vue.use() 调用 install(Vue, ...options),并对同一个插件对象去重。
  • Vue Router 3 的 install 会接入 Vue 2 的组件树、响应式系统、实例属性和全局组件。
  • Vue 3 使用 app.use(),插件安装状态按 app 隔离;Vue Router 4 应通过 app.use(router) 安装。
  • axios 不需要安装,是因为它本身不依赖 Vue;是否注入到组件实例是项目架构选择。
  • Vue.utilVue.prototypeVue.use 都是 Vue 2 语境,不应直接复制到 Vue 3 项目。

官方参考

作者:Edon

原文链接:https://juejin.cn/post/6844903946343940104

来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS