vuejs / vue

This is the repo for Vue 2. For Vue 3, go to https://github.com/vuejs/core
http://v2.vuejs.org
MIT License
207.82k stars 33.67k forks source link

Vue.component doesnt except argument of type `Component` #8719

Open backbone87 opened 6 years ago

backbone87 commented 6 years ago

Version

2.5.17

Reproduction link

https://codesandbox.io/s/8z04jxj3y8

Steps to reproduce

What is expected?

I can pass any component to Vue.component

What is actually happening?

Type error:

Argument of type 'Component<DefaultData<never>, DefaultMethods<never>, DefaultComputed, Record<string, any>>' is not assignable to parameter of type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Rec...'.
  Type 'VueConstructor<Vue>' is not assignable to type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Rec...'.
    Value of type 'VueConstructor<Vue>' has no properties in common with type 'ComponentOptions<Vue, DefaultData<Vue>, DefaultMethods<Vue>, DefaultComputed, PropsDefinition<Rec...'. Did you mean to call it?

Adding the following overload to Vue.component will fix it:

    component<Data, Methods, Computed, Props>(
      id: string,
      definition: Component<Data, Methods, Computed, Props>,
    ): ExtendedVue<V, Data, Methods, Computed, Props>;
ktsn commented 6 years ago

This is because Component type is a union type with VueConstructor and ComponentOptions.

Why do you use Component type? It means your getComponent function can return both Vue constructor and component options object. I think you should use more concrete type such as VueConstructor or ComponentOptions as the return type.

backbone87 commented 6 years ago

Because the function loads components via webpacks require.context. Also there is a EsModule type for async comps that hints against Component. There is also a Vue.component overload foreach member type of the Component union. I think that is just a problem of typescript not being able to check that correctly.

backbone87 commented 6 years ago

Another indicator for this being a ts problem is that the following works:

const comp: Component = {};
Vue.component('test', comp);
posva commented 6 years ago

well, {} is a valid component

backbone87 commented 6 years ago

ofc it is. that is not the point. but then TS somehow finds the correct Vue.component overload:

// type Component = typeof Vue | ComponentOptions | FunctionalComponentOptions;

const first = {};
const firstComponent: Component = first;
Vue.component('first', first); // ok
Vue.component('firstComponent', firstComponent); // ok

const second = { functional: true };
const secondComponent: Component = second;
Vue.component('second', second); // ok
Vue.component('secondComponent', secondComponent); // ok

const third = Vue.extend({});
const thirdComponent: Component = third;
Vue.component('thrid', third); // ok
Vue.component('thirdComponent', thirdComponent); // ok

declare function getComponent(): Component;
const final: Component = getComponent();
Vue.component('final', final); // type error

It is a typescript problem. TS seems like it doesnt allow multiple overloads to match (what the final case is requiring).

backbone87 commented 6 years ago

here is a plain typescript example demonstrating the problem:

interface A {
  a: string;
}
interface B {
  b: string;
}
interface C {
  c: string;
}
type union = A | B | C;
declare function acceptsUnion(x: A): void;
declare function acceptsUnion(x: B): void;
declare function acceptsUnion(x: C): void;
declare function returnsUnion(): union;
acceptsUnion(returnsUnion()); // type error
ktsn commented 6 years ago

I see your use case. I'm not sure that union type sometimes passes overload but we can replace fallback overload of component method with Component type.

backbone87 commented 5 years ago

any update on this?

i am using the following vue.d.ts in my projects, which solves the type problem according to @ktsn proposed solution:

import Vue, { Component } from 'vue';
import { ExtendedVue } from 'vue/types/vue';

declare module 'vue/types/vue' {
  interface VueConstructor<V extends Vue = Vue> {
    component<Data, Methods, Computed, Props>(
      id: string,
      definition: Component<Data, Methods, Computed, Props>,
    ): ExtendedVue<V, Data, Methods, Computed, Props>;
  }
}