为了之后网站的更新,我着手做了一个自己的ui库。再之前测试打包上传到NPM然后再从其他项目中安装引入时,它似乎运行良好,所以我也就没太在意了。
可是,最近我更新了vue3版本之后,并且在另一个项目中实际使用时,它开始报错了,初步一看,似乎是因为我外面没有包提供Provide告知组件显示成什么样子的组件导致的,可是我有设置默认值啊。难道是vue-property-decorator
的锅导致默认值没有拿到?于是回组件开发那边去掉外面包裹的组件进行尝试,发现是可以获取默认值的。
实在是没想到是怎么回事,于是我想既然你就是要有个Provide,那我这边也给你写那个组件好了。可是,当我自信的按下ctrl+s然后在浏览器上按下F5之后,页面彻底没了显示,同时控制台显示了一个这样的警告:
- Invalid VNode type: Symbol()
对于这个问题,我毫无头绪,只能上网去找,可是找到的几个情况都跟我这个不一样,所以并不能解决问题。于是我把代码恢复到了之前的状态,准备详细查看,然后我从一大堆error中找到了这样一条不起眼的警告:
- inject() can only be used inside setup() or functional components.
好家伙,这就去看vue-property-decorator
那边Inject
的实现:
- export function Inject(
- options: InjectOptions = Object.create(null),
- ): VueDecorator {
- return createDecorator((componentOptions, key) => {
- const originalSetup = componentOptions.setup
- componentOptions.setup = (props, ctx) => {
- const result = originalSetup?.(props, ctx)
- const injectedValue = inject(options.from || key, options.default)
- return {
- ...result,
- [key]: injectedValue,
- }
- }
- })
- }
嗯,确实是用setup
做的啊,并没有什么问题。思来想去老久,没有想到啥答案。唯一能想到的是文档中提到了一句:
如果使用字符串 key 或非类型化 symbols,则需要显式声明注入值的类型:
- const foo = inject<string>('foo') // string | undefined`
但是我这样修改之后,问题仍然没法解决。
过了一天之后,想到那么vue是怎么判断我是否是在setup中调用的呢,会不会因为是因为vue3现在还不完善的bug?正好整个警告的点上是有堆栈信息的,所以我也就直接点击进去打了个断点一看,它的代码如下:
- function inject(key, defaultValue, treatDefaultAsFactory = false) {
- // fallback to `currentRenderingInstance` so that this can be called in
- // a functional component
- const instance = currentInstance || currentRenderingInstance;
- if (instance) {
- //...
- }
- else if ((process.env.NODE_ENV !== 'production')) {
- warn(`inject() can only be used inside setup() or functional components.`);
- }
- }
好了,首先确定了这个判断肯定是在vue调用setup时对currentInstance
赋值,调用render时对currentRenderingInstance
赋值,然后通过这两个变量可以获取到当前从属的是哪个组件。可是问题来了啊,为什么这个值会变成空的呢?回前几步继续调试看看。似乎没啥问题,每个组件都正常赋值了,包括这个组件也一样。那好,我多跟踪几步看是啥时候丢的。
可是跟踪了一圈好像没有其他地方会导致currentInstance
变为空啊。似乎又没头绪你,然后我再调试了一次。嗯,等等,怎么还跳了个文件呢?再一看上面打开的文件才发现,好家伙,我们的组件创建都是在项目所属的文件夹下的node_modules中,可是一到inject这里却跳到了node_modules/@thestarweb/ui/node_modules中,去看了下,这个文件真实存在。
嗯,破案了。同一个模块在不同的文件中,因为闭包,或者说模块独立性的问题,他们的变量当然是独立的。那怎么解呢?为什么npm会给我安装两个同样的模块呢。个人觉得npm可能是为了防止冲突,特别是版本冲突,所以,每个模块的运行依赖都会独立安装(这也是个麻烦事,所以cnpm在这里会用很多的快捷方式,而不是完全独立安装一份新的,但是我用的还是npm装的,不是说cnpm不好,而是这样可能会把问题掩盖过去,然后就一直没修复)。那么我要怎么操作才能告诉npm,安装我这个库就必须安装vue,并且这个vue跟项目还是得共享同一个呢?那就找找现成的别人的写法咯,比如ElementUI的package.json
文件,然后就会发现有peerDependencies
这么一个东西,它就是告诉npm,依赖这个包的项目必须要包含的依赖,并且这个依赖会在项目一级。这样就完事了?提交代码,github-action开始自动给我打包发布,然后我收到了运行出错的邮件,显示错误内容如下:
- Error: node_modules/vue-class-component/dist/vue-class-component.d.ts(1,39): error TS2307: Cannot find module 'vue' or its corresponding type declarations.
嗯peerDependencies
的依赖并不会主动安装啊,那么的话,就只能在devDependencies
中也加入这个依赖,这样这个依赖就必定会安装了,ElementUI也是这么做的。虽然感觉还是有点怪怪的,但是至少,现在我这个库终于是真实可用的了。