Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Vue component's Props type parameter for prop type validation #2344

Open
1 task done
ktsn opened this issue Sep 28, 2020 · 11 comments
Open
1 task done

Use Vue component's Props type parameter for prop type validation #2344

ktsn opened this issue Sep 28, 2020 · 11 comments
Labels
Milestone

Comments

@ktsn
Copy link
Member

ktsn commented Sep 28, 2020

Issuehunt badges

  • 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.


IssueHunt Summary

Backers (Total: $100.00)

  • $100.00 have been anonymously funded.

Become a backer now!

Or submit a pull request to get the deposits!

Tips

@yoyo930021
Copy link
Member

yoyo930021 commented Oct 21, 2020

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

@octref
Copy link
Member

octref commented Nov 1, 2020

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

@octref
Copy link
Member

octref commented Nov 1, 2020

@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?

@yoyo930021
Copy link
Member

yoyo930021 commented Nov 2, 2020

@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.

@yoyo930021
Copy link
Member

yoyo930021 commented Nov 2, 2020

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.

@ktsn
Copy link
Member Author

ktsn commented Nov 20, 2020

@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. vuejs/vue-class-component#465

@youngboy
Copy link

youngboy commented Jan 14, 2021

  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 feat: use Vue TS plugin #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 vue-styleguidist/vue-styleguidist#965 in the same style

Thanks again guys, this is plugin is awesome.

@SnooHD
Copy link

SnooHD commented Apr 21, 2021

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

@issuehunt-oss
Copy link

issuehunt-oss bot commented May 17, 2021

An anonymous user has funded $100.00 to this issue.


@issuehunt-oss issuehunt-oss bot added the 💵 Funded on Issuehunt This issue has been funded on Issuehunt label May 17, 2021
@melan0802
Copy link

Is this issue has be abandoned? Or there is another plan?

@octref I got some ideas, would you like give me some advise?

  1. For class component & deccorator:
    We can assume instance type contains props type, by using InstanceType<typeof ChildComponent>[PropName], PropName is already in type PropInfo. There will be some limitations but at least won't worse with current typeString way.

  2. For import child component:
    In Use real proptype for prop type validation. Fix #2343. Fix #2312 #2422 , you are doing this by copy import statement, is could cover most cases. Pulling child component's type out from default export like @yoyo930021 wish will be a real challenge. Maybe we could inject export statement for components option but will brings wrong code completion when user using import. Or inect types into $options.components, just a rough idea.

  3. For vue3:
    All prop's type could be found in $props (using Vue.with for class component), just pull it out.

Thanks all of your works, would be great to get your advise.

@yoyo930021 yoyo930021 added this to the v0.36.0 milestone Oct 5, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

6 participants