vue3+ts组件封装踩坑(install引发的类型错误)
最近在琢磨基于vue3+ts封装自己的组件,中间碰到一个类型定义的坑,在此做个记录和分享
组件示例
举一个最简单的例子,要封装一个Hello组件,目录如下:
Hello
├── Hello.vue
└── Hello.ts
// Hello.ts
import { App } from 'vue'
import Hello from './Hello.vue'
type SFCWithInstall<T> = T & { install(app: App): void; } // 这是从element-puls取过来的类型
Hello.install = (app: App): void => {
app.component(Hello.name, Hello)
}
const _Hello: SFCWithInstall<typeof Hello> = Hello
export default _Hello
报错
定义组件install方法,导出组件,我这里的写法和element-puls组件一样。到这里就出现了类型报错,错误信息:
不能将类型“DefineComponent<{}, {}, any, ComputedOptions, MethodOptions, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>”分配给类型“SFCWithInstall<DefineComponent<{}, {}, any, ComputedOptions, MethodOptions, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ..., {}>>”。
类型 "ComponentPublicInstanceConstructor<any, any, any, any, ComputedOptions, MethodOptions> & ComponentOptionsBase<Readonly<{} & ... 1 more ... & {}>, ... 8 more ..., {}> & VNodeProps & AllowedComponentProps & ComponentCustomProps" 中缺少属性 "install",但类型 "{ install(app: App<any>): void; }" 中需要该属性。ts(2322)
问题分析
毫无疑问,Hello.ts
的代码是没有问题的。出现这种类型报错的原因实际来自于这一行:
import Hello from './Hello.vue'
这是一个非常隐蔽的问题,是由于ts对于vue类型的识别导致的。搭建项目之初,vue类型声明我是从网上copy的,是下面这种:
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
导致我们导入Hello
组件时,组件的类型就是普通的组件类型DefineComponent<{}, {}, any>
,这个类型中是没有install
属性的,所以才会导致后续的报错。
这里定义了新的类型
SFCWithInstall<T>
没有作用,具体原因还在寻找
解决方案
因此,我们解决这个问题的思路就是在组件类型中补充install
属性。做法可以有多种:
// 第1种 使用联合类型
declare module '*.vue' {
import type { App, DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any> & {
install(app: App): void
}
export default component
}
// 第2种 使用函数返回值类型
// defineComponent函数的返回值类型本身是包含install属性的,这种做法更直观且更贴合组件本身的类型
// 个人更推荐这种
declare module '*.vue' {
import { defineComponent } from 'vue'
const component: ReturnType<typeof defineComponent>
export default component
}
修改后,报错消失。进一步思考,如果Hello
组件的类型本身是包含install
属性,那么SFCWithInstall<T>
是不是就已经不需要了?
// Hello.ts
import { App } from 'vue'
import Hello from './Hello.vue'
Hello.install = (app: App): void => {
app.component(Hello.name, Hello)
}
export default Hello
没有问题,不报错,安装组件时也没有报错。
不知道element-puls为什么要保留这个类型
转载自:https://juejin.cn/post/6985363584804978719