Vue 3.5 Released: New features and improvements

Vue 3.5 has just dropped this week, and it's packed with some nice features and improvements. I have been digging through the release notes and playing around with the new features, so let me glance over the highlights!

Reactive Props Destructure

Up until now, you could use toRef() to destructure props reactively. But in Vue 3.5, the Reactive Props Destructure feature has stabilized, making this much easier. Here's what it looks like now:

<script setup>
// Before
const props = defineProps(['count'])
const count = toRef(props, 'count')
 
// Now in Vue 3.5
const { count } = defineProps(['count'])
 
// It's reactive out of the box!
watchEffect(() => {
console.log(count)
})
</script>

But there's more! This new syntax also simplifies how to declare props with default values:

// Before
const props = defineProps({
count: {
type: Number,
default: 0,
},
message: {
type: String,
default: 'Laravel + Vue rocks!',
},
})
 
// Now in Vue 3.5
const { count = 0, message = 'Laravel + Vue rocks!' } = defineProps({
count: Number,
message: String,
})

useTemplateRef(): Clearer Template Refs

Vue 3.5 introduces a new way to handle template refs with the useTemplateRef() API. This solves the confusion of whether a ref is reactive data or a DOM element. Check it out:

<script setup>
import { useTemplateRef } from 'vue'
 
const inputRef = useTemplateRef('input')
</script>
 
<template>
<input ref="input">
</template>

This new API is not only clearer but also more flexible. You can now use it in composables, making it easier to access DOM elements from shared logic. Here's an example of how you might use it in a composable:

// useAutoFocus.js
import { useTemplateRef, onMounted } from 'vue'
 
export function useAutoFocus(refName) {
const elementRef = useTemplateRef(refName)
 
onMounted(() => {
elementRef.value?.focus()
})
 
return elementRef
}
 
// MyComponent.vue
<script setup>
import { useAutoFocus } from './useAutoFocus'
 
const inputRef = useAutoFocus('inputRef')
</script>
 
<template>
<input ref="inputRef" v-model="someData" />
</template>

In this example, we have created a useAutoFocus composable that uses useTemplateRef() to get a reference to an element and automatically focuses it when the component mounts. We can now reuse this logic across multiple components without duplicating code. It's pretty neat, right?

useId(): Generate Unique IDs

Vue 3.5 introduces useId(), a new utility for generating unique IDs that are stable across server and client renders:

<script setup>
import { useId } from 'vue'
 
const id = useId()
</script>
 
<template>
<form>
<label :for="id">Name:</label>
<input :id="id" type="text" />
</form>
</template>

This is particularly useful for creating accessible form elements and ensuring ID consistency in SSR applications.

SSR Improvements: Lazy Hydration and More

Server-Side Rendering gets some love in Vue 3.5 too:

  1. Lazy Hydration: You can now control when async components should be hydrated, for example, when they become visible in the viewport.
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
 
const AsyncComp = defineAsyncComponent({
loader: () => import('./Comp.vue'),
hydrate: hydrateOnVisible(),
})
  1. data-allow-mismatch: Suppress hydration mismatch warnings for inevitable differences between server and client renders:
<template>
<span data-allow-mismatch>{{ data.toLocaleString() }}</span>
</template>

Performance Boost

Vue 3.5 comes with some serious performance improvements:

  • 56% reduction in memory usage
  • Up to 10x faster operations on large, deeply reactive arrays

These optimizations mean your apps will be snappier and more efficient, especially when dealing with large datasets or complex reactivity.

Other Cool Features

Vue 3.5 brings several other improvements that are worth mentioning:

  • Deferred Teleport: The <Teleport> component has a new defer prop. This feature allows you to mount the teleported content after the current render cycle. It's particularly useful when you need to teleport content to elements that might not exist when the teleport component is first rendered.

  • onWatcherCleanup(): Vue 3.5 introduces a new globally imported API, onWatcherCleanup(), which makes it easier to register cleanup callbacks in watchers. This is great for managing side effects in your watchers. Now, you can easily abort pending requests, clear timers, or perform any other cleanup tasks when your watcher is about to re-run or when it's being torn down.

import { ref, watch, onWatcherCleanup } from 'vue'
 
const userInput = ref('')
const debouncedInput = ref('')
 
watch(userInput, (newValue) => {
const timer = setTimeout(() => {
debouncedInput.value = newValue
}, 500)
 
onWatcherCleanup(() => {
clearTimeout(timer)
})
})

Wrapping Up

Vue 3.5 is a solid update that brings some really nice quality-of-life improvements for developers. The Reactive Props Destructure and useTemplateRef() are my personal favorites – they are going to make our code cleaner and easier to understand.

Remember, Vue 3.5 is a minor release, so it should not break your existing Vue 3 apps. But as always, test thoroughly before updating production apps.