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