
Vue 3.3 "Rurouni Kenshin" is now available and "is focused on developer experience improvements".
In this article, I give an overview of the highlighted features in Vue 3.3. Read the changelog if you are interested in all changes of this new version.
Props Destructuring
I think this is one of the coolest features of the new release. You can now destructure props without losing reactivity and also set default values:
<script setup lang="ts">
const { counter = 0, testId = 'counter' } = defineProps<{ counter?: number; testId?: string }>()
</script>
In my opinion, this is a very clean and "natural" way to define your props. Previously you had to use toRefs
in combination with withDefaults
to achieve the same result:
<script setup lang="ts">
const { counter, testId } = toRefs(
withDefaults(defineProps<{ counter?: number; testId?: string }>(), { counter: 0, testId: 'counter' })
)
</script>
defineModel
Vue 3.3 provides a very elegant way to support two-way binding with v-model
. Before 3.3 you had to write a lot of boilerplate code to support it:
<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
function onInput(event) {
emit('update:modelValue', event.target.value)
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
With 3.3 we can achieve the same functionality with less code:
<script setup>
const modelValue = defineModel()
</script>
<template>
<input v-model="modelValue" />
</template>
In this example, the defineModel
macro automatically registers a prop modelValue
and returns a ref that can be directly mutated. Additionally. it registers the update:modelValue
event.
Improved TypeScript Type Support
In the past, only local types such as type literals and interfaces could be used in the type parameter position of the defineProps
and defineEmits
compiler macros.
The reason for this was that Vue needed to analyze the properties on the props interface to create runtime options. However, this limitation has been addressed in version 3.3. The Vue compiler can now handle imported types and a limited set of complex types:
<script setup lang="ts">
import type { Props } from './foo'
// imported + intersection type
defineProps<Props & { extraProp?: string }>()
</script>
Generic Components
Your components can now accept generic type parameters via the generic
attribute if you are using <script setup>
:
<script setup lang="ts" generic="T">
defineProps<{
items: T[]
selected: T
}>()
</script>
More Ergonomic defineEmits
Typing defineEmits
was a bit verbose before 3.3:
<script setup lang="ts">
const emit = defineEmits<{
(e: 'foo', id: number): void
(e: 'bar', name: string, ...rest: any[]): void
}>()
</script>
Vue 3.3 provides a "more ergonomic" way:
<script setup lang="ts">
const emit = defineEmits<{
foo: [id: number]
bar: [name: string, ...rest: any[]]
}>()
</script>
Use console in the template
You can now use console
in your template:
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<h1>{{ count }}</h1>
<button @click="console.log(++count)">Increment</button>
</template>
In previous versions of Vue, this caused an error: TypeError: Cannot read properties of undefined (reading 'log')
Conclusion
Vue got so much better with this new version. The new features improve the developer experience and I'm very excited to see how the framework further evolves with the next upcoming releases.
For more information, read the official announcement and the GitHub changelog.
If you liked this article, follow me on Twitter to get notified about new blog posts and more content from me.
Alternatively (or additionally), you can subscribe to my weekly Vue newsletter: