Vetur: Use Vue component's Props type parameter for prop type validation

Created on 28 Sep 2020  ·  9Comments  ·  Source: vuejs/vetur

  • [x] I have searched through existing issues

Feature Request

To use Vue component Props type parameter for prop type validation feature.

Vue component type has dedicated Props type parameter to represent props:

By using it, we can validate any kind of component if it is properly typed. An example use case is Vue Class Component. I'm planning to introduce a new way to define component props and it will be quite a different API interface from the basic Vue's prop definition. We can even use prop type validation with class component if we use Props type parameter from the component.

It may also solve #2312 and #2343.

feature-request template-interpolation

Most helpful comment

What is the status on this?
Would love to see this working :)

All 9 comments

Wow, I mentioned the same method in Discord with @octref .🤣

Here is a POC in TS AST viewer.
https://ts-ast-viewer.com/#code/KYDwDg9gTgLgBDAnmYcAKUJgDwBUB8cAvHAN5wAUAlAFxy5wC+cAPmXAHbADuFAdAICGUAOYBnOlwBuwKAG0AurXpwAZHAgAjAFbAAxvGZtyXXgL7DxdMTCgBLDiMXKAYgFcOBuxA5MAUH6gkLAIyKgYWLhheIQkETgErOiYCfiKANwBQdDwSCjJWABqggA2dgAmgjDQMcQFYADyYDDeHGK1bPFRKDGZfnmoACJVgoPAAGbYwzCCADT1YrFw04JJFBQwABZ2EnAASsCC5T4liNjxi1TEhCtUWeA5cA4wsuOCeuEpTS0+7bhEgg4iEIpD8cFCKAA-HQutECJlwVBgABHNx2JHlaFwTQQCAlQ4cBFwcoTQRuEowLEMNgccklJIeEnjBzAcprajXFQ0ukMjhMlnlKhEqSlCpVaCQigikpuYB0XDKHF4gmZRj3YK5ML7fTQcoXMbMjh2H4cWokUHguQAaSevgA1sBEBBxvQFDCUsUypVqlA8NaFPg-GqAgM4ABhCAAW0gXA4MG+rXaK3mFyWFvBcDAKV2Bz0uv1Ewcxta52z+FmYIz3sEdBWBqmIxTZaDIa1EejPmAcYAkm0Zp5gA2Zk2sIs6is1AsAnm+3BQC8+XUh3MFvgKFgTbt2zGu-HmonlyOwJc6NvOz2+4CPofV5z04jgDA3FBfKRmIIxOGozuLzYr4Pk1XFt1UeJkyQpOcQAXcoKHvTNszoOCMzgcZcUQytkMwiE5TgABlWwHBECssKwpFUXRVk6FsWUMMwtVkMYYiqxGSgriQjMkSfF8yFokjsWEKjtjEPhUIgXiM3oiSgzuIA

We can do that by injecting a fake attribute.

Here's an example.

// vue-editor-bridge
export declare const __veturInjectComponentData = <Data, Props>(instance: ComponentInstance<Data, Props>) => {
    return instance as ComponentInstance<Data, Props> & {
        __vlsComponentData: {
            props: Props & { [other: string]: any },
            on: ComponentListeners<ComponentInstance<Data, Props>>;
            directives: any[];
        } 
    }
}

// childComponent.vue
export default __veturInjectComponentData({
  props: {
      foo: {
          type: String,
          required: true
      }
  }
})

// parentComponent.vue
import childComponent from './childComponent.vue'

declare const __vlsComponentHelper__app_navbar: {
  <T>(
    vm: T,
    tag: string,
    data: childComponent.__vlsComponentData & ThisType<T>,
    children: any[]
  ): any
}

POC: https://ts-ast-viewer.com/#code/KYDwDg9gTgLgBDAnmYcAKUJgDwBUB8cAvHAN5wAUAlAFxy5wC+cAPmXAHbADuFAdAICGUAOYBnOlwBuwKAG0AurXpwAZHAgAjAFbAAxvGZtyXXgL7DxdMTCgBLDiMXKAYgFcOBuxA5MAUH6gkLAIyKgYWLhheIQkETgErOiYCfiKANwBQdDwSCjJWABqggA2dgAmgjDQMcQFYADyYDDeHGK1bPFRKDGZfnmoACJVgoPAAGbYwzCCADT1YrFw04JJFBQwABZ2EnAASsCC5T4liNjxi1TEhCtUWeA5cA4wsuOCeuEpTS0+7bhEgg4iEIpD8cFCKAA-HQutECJlwVBgABHNx2JHlaFwTQQCAlQ4cBFwcoTQRuEowLEMNgccklJIeEnjBzAcprajXFQ0ukMjhMlnlKhEqSlCpVaCQigikpuYB0XDKHF4gmZRj3YK5ML7fTQcoXMbMjh2H4cWokUHguQAaSevgA1sBEBBxvQFDCUsUypVqlA8NaFPg-GqAgM4ABhCAAW0gXA4MG+rXaK3mFyWFvBcDAKV2Bz0uv1Ewcxta52z+FmYIz3sEdBWBqmIxTZaDIa1EejPmAcYAkm0Zp5gA2Zk2sIs6is1AsAnm+3BQC8+XUh3MFvgKFgTbt2zGu-HmonlyOwJc6NvOz2+4CPofV5z04jgDA3FBfKRmIIxOGozuLzYr4Pk1XFt+jbb9zxgAAZHYF1kP400ra1bTgB0nRdXAFCxCgABJgBkON5X9K4iEIQFEFVPoZxsOAAH1qJkJ8oF7XQDDPWMYAnEgb1TCgHD-AdTzAtjez469ANTIiQUrB8GN8Xj+w+OAPy-DshMvAcuLLSd7wzcFaKkEoxFY3cVjobSdJ0rNR3dUctLgOQIC2WRrFsBwRDdRSgSYCtzJ8jQOAElTdygmwu1g7AjN-eSAMbVd8CJXyq3RfQWhkXZSIyKTzOYTK1WDSj4DzQK4zqPTH2fJjkoi9iRgoecu3KChtMs49TMynTxlxVqEp0gY6AAZRcxxvO6h9USS8o6FsWU2ozNUdMYYbwWrSgrjMjMkRksgZp8zRhEm7YxD4DqIG2-x5qDKg7j8QqfxgPg9IMqqVj8IA

That's what's working for me, now hooking them up:

Vue 2:

import Vue from 'vue'
import type { ExtendedVue } from 'vue/types/vue'

const __veturInjectComponentData = <Instance extends Vue, Data, Methods, Computed, Props>(
  instance: ExtendedVue<Instance, Data, Methods, Computed, Props>
) => {
  return instance as ExtendedVue<Instance, Data, Methods, Computed, Props> & {
    __vlsComponentData: {
      props: Props & { [other: string]: any }
      on: ComponentListeners<ExtendedVue<Instance, Data, Methods, Computed, Props>>
      directives: any[]
    }
  }
}

const comp = __veturInjectComponentData(
  Vue.extend({
    props: {
      foo: {
        type: String,
        required: true
      }
    },
    data() {
      return {
        bar: this.foo
      }
    }
  })
)

comp.__vlsComponentData

Vue 3:

import { defineComponent } from 'vue'
import type { Component, ComputedOptions, MethodOptions } from 'vue'

const __veturInjectComponentData = <Props, RawBindings, D, C extends ComputedOptions, M extends MethodOptions>(
  instance: Component<Props, RawBindings, D, C, M>
) => {
  return instance as Component<Props, RawBindings, D, C, M> & {
    __vlsComponentData: {
      props: Props & { [other: string]: any }
      on: ComponentListeners<Component<Props, RawBindings, D, C, M>>
      directives: any[]
    }
  }
}

const comp = __veturInjectComponentData(
  defineComponent({
    props: {
      foo: {
        type: String,
        required: true
      }
    },
    data() {
      return {
        bar: this.foo
      }
    }
  })
)

comp.__vlsComponentData

@ktsn I have a rough prototype at #2422. It seems to have fixed #2312 and #2343, works fine with Vue2/3, but doesn't seem to work with vue-class-component — do you mind taking a look?

@ktsn I have a rough prototype at #2422. It seems to have fixed #2312 and #2343, works fine with Vue2/3, but doesn't seem to work with vue-class-component — do you mind taking a look?

I don't think this PR will solve #2343.
Because this interface and type alias probably not from import statments.
It could be defined directly in the file.

We should just import the component script in and try to pull it out.

and I don't think we actually need to inject an export default for each component.

We can keep it and inject when we use.

In vue2 is call ExtendedVue.
In vue3 is call ComponentPublicInstanceConstructor.

@octref

I have a rough prototype at #2422. It seems to have fixed #2312 and #2343, works fine with Vue2/3, but doesn't seem to work with vue-class-component — do you mind taking a look?

Looks like when we directly look at $props type like the below, it obtains the correct Props type from class component.

export const ${injectComponentDataName} = <Props, RawBindings, D, C extends ComputedOptions, M extends MethodOptions>(
  instance: { new (...args: any[]): { $props: Props } }
) => {
  return instance as Component<Props, RawBindings, D, C, M> & {
    __vlsComponentData: {
      props: Props & { [other: string]: any }
      on: ComponentListeners<Component<Props, RawBindings, D, C, M>>
      directives: any[]
    }
  }
}

This is the same way how Vue 3 get props type on h function.

https://github.com/vuejs/vue-next/blob/62830f8fa40715bcf3964daa611d5dda395b7a34/packages/runtime-core/src/h.ts#L147-L151

Just in case, we have to use the new API Vue.with to properly type props with vue-class-component. https://github.com/vuejs/vue-class-component/issues/465

  instance: { new (...args: any[]): { $props: Props } }

in my local test, with this modification this works for me perfectly (vue 3)... 👍

But this issue has been not updated for a while. I'm curious what is the plan for the next ?

  1. and what about https://github.com/vuejs/vetur/pull/2145 ? looks like it is a different approach and have some feature overlapping while with some strange different behavior.
  2. props hover can get correct type hint, but code-complete/suggestion is not work as expect
  3. also, what about child component emits type infer 😭
export default {
  emits: {
    click: (e: Event, another: string) => {
      return true
    }
  },
}

this would works nicely with https://github.com/vue-styleguidist/vue-styleguidist/issues/965 in the same style

Thanks again guys, this is plugin is awesome.

What is the status on this?
Would love to see this working :)

Was this page helpful?
0 / 5 - 0 ratings