How to Type Vue 3 DefineProps and DefinEmits with Typescript

Vue 3 comes with better Typescript support than its previous version. In fact, this version is built with Typescript from the ground up.

The compiler now provides macros, which are methods that we don’t need to import. However, they can only be used inside a setup script.

The ones you will use the most are defineProps and defineEmits. Let’s see how to type them with Typescript.

How to Type Vue 3 defineProps with Typescript

This macro, as the name suggests, allows us to define props accepted by the component.

Let’s imagine that our component will accept a list of users of the type User. We can use the defineProps macro with the PropType type like so:

<script setup lang="ts">
  import { type PropType } from 'vue'
  import { User } from 'somewhere'

  defineProps({
    users: {
      type: Array as PropType<User[]>,
      required: true
    }
  })
</script>

This approach is called runtime declaration. Although it works fine, there is another way that doesn’t involve casting. With type declaration we pass the type directly to the macro:

<script setup lang="ts">
  import { User } from 'somewhere'

  defineProps<{
    users: User[]
  }>()
</script>

When we want to use the props in the setup script, we assign the return value of defineProps to a variable

<script setup lang="ts">
  import { User } from 'somewhere'

  const props = defineProps<{
    users: User[]
  }>()

  doSomethingWith(props.users)
</script>

You might be wondering, how can we provide default values with this approach. Don’t worry, withDefaults macro got you covered:

<script setup lang="ts">
  import { User } from 'somewhere'

  const props = withDefaults(defineProps<{
    users: User[]    
  }>(), {
    users: () => []
  })

  doSomethingWith(props.user)
</script>

To give an Array prop type a default value provide a method that returns an array.

To make the users prop optional, add the Typescript optional operator ? to defineProps:

<script setup lang="ts">
  import { User } from 'somewhere'

  const props = withDefaults(defineProps<{
    users?: User[]  // <----- here
  }>(), {
    users: () => []
  })

  doSomethingWith(props.user)
</script>

And of course, if we use the prop in the template we will get auto-completion:

vue 3 prop autocompletion

How to Type Vue 3 defineEmits with Typescript

defineEmits is a macro that allows us, also as the name suggests, to define the events that the component can emit to its parent. Similar to defineProps, we pass types directly:

<script setup lang="ts">
  defineEmits<{
    (e: 'removeUser'): void
  }>()
</script>

Furthermore, when the event contains data, we can also type that. To type the emitted value, add it as an extra param to the method type:

<script setup lang="ts">
  defineEmits<{
    (e: 'removeUser', user: User): void
  }>()
</script>

To use it in the setup directly, assign the return value of defineEmits to a variable and call with the event name and parameter.

<script setup lang="ts">
  const emit = defineEmits<{
    (e: 'removeUser', user: User): void
  }>()

  const onRemoveUserClick = (user: User) => {
    emit('removeUser', user)
  }
</script>

And if we listen to this event from a parent component we will get better typescript support now:

Conclusion

Vue 3 macros are a joy to work with, you don’t have to import them and they are Typescript friendly. this promotes better productivity and faster development.

Amenallah Hsoumi
Amenallah Hsoumi

Senior Software Engineer, Indie Hacker, building cool stuff on the web!

Articles: 19