Vue.use(plugin) 详解
本文原文是 Vue 2、Vue Router 3、Vuex 3 和 Element UI 时代的文章。相关历史 API 和源码仍值得学习,但 Vue 3 应使用
createApp()、app.use()、app.config.globalProperties及app.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.$router、this.$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 的插件安装大致完成了以下工作:
- 通过全局混入让根组件保存 router,并让子组件找到根组件。
- 让路由 current route 参与 Vue 2 的响应式更新。
- 把
$router、$route代理到组件实例。 - 注册
<router-link>和<router-view>。 - 配置路由组件守卫的选项合并策略。
这也是 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,注册 RouterLink、RouterView,提供路由依赖,并为 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.util、Vue.prototype和Vue.use都是 Vue 2 语境,不应直接复制到 Vue 3 项目。
官方参考
- Vue 2:插件(历史)
- Vue 2:Vue.use API(历史)
- Vue 2.7.16:Vue.use 源码
- Vue 2.7.16:Vue Router 3 兼容源码参考
- Vue 3:插件
- Vue 3:应用 API
- Vue Router 4:安装
- Vue Router 4:Composition API
作者:Edon
原文链接:https://juejin.cn/post/6844903946343940104
来源:稀土掘金。著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。