typescriptvue.jsvuejs3vue-composition-api

Vue 3 defineEmits breaks defineProps types


I'm using Vue 3 and TS 4.4. I've got a component that defines its prop types with defineProps. When I add a call to defineEmits, VS Code starts telling me my props variable doesn't exist in the component template. Here's the source code:

<script setup lang="ts">
import { defineProps, defineEmits, VueElement, defineComponent } from "vue";

const emit = defineEmits<{
  "update:checked": boolean;
}>();

const props = defineProps<{
  label?: VueElement | string;
  checked?: boolean;
}>();
</script>

<template>
  <label>
    <input
      type="checkbox"
      :checked="props.checked"
      @change="(event) => emit('update:checked', (event.target as any)?.checked)"
    />
    {{ props.label }}
  </label>
</template>

And a couple of screenshots to better show what I'm seeing in VS Code. This is after adding defineEmits:

VS Code TS error

And this is without defineEmits:

VS Code without TS error

What is the correct way to define types for both props and emits?


Solution

  • The defineEmits<T>() generic argument is essentially a TypeScript interface that defines only functions, which receive a specific event name and optional arguments:

    interface Emits {
      (e: __EVENT1_NAME__ [, arg1: __ARG1_TYPE__ [, arg2: __ARG2_TYPE__]]...): void
      (e: __EVENT2_NAME__ [, arg1: __ARG1_TYPE__ [, arg2: __ARG2_TYPE__]]...): void
    }
    

    Examples:

    // as inline type
    const emits = defineEmits<{
      (eventName: 'hover', hovering: boolean): void
      (eventName: 'changed', newValue: number, id: string): void
    }>()
    
    // as interface
    interface Emits {
      (eventName: 'hover', hovering: boolean): void
      (eventName: 'changed', newValue: number, id: string): void
    }
    const emits = defineEmits<Emits>()
    
    // as type alias
    type Emits = {
      (eventName: 'hover', hovering: boolean): void
      (eventName: 'changed', newValue: number, id: string): void
    }
    const emits = defineEmits<Emits>()
    

    For your update:checked event, the code should look similar to the following:

    // as inline type
    const emits = defineEmits<{
      (e: 'update:checked', checked: boolean): void
    }>()
    
    // as interface
    interface Emits {
      (e: 'update:checked', checked: boolean): void
    }
    const emits = defineEmits<Emits>()
    
    // as type alias
    type Emits = {
      (e: 'update:checked', checked: boolean): void
    }
    const emits = defineEmits<Emits>()