背景
当我们开发的 VUE 组件中需要用到 vue-router
,但是这个 vue-router
实例是需要外部传递进来的。比如我们开发了一个类似 admin 模板的组件,里面用到了 vue-router
来实现页面路由的功能,但是这个路由的配置以及有哪些路由页面都是由使用我这个模板组件的人来控制的,所以 vue-router
实例也就只能由使用者创建了传递给我。
使用属性传入
最容易想到的方案就是通过 props
来将 vue-router
实力传递进来,然后再通过 provide
传递给所有子组件。但是使用 vue-router
的时候一般需要用到 route
和 router
两个对象,这就需要将这两个都作为属性来传递。而且使用时我们也不能正常通过 useRoute
或者 useRouter
来获取,而是要用 inject
,使用起来有点不爽。
通过注册注入
我们组件作为插件导出时是需要定义 install
方法来给使用者来注册我们组件的。
ts
import { type App } from 'vue'
import Admin from './admin.vue'
Admin.install = (app: App) => {
app.component('Admin', Admin)
}
export default Admin
那我们是不是可以通过这个 install
方法来从外部注入 vue-router
实例。install
方法第二个参数 options
正好可以用来传递 vue-router
实例。
ts
import { type App } from 'vue'
import type { Router } from 'vue-router'
import Admin from './admin.vue'
Admin.install = (app: App, options: {
router: Router
}) => {
app.use(options.router)
app.component('Admin', Admin)
}
export default Admin
上面代码打包发布后,发现怎么不起作用?
经过吭哧吭哧调试发现原来是 app.use(options.router)
这行代码的问题。在使用者注册我们插件的时候是如下调用的:
ts
import { createApp } from 'vue'
import App from './App.vue'
import Admin from 'admin'
const app = createApp(App)
app.use(Admin, {
router
});
app.mount('#app');
而 vue 的 use
方法定义如下:
调用插件的 install
方法传入的 app
是当前上面 const app = createApp(App)
创建的实例,而我们是需要在我们开发的 Admin
组件实例上注册。所以需要将代码修改成如下:
ts
import { type App, createApp } from 'vue'
import type { Router } from 'vue-router'
import Admin from './admin.vue'
const adminApp = createApp(Admin);
Admin.install = (app: App, options: {
router: Router
}) => {
// 应该向 admin 实例注册
adminApp.use(options.router)
app.component('Admin', Admin)
}
export default Admin
打包发布后,发现,唉,怎么还是不起作用?
又经过吭哧吭哧调试发现原来是我们打包的时候,将 vue-router
的代码一起打包进组件代码里了。
可以看到打包出来的组件代码里把组件里用到的 useRoute
和 useRouter
方法一起打包进来了。
当我们执行 useRouter
方法去获取 router
对象的时候,就是执行的上图的代码
ts
function useRouter() {
return inject(routerKey);
}
通过 inject
来注入前面通过 adminApp.use(options.router)
注册的 router
对象。我们调试进入 inject
方法:
可以看到我们的 router
已经注册进来了(provides
中有 symbol(router)
对象),并且我们要取的 key 的值也是 symbol(router)
,但是 key in provides
却是 false
:
这是怎么回事呢?
我们再仔细看打包出来的代码
routerKey
是在打包代码里生成的一个 Symbol
值,而我们注册时的 key 是外部生成的。虽然两个值都是 symbol(router)
,但是由于 Symbol
值是独一无二的特性,只要它们不是同一个变量,它们就是不相等的。
所以我们打包时不能将 vue-router
的代码打包进我们的组件代码里。
我们打包是用的 vite
。在 vite
中我们可以通过 build.rollupOptions.external
配置来排除不需要打包的模块:
ts
export default defineConfig({
plugins: [
vue()
],
build: {
outDir: path.resolve(__dirname, './dist/'),
lib: {
entry: path.resolve(__dirname, './index.ts'),
},
rollupOptions: {
external: ['vue', 'vue-router'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
'vue': 'Vue',
'vue-router': 'vueRouter'
},
},
}
},
})
我们再来看打包生成出来的组件代码,可以看到 vue-router
相关的代码变成 import
方式引入了:
我们打包发布后再来运行一下,终于成功了!