[Vue] Vue 3.3 TypeScript Upgrades for Macro

Vue 3.3 brings an upgraded experience for using TypeScript with compiler macros such as:

  • defineProps
  • defineEmits
  • defineSlots

First, defineProps in itself has not changed, but now you can use imported types with it (and also with other macros as well).

import type { FooProps } from './Props.ts'

const props = defineProps<FooProps>()

Now for defineEmits, we used to define the types for the emits like this:

const emit = defineEmits<{
  (event: 'update', text: string): void
  (event: 'quit'): void
}>()

The issue here is that since emit doesn’t return anything, the return type will always be void—making it redundant to specify this for every event.

Vue 3.3 provides an alternative to this:

const emit = defineEmits<{
  update: [text: string]
  quit: []
}>()

Basically, you just specify name of the event and the types for the parameters of the event handlers.

Which approach should you choose? While both work equally well functionally, the choice depends on how you think about event emission. Do you focus more on the emit function itself, or on the event handler that it triggers? The new type signature better reflects the event handler’s parameters, while the original type signature aligns with how you use the emit function.


Lastly, there’s a new macro in Vue 3.3 called defineSlots.

<script setup lang="ts">
defineSlots<{
  default: () => any
}>()
</script>

<template>
  <div>
    <slot />
  </div>
</template>

If you don’t use TypeScript, this new macro won’t affect you since slots can be used without being defined first. The purpose of defineSlots is to provide type support for slot usage.

If you need default slot for your component, you define it like this:

<script setup lang="ts">
defineSlots<{
  default?: () => any
}>()
</script

And if this slot requires props, you can specify the type for the first parameter:

<script setup lang="ts">
defineSlots<{
  default?: (props: { bar: number }) => any
}>()
</script>

As you can see, the slot in the template has a type error because we haven’t set the prop that was defined with defineSlots.

slot_error.png

This is what defineSlots is useful for; reminding you to set the right props in the <template>:

<template>
  <div>
    <slot :bar="1" />
  </div>
</template>

If you want to learn more about definePropsdefineEmits, or defineSlots, you can check out the Vue.js documentations.


Code

📁 /src/components/MyComponent.vue

<script setup lang="ts">
import type { FooProps } from './Props.ts'

const props = defineProps<FooProps>()

const emit = defineEmits<{
  update: [text: string]
  quit: []
}>()

const reset = () => {
  emit('update', '')
}

defineSlots<{
  default?: (props: { bar: number }) => any
}>()
</script>

<template>
  <div>
    <p>{{ props.foo }}</p>
    <button @click="reset">Reset</button>
    <slot :bar="1" />
  </div>
</template>

📁 /src/components/Props.ts

export interface MyComponentProps { foo: string }

📁 /src/App.vue

<script setup lang="ts">
import { ref } from 'vue'
import MyComponent from './components/MyComponent.vue'

const foo = ref('foo')
</script>

<template>
  <MyComponent :foo="foo" @update="(newVal) => foo = newVal" />
</template>
posted @ 2025-03-13 15:01  Zhentiw  阅读(34)  评论(0)    收藏  举报