Recently I was thinking about encapsulating my own components based on VUe3 + TS. I came across a type definition pit in the middle
Components in the sample
The simplest example is to encapsulate a Hello component in the following directory:
├── Bass exercisesCopy the code
// Hello.ts
import { App } from 'vue'
import Hello from './Hello.vue'
type SFCWithInstall<T> = T & { install(app: App): void; } // This is the type taken from element-puls
Hello.install = (app: App): void= > {
app.component(Hello.name, Hello)
}
const _Hello: SFCWithInstall<typeof Hello> = Hello
export default _Hello
Copy the code
An error
Define the component install method and export the component in the same way I wrote the element-puls component. Type error:
Cannot define the type "DefineComponent<{}, {}, any, ComputedOptions, MethodOptions, ComponentOptionsMixin, ComponentOptionsMixin,... 4 more ... , {}> "assigned to type" SFCWithInstall<DefineComponent<{}, {}, any, ComputedOptions, MethodOptions, ComponentOptionsMixin, ComponentOptionsMixin, ... 4 more ... , {} > > ". Type "ComponentPublicInstanceConstructor < any, any, any, any ComputedOptions, MethodOptions> & ComponentOptionsBase<Readonly<{} & ... 1 more ... & {} >,... 8 more ... , {}> &vnodeprops & AllowedComponentProps & ComponentCustomProps" < install > {install(app: app <any>): void; }" is required. ts(2322)Copy the code
Problem analysis
There is no doubt that the hello.ts code is fine. The reason for this type of error actually comes from this line:
import Hello from './Hello.vue'
Copy the code
This is a very insidious problem caused by TS’s recognition of the VUE type. At the beginning of the project, the vUE type declaration I copy from the Internet is as follows:
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
Copy the code
When we import Hello, the component type is the normal component type DefineComponent<{}, {}, any>, and there is no install attribute in this type, which results in a subsequent error.
The new type SFCWithInstall
defined here does not work, the exact reason is still to be found
The solution
Therefore, our solution to this problem is to add the install attribute to the component type. It can be done in a variety of ways:
// The first uses the union type
declare module '*.vue' {
import type { App, DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any> & {
install(app: App): void
}
export default component
}
Copy the code
// The second uses the function return value type
// defineComponent's return value type itself contains the install attribute, which is more intuitive and fits the component's own type
// I prefer this one
declare module '*.vue' {
import { defineComponent } from 'vue'
const component: ReturnType<typeof defineComponent>
export default component
}
Copy the code
After modification, the error message disappears. Further, if the type of the Hello component itself contains the install attribute, would SFCWithInstall
not be needed?
// Hello.ts
import { App } from 'vue'
import Hello from './Hello.vue'
Hello.install = (app: App): void= > {
app.component(Hello.name, Hello)
}
export default Hello
Copy the code
There are no problems, no errors reported, and no errors reported when installing components.
Wonder why Element-Puls keeps this type