[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
.
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 defineProps
, defineEmits
, 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>