Vue.js 的响应式系统是其核心特性之一,它允许开发者以声明式方式构建用户界面,当数据发生变化时,视图会自动更新,无需手动操作 DOM。Vue 3.5 对响应式系统进行了重大重构,在保持原有行为兼容性的基础上,实现了性能的显著提升:内存占用降低 56%,大型深度响应式数组操作速度提升 10 倍,并解决了 SSR 期间因挂起计算导致的过时计算值和内存问题[3][18]。这一系统基于 Proxy 实现,相比 Vue 2 的 Object.defineProperty 机制,提供了更全面的响应式能力和更优的性能表现。
Vue 2 的响应式系统基于 Object.defineProperty 实现,通过遍历数据对象的属性并为其定义 getter 和 setter 来追踪变化。然而,这种方式存在固有局限:需要在初始化时遍历所有属性,无法监听对象新增属性或删除属性,对数组的某些操作(如通过索引修改元素)也无法触发响应。
Vue 3 采用 Proxy 重构了响应式系统,从根本上解决了这些问题。Proxy 可以创建一个对象的代理,从而实现对目标对象的属性读取、赋值、删除等操作的拦截。其核心优势在于:
以下是 Proxy 实现响应式的基础示例:
// 创建响应式对象
const target = { count: 0 };
const proxy = new Proxy(target, {
get(target, key) {
track(target, key); // 收集依赖
return target[key];
},
set(target, key, value) {
target[key] = value;
trigger(target, key); // 触发更新
return true;
}
});
// 使用代理对象
proxy.count++; // 会触发 set 拦截,进而触发更新
在 Vue 3.5 中,这一机制被进一步优化,特别是针对大型深度响应式数组的追踪效率,某些场景下操作速度提升可达 10 倍[18]。
Vue 3 提供了两种主要方式创建响应式数据:ref 和 reactive。两者基于不同的设计理念,适用于不同场景。
ref 用于将基本类型数据(如数字、字符串)转换为响应式对象,它会创建一个包含 .value 属性的包装对象。在模板中使用时,Vue 会自动解包 .value,无需手动访问;但在 JavaScript 代码中必须显式使用 .value。
类型标注与使用示例(用户信息管理案例):
import { ref } from 'vue';
// ref 定义数字类型
const age = ref<number>(25);
// ref 定义字符串类型
const name = ref<string>('Alice');
// JavaScript 中修改需使用 .value
age.value++; // age 变为 26
// 模板中自动解包(无需 .value)
// <template>{{ age }} - {{ name }}</template>
reactive 用于将对象类型数据(如对象、数组)转换为响应式对象,它直接返回一个代理对象,无需 .value 访问。但 reactive 存在局限性:不能直接赋值新对象(会丢失响应性),且解构后会失去响应性。
类型标注与使用示例:
import { reactive } from 'vue';
// reactive 定义对象类型
const user = reactive<{ name: string; age?: number }>({
name: 'Alice',
age: 25
});
// 直接修改属性(无需 .value)
user.age = 26;
// 解构会丢失响应性(错误示范)
const { name } = user;
name = 'Bob'; // 不会触发更新
toRefs 可将 reactive 对象转换为包含多个 ref 对象的集合,每个属性都是一个独立的 ref,从而支持解构赋值且保持响应性:
import { reactive, toRefs } from 'vue';
const user = reactive({ name: 'Alice', age: 25 });
const userRefs = toRefs(user);
// 解构后仍保持响应性
const { name, age } = userRefs;
age.value++; // user.age 变为 26,视图更新
使用建议:
ref。 reactive。 reactive 对象时,配合 toRefs 保持响应性。computed 用于创建依赖其他响应式数据的计算属性,它会缓存计算结果,只有当依赖的响应式数据变化时才重新计算,相比 methods 能显著提升性能。
methods:每次调用都会重新执行函数,无论依赖数据是否变化。computed:依赖数据不变时直接返回缓存结果,避免重复计算。import { ref, computed } from 'vue';
// 商品列表(ref 数组类型)
const products = ref<{ price: number; quantity: number }[]>([
{ price: 100, quantity: 2 },
{ price: 200, quantity: 1 }
]);
// 计算总价(只读 computed)
const totalPrice = computed(() => {
console.log('计算总价...'); // 仅依赖变化时执行
return products.value.reduce(
(sum, item) => sum + item.price * item.quantity,
0
);
});
console.log(totalPrice.value); // 400(首次计算)
products.value[0].quantity = 2; // 依赖未变,不重新计算
console.log(totalPrice.value); // 400(返回缓存)
products.value[0].quantity = 3; // 依赖变化,重新计算
console.log(totalPrice.value); // 500(重新计算)
computed 还支持通过 get 和 set 配置实现可写计算属性,用于双向绑定场景:
import { ref, computed } from 'vue';
const count = ref(1);
// 带 setter 的 computed
const doubleCount = computed({
get: () => count.value * 2,
set: (val) => { count.value = val / 2; } // 设置时反向更新依赖
});
doubleCount.value = 4; // 触发 setter,count 变为 2
console.log(count.value); // 2
Vue 3 提供 watch 和 watchEffect 两种监听响应式数据变化的方式,适用于不同场景。
watch 需显式指定监听源,支持配置监听时机、深度监听、立即执行等,灵活性高。
搜索框防抖案例(监听搜索文本变化,延迟发送请求):
import { ref, watch } from 'vue';
const searchText = ref('');
let timeoutId: number;
// 监听 searchText 变化
watch(
() => searchText.value, // 监听源(函数返回值为依赖)
(newVal, oldVal) => {
// 防抖:清除上一次定时器
clearTimeout(timeoutId);
// 延迟 500ms 发送请求
timeoutId = window.setTimeout(() => {
console.log(`搜索: ${newVal}`);
// 实际场景:fetch(`/api/search?q=${newVal}`)
}, 500);
},
{
immediate: true, // 初始立即执行一次
deep: false // 非对象类型无需深度监听
}
);
watchEffect 无需指定监听源,会自动收集函数内的响应式依赖,当依赖变化时重新执行。适用于“副作用跟随多个依赖变化”的场景。
清理副作用(取消过时 API 请求):
import { ref, watchEffect } from 'vue';
const searchText = ref('');
watchEffect((onInvalidate) => {
// 自动收集 searchText 依赖
const query = searchText.value;
const controller = new AbortController(); // 用于取消请求
// 发送请求
fetch(`/api/search?q=${query}`, { signal: controller.signal })
.then(res => res.json())
.then(data => console.log('搜索结果:', data));
// 清理函数:依赖变化或组件卸载时执行
onInvalidate(() => {
controller.abort(); // 取消上一次未完成的请求
console.log('取消过时请求:', query);
});
});
// 修改 searchText 会触发重新执行,并清理上一次副作用
searchText.value = 'vue';
watch 与 watchEffect 对比:
#的性能优化与最佳实践
Vue 3.5 响应式系统的重构带来了显著的性能提升,包括内存占用降低 56%、大型数组操作速度提升 10 倍等[18]。在实际开发中,还需注意以下最佳实践:
ref 或 reactive 包装。computed,而非 methods。deep: true,避免性能损耗。watch 和 watchEffect 中涉及定时器、事件监听等,需通过清理函数避免内存泄漏。通过理解并合理运用 Vue 3.5 的响应式系统,开发者可以构建高性能、易维护的前端应用,充分发挥其在性能与开发体验上的优势。