Skip to content

Vue 3.5: Reactivity System Rewrite and Performance Leap

Vue 3.5 has been released — another important version following 3.4. The most significant change is a complete rewrite of the reactivity system, bringing notable memory and performance improvements.

Reactivity System Rewrite

Vue 3.5 refactored the underlying implementation of reactive(), ref(), and computed(), with the core goal of reducing memory usage.

Official data shows:

内存占用降低 56%(大型响应式对象场景)

The rewrite focused on optimizing the Proxy handler implementation and reducing the creation of intermediate objects.

Impact on Real Projects

We have a data-intensive admin panel where pages often mount 1000+ reactive nodes:

vue
<script setup lang="ts">
// 以前用 shallowRef 处理大数组避免响应式开销
const largeList = shallowRef<Item[]>([]);

// Vue 3.5 之后,普通 ref 也足够轻量
const largeList = ref<Item[]>([]);

// 1000 个对象的响应式包装,内存从 ~8MB 降到 ~3.5MB
</script>

effectScope Enhancement

Vue 3.5 enhanced the effectScope API, making unified management of side effects more elegant:

typescript
import { effectScope, watch, ref, onScopeDispose } from "vue";

function useDataSync(key: string) {
  const scope = effectScope();

  scope.run(() => {
    const data = ref(null);

    // 所有 watch 都在这个 scope 内
    watch(
      data,
      (val) => {
        localStorage.setItem(key, JSON.stringify(val));
      },
      { deep: true }
    );

    // scope 销毁时,所有 watch 自动清理
    onScopeDispose(() => {
      console.log(`sync for ${key} disposed`);
    });
  });

  return scope;
}

// 在组件中使用
const syncScope = useDataSync("user-settings");
// 组件卸载时,scope 内的所有副作用自动清理

defineModel Stabilized

Introduced in Vue 3.4, defineModel becomes a stable API in 3.5, no longer requiring the experimental flag:

vue
<!-- 子组件 -->
<script setup>
// 以前:需要 modelValue + emit update
// const props = defineProps(['modelValue']);
// const emit = defineEmits(['update:modelValue']);

// Vue 3.5:一行搞定
const modelValue = defineModel();
const count = defineModel("count", { default: 0 });
</script>

<template>
  <input v-model="modelValue" />
  <input v-model="count" type="number" />
</template>

useId

The new useId composable generates SSR-safe unique IDs:

vue
<script setup>
import { useId } from "vue";

const labelId = useId(); // "v-0"
const inputId = useId(); // "v-1"

// SSR 和 CSR 水合时保证一致
</script>

<template>
  <label :for="inputId">用户名</label>
  <input :id="inputId" :aria-describedby="labelId" />
</template>

Lazy Teleport

The Teleport component gains a defer option, delaying teleportation until the DOM is ready:

vue
<template>
  <!-- 以前:如果 #modal-root 还没渲染,Teleport 会报错 -->
  <Teleport to="#modal-root" defer>
    <Modal />
  </Teleport>
</template>

data-allow-mismatch

A new attribute for handling SSR hydration mismatches:

vue
<template>
  <!-- 日期、相对时间等经常不一致的内容 -->
  <time data-allow-mismatch>{{ formattedDate }}</time>
</template>

Team Upgrade Recommendations

bash
pnpm update vue@3.5 @vitejs/plugin-vue

Upgrade checklist:

  1. Remove the experimentalDefineModel configuration
  2. In large-array scenarios, remove shallowRef and use regular ref
  3. Migrate component ID generation logic to useId
  4. Test that SSR hydration works correctly

Summary

  • Reactivity system rewrite: memory usage reduced by ~56%, significant gains in large-data scenarios
  • defineModel stabilized: simplifies v-model two-way binding
  • useId: SSR-safe unique ID generation
  • effectScope enhanced: unified side-effect management
  • Lazy Teleport: solves the issue of the target node not being ready

MIT Licensed