Skip to content

VueJS - 响应式接口

Vue 的核心特性是它的响应性系统。当您修改响应式数据时,Vue 会自动检测到变化并高效地更新 DOM。Vue 3 使用 JavaScript Proxies 对响应性系统进行了重大改进,使其比 Vue 2 基于 Object.defineProperty 的系统更强大、更一致。

本章将探讨 Vue 3 中的响应性工作原理,如何使用 ref 和 reactive 声明响应式状态,以及如何处理响应性的一些注意事项。

  • Proxies(代理): 当您使用 ref() 或 reactive() 声明状态时,Vue 会用 JavaScript Proxy 包裹您的数据(基础类型或对象)。
  • Tracking(追踪): 当组件的渲染函数执行时,Vue 会追踪哪些响应式属性被访问(Proxy 的 ‘get’ trap)。
  • Triggering(触发): 当响应式属性被修改(Proxy 的 ‘set’ trap)时,Vue 会检查哪些组件依赖于该属性(在渲染期间追踪),并触发它们重新渲染。

这种基于 Proxy 的系统使 Vue 3 能够追踪使用 reactive 声明的对象的属性添加和删除,克服了 Vue 2 中的限制。

ref() 用于为任何值类型创建响应式变量,包括基础类型(String, Number, Boolean)和对象。它将值包装在一个具有 .value 属性的对象中。

import { ref } from 'vue';
// 对于基础类型
const counter = ref(0);
const message = ref('Hello');
const isActive = ref(true);
console.log(counter.value); // 在 JS 中使用 .value 访问/修改
counter.value++;
// 对于对象
const user = ref({ name: 'Alice', age: 30 });
user.value.age++; // 通过 .value 修改对象属性
// 在模板中,.value 会被自动解包:
// <p>{{ counter }}</p>
// <p>{{ user.name }}</p>

当您需要独立的、基础类型值的响应性,或可能需要重新赋值整个对象/数组时,请使用 ref。

reactive() 用于专门为对象(包括数组和内置类型如 Map, Set)创建响应式代理。它使对象本身深度响应。

import { reactive } from 'vue';
const state = reactive({
count: 0,
user: {
name: 'Bob',
isAdmin: false
},
items: ['apple', 'banana']
});
// 在 JS 中直接访问/修改
state.count++;
state.user.isAdmin = true;
state.items.push('orange'); // 数组变动是响应的
// 在模板中:
// <p>{{ state.count }}</p>
// <p>{{ state.user.name }}</p>

使用 reactive 将相关数据分组到单个对象中。请注意,解构 reactive 对象的属性将导致它们失去响应性,除非您使用像 toRefs 这样的工具函数。

  • 使用 ref 处理基础类型以及您可能需要重新赋值整个变量的情况。
  • 使用 reactive 处理您主要修改对象内部属性的复杂对象。
  • ref 在脚本中需要 .value,而 reactive 不需要。
  • 两者在模板中都能无缝工作(refs 会自动解包)。

Vue 3 基于 Proxy 的系统可以响应式地处理大多数数组和对象的操纵:

  • 诸如 push、pop、shift、unshift、splice、sort、reverse 之类的变动方法会被追踪。
  • 直接通过索引设置元素(例如 myArray.value[0] = newValue)是响应的。
  • 修改 .length 属性是响应的。
  • 修改现有属性是响应的。
  • 在用 reactive() 声明的对象中添加新属性是响应的(例如 state.newProp = 'some value')。这是相对于 Vue 2 的一个重大改进。
  • 使用 delete 操作符删除属性(例如 delete state.oldProp)对于用 reactive() 声明的对象是响应的。

重要提示: 如果您使用 ref() 包装一个对象(例如 const myObj = ref({ a: 1 })),直接添加一个新的根级属性(myObj.value.b = 2)是响应的。删除(delete myObj.value.a)也是响应的。

由于 Proxies 的能力,Vue 2 中对 Vue.set 和 Vue.delete 的需求在 Vue 3 中已被消除。

与 Vue 2 中手动侦听实例外部属性(如 vm.$watch)不同,请使用 Composition API 中的 watch 或 watchEffect 函数。它们提供了更明确、更强大的方式来响应状态变化。

import { ref, watch, watchEffect } from 'vue';
const count = ref(0);
const data = reactive({ name: 'test', value: 10 });
// 侦听特定的 ref
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// 侦听 reactive 属性的 getter 函数
watch(() => data.value, (newValue) => {
console.log(`Data value changed to ${newValue}`);
});
// 侦听多个源
watch([count, () => data.value], ([newCount, newDataValue], [oldCount, oldDataValue]) => {
console.log('Count or data.value changed');
});
// watchEffect 会立即运行一次,并在其依赖项改变时重新运行
watchEffect(() => {
// 自动追踪 count 和 data.name 作为依赖项
console.log(`Current count: ${count.value}, Current name: ${data.name}`);
});
// 示例用法
setTimeout(() => {
count.value = 5;
data.value = 20;
data.name = 'updated';
}, 1000);

watch 提供了对要侦听什么的细粒度控制,并能访问新旧值。watchEffect 对于依赖于多个响应式属性的副作用更简单,它会自动追踪依赖。

  • 解构 reactive 对象: 解构 reactive 对象的属性会导致它们失去响应性,因为解构后的变量持有一个普通值,而不是代理。如果需要,请使用 toRefs 或 toRef 为单个属性创建 refs。
  • import { reactive, toRefs } from ‘vue’;

const state = reactive({ foo: 1, bar: 2 });

// 错误:这里的 foo 和 bar 不是响应式的 // let { foo, bar } = state;

// 正确:fooRef 和 barRef 是 refs,与 state 同步 const { foo: fooRef, bar: barRef } = toRefs(state);

console.log(fooRef.value); // 1 state.foo++; console.log(fooRef.value); // 2

  • 替换 reactive 对象: 您不能直接替换整个用 reactive 声明的对象。如果需要替换整个对象,请改用 ref。
  • import { reactive, ref } from ‘vue’;

let state = reactive({ count: 0 }); // 错误:这将破坏响应性 // state = reactive({ count: 1 });

// 而是修改属性: // state.count = 1;

// 或者:如果需要合并对象,使用 Object.assign // Object.assign(state, { count: 1, newProp: true });

// 或者:如果需要完全替换,使用 ref let stateRef = ref({ count: 0 }); stateRef.value = { count: 1 }; // 正确:这样可行

Vue 3 的响应性系统,由 Proxies 和 ref/reactive API 提供支持,提供了一种强大且直观的方式来构建动态用户界面。理解它的工作原理并选择正确的 API (ref vs reactive) 有助于编写高效且可维护的 Vue 应用。

探索官方文档以了解更多高级主题:Reactivity Fundamentals 和 Reactivity in Depth。