组件库中vueRouter实例如何从外部注入

背景

当我们开发的 VUE 组件中需要用到 vue-router,但是这个 vue-router 实例是需要外部传递进来的。比如我们开发了一个类似 admin 模板的组件,里面用到了 vue-router 来实现页面路由的功能,但是这个路由的配置以及有哪些路由页面都是由使用我这个模板组件的人来控制的,所以 vue-router 实例也就只能由使用者创建了传递给我。

使用属性传入

最容易想到的方案就是通过 props 来将 vue-router 实力传递进来,然后再通过 provide 传递给所有子组件。但是使用 vue-router 的时候一般需要用到 routerouter 两个对象,这就需要将这两个都作为属性来传递。而且使用时我们也不能正常通过 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 的代码一起打包进组件代码里了。

可以看到打包出来的组件代码里把组件里用到的 useRouteuseRouter 方法一起打包进来了。

当我们执行 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 方式引入了:

我们打包发布后再来运行一下,终于成功了!

相关推荐
IT女孩儿8 分钟前
JavaScript--WebAPI查缺补漏(二)
开发语言·前端·javascript·html·ecmascript
m0_748256562 小时前
如何解决前端发送数据到后端为空的问题
前端
请叫我飞哥@2 小时前
HTML5适配手机
前端·html·html5
@解忧杂货铺4 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js
F-2H6 小时前
C语言:指针4(常量指针和指针常量及动态内存分配)
java·linux·c语言·开发语言·前端·c++
苹果酱05676 小时前
「Mysql优化大师一」mysql服务性能剖析工具
java·vue.js·spring boot·mysql·课程设计
gqkmiss6 小时前
Chrome 浏览器插件获取网页 iframe 中的 window 对象
前端·chrome·iframe·postmessage·chrome 插件
m0_748247558 小时前
Web 应用项目开发全流程解析与实战经验分享
开发语言·前端·php
m0_748255029 小时前
前端常用算法集合
前端·算法
真的很上进9 小时前
如何借助 Babel+TS+ESLint 构建现代 JS 工程环境?
java·前端·javascript·css·react.js·vue·html