Skip to content

Vue 3.6 New Features Preview

Vue 3.6 is the next major version previewed by Evan You at VueConf 2025, with the core theme of "compile-time optimization + Vapor Mode going officially stable." This article analyzes the most noteworthy changes in 3.6, based on RFCs and early alpha builds.

Reactivity System: Ref Sugar Goes Stable

Ref Sugar ($ref, $computed) moves from experimental to stable, completely solving the verbosity of ref.value.

vue
<script setup lang="ts">
// Vue 3.5: requires .value
const count = ref(0);
const doubled = computed(() => count.value * 2);
watch(count, (val) => console.log(val));

// Vue 3.6 Ref Sugar: compiler auto-desugars
$ref: let count = 0;
$computed: let doubled = count * 2;

// watch can also be simplified
$watch: (count, (val) => console.log(val));

function increment() {
  count++; // no .value needed
}

// Type inference is completely correct
$ref: let user = { name: "张三", age: 25 };
// user's type is { name: string; age: number }
// NOT Ref<{ name: string; age: number }>
</script>

<template>
  <button @click="increment">{{ count }} ({{ doubled }})</button>
</template>

The compiler converts $ref to ref() and $computed to computed(), automatically adding .value for template access. This means zero runtime overhead — it's purely syntactic sugar.

defineProps Destructuring Default Values

3.6 finally supports declaring default values directly via destructuring, eliminating the need for withDefaults:

vue
<script setup lang="ts">
// Vue 3.5: required withDefaults
// const props = withDefaults(defineProps<{ title: string; size?: number }>(), {
//   size: 16,
// });

// Vue 3.6: direct destructuring + default values
const {
  title,
  size = 16,
  color = "#1a1a1a",
} = defineProps<{
  title: string;
  size?: number;
  color?: string;
}>();

// The compiler handles this automatically: destructured variables remain reactive
// No need to worry about losing reactivity after destructuring
console.log(size); // use directly, no need for props.size

// Complex defaults are also supported
const { items = [], onSelect = () => {} } = defineProps<{
  items?: Item[];
  onSelect?: (item: Item) => void;
}>();
</script>

This change eliminates one of the most common sources of noise in Vue projects, and type inference is more natural.

Component Lazy Loading Improvements

vue
<script setup lang="ts">
import { defineAsyncComponent, hydrateOnVisible } from "vue";

// New in Vue 3.6: conditional lazy loading
const AdminPanel = defineAsyncComponent({
  loader: () => import("./AdminPanel.vue"),
  // Only load when the user has permissions
  condition: () => userStore.isAdmin,
  loadingComponent: () => import("./AdminSkeleton.vue"),
  // SSR hydration strategy
  hydrate: hydrateOnVisible(),
});

// Batch prefetch
import { prefetchComponents } from "vue";

function onRouteHover() {
  // Prefetch target pages on mouse hover
  prefetchComponents([
    () => import("./Dashboard.vue"),
    () => import("./Analytics.vue"),
  ]);
}
</script>

hydrateOnVisible is a new hydration strategy in 3.6 — the component only hydrates when it enters the viewport, significantly reducing the amount of JS executed on the first screen.

Caveats and Best Practices for Reactive Props Destructuring

Although Vue 3.6's reactive destructuring is convenient, there are a few things to watch out for:

vue
<script setup lang="ts">
const { items, filter } = defineProps<{
  items: Item[];
  filter: string;
}>();

// ❌ Wrong: array methods on destructured values don't trigger updates
// items.push(newItem); // won't trigger re-render

// ✅ Correct: mutations go through emit or toRef
const emit = defineEmits<{
  "update:items": [items: Item[]];
}>();

function addItem(item: Item) {
  emit("update:items", [...items, item]);
}

// ✅ If you genuinely need a mutable local copy
import { toRef } from "vue";
const localItems = toRef(() => items);
// localItems is a Ref that tracks props.items changes
</script>

Summary

  • Ref Sugar goes stable; $ref/$computed eliminate .value verbosity with zero runtime overhead
  • Props destructuring supports native default values, removing the need for withDefaults wrappers
  • hydrateOnVisible enables viewport-level hydration, optimizing SSR first-screen performance
  • Batch prefetch API makes route transitions smoother
  • Vue 3.6's core strategy: "do more optimization at compile time, let developers write less glue code"

MIT Licensed