Vue 3 script setup prop validation typescript
Asked Answered
M

7

5

I'm trying to replace my Vue 2 options API props object code with Vue 3 script setup syntax in TypeScript.

Existing:

type: {
  type: String,
  default: 'button',
  validator: (prop) => ['button', 'submit', 'reset'].includes(prop)
}

I have this so far:

<script lang="ts" setup>
interface Props {
  type?: string;
}

const props = withDefaults(defineProps<Props>(), { type: 'button' });
</script>

But I can't find any info on how to handle prop validators in the script setup syntax.

Machutte answered 2/1, 2022 at 22:44 Comment(1)
what did you end up doing to get validators to work? None of the answers is working for me in March 2023Caban
M
5

I believe I've worked it out, still new to typescript but this should be the equivalent (will be validated in typescript rather than vue runtime)

interface Props {
  type?: 'button' | 'submit' | 'reset';
}
Machutte answered 2/1, 2022 at 22:55 Comment(2)
Nice idea for the typescript validation. But this solution means that, at runtime, there will be no validation.Evocative
for most use cases, this is sufficient. 99% of the time you'll be passing the type directly (ie, not from a variable), and if you ever do need to (fer example) use user input, the validation should probably happen where the input comes in, not here in the componentBoyt
C
5

You can use in defineProps the same structure as in Options API. (DOCS)

<script lang="ts" setup> 
  const props = defineProps({ 
    type: {
      type: String as PropType<'button' | 'submit' | 'reset'>,
      default: 'button',
      validator: (prop: Type) => ['button', 'submit', 'reset'].includes(prop)
    }
  });
</script>
Carolinecarolingian answered 3/1, 2022 at 11:47 Comment(12)
thanks for the alternative! Pretty new to Vue3 and Typescript but having to define each prop 3 times is...wild...Machutte
@Titan. I am not sure what you mean with 'define prop 3 times'. You just need to define them in <script setup> or in Options APICarolinecarolingian
type should be optional in interface as it has default value in defineProps ;)Quartan
Also typescript shout at me "Default export of the module has or is using private name 'Props'" when I don't export interface :)Quartan
Cannot get rid of: Type 'Props' does not satisfy the constraint 'ComponentObjectPropsOptions<Data>'. Index signature for type 'string' is missing in type 'Props'.ts(2344) 🤔Quartan
I think you have used type: string instead of type: String. @QuartanCarolinecarolingian
@orbis I think it is correct for detailed definitions, please kindly see docs here: vuejs.org/guide/components/props.html#prop-passing-detailsQuartan
@Luckylooke, if you share more code, we can debug this issue. I dont know you current code, that triggered you ErrorCarolinecarolingian
I thought to do the same, but unfortunately eslint is not happy: defineProps has both a type-only props and an argument.eslint(vue/valid-define-props)Caban
From the docs: "defineProps or defineEmits can only use either runtime declaration OR type declaration. Using both at the same time will result in a compile error."Mich
The type-based definition is a more concise syntax, and covers the some functionality but in a different way. Defaults can be added with defineDefaults, and validation is handled at compile time (complete with build errors) instead of runtime. As @Machutte referred to in his own answer.Mich
actually i messed up. I've adjusted my solutionCarolinecarolingian
Q
3

I end up with this at the end 🤷‍♂️

<script lang="ts" setup>
  import type { PropType } from 'vue';
  export type Type = 'button' | 'submit' | 'reset';

  export interface Props {
    type: Type;
  }

  defineProps({ 
    type: {
      type: String as PropType<Type>,
      default: 'button',
      validator: (prop: Type) => ['button', 'submit', 'reset'].includes(prop)
    }
  });
</script> 
Quartan answered 31/8, 2022 at 12:0 Comment(0)
M
0

Following up @Titan 's answer I like to write it in a way in which I get direct access to the prop in the script.

<script setup lang="ts">
type Type = 'button' | 'submit' | 'reset';

const { type } = withDefaults(defineProps<{
  type?: Type
}>(),{
  type: 'button'
})
//As it is already deconstructed, you can use it as `type` instead of `props.type`
</script>

Although I would recommend changing the prop name from type to something different since typescript might misunderstand the prop name for an actual type declaration.

Moffit answered 17/1, 2022 at 22:35 Comment(5)
Nice version, but only if you don't need to consume interface in some parent.. so @Orbis's version is better in that case :)Quartan
btw props are automatically available in template, you don't need to assign the value returned from withDefaults at all 👍Quartan
The question is how to handle validator: (x) => ... with typed syntaxCaban
You don't need a validator function in most cases when using the typed syntax, as the validation is handled by TypeScript.Mich
Unless you are doing something like date or email address validation.Mich
P
0

For those also arriving from Google, and since the Vue docs aren't very clear on this, you can do a straight conversion using the Vue 2 style options format as well:

const props = defineProps({
  type: {
    type: String,
    default: 'button',
    validator: (prop) => ['button', 'submit', 'reset'].includes(prop)
  }
})
Predigest answered 9/2, 2022 at 22:54 Comment(0)
S
0

I like to use this approaches
solution 1

interface Props {
  type: 'Pie' | 'Line' | 'Bar'
  data: Chart
}

withDefaults(defineProps<Props>(), {
  type: 'Line',
  data: () => {}
}) 

solution 2

enum ChartType {
  Pie = 'Pie',
  Line = 'Line',
  Bar = 'Bar',
}

interface Props {
  type: ChartType
  data: ChartData
}

And when I try to pass an invalid value for the type prop, I get a warning.

enter image description here

Shove answered 16/1 at 13:47 Comment(0)
S
-1

You can do this to avoid repeat the values.

<script lang="ts" setup>
const types = ['button' | 'submit' | 'reset'] as const
export type Type = typeof types[number]

const props = defineProps<Props>({ 
  type: {
    type: String as PropType<Type>,
    default: 'button',
    validator: (prop: Type) => types.includes(prop)
  });
</script>
Salvatore answered 23/1, 2023 at 17:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.