Vue 的响应式系统负责追踪组件渲染所读取的状态,并在这些状态变化时触发相关更新。组件使用模板或渲染函数声明界面与状态的关系,而不是在每次数据变化后手动寻找并修改 DOM。
声明式视图
<script setup>
import { ref } from 'vue'
const count = ref(0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
点击按钮修改的是响应式状态 count。Vue 记录组件渲染时对 count 的读取;值变化后,相应的组件更新会被调度,新的渲染结果最终反映到 DOM。
ref 与 reactive
Vue 3 主要通过 ref() 和 reactive() 创建响应式状态:
ref()使用带有.value的对象封装值,通过访问器追踪读取和触发更新;模板会自动解包符合条件的 ref。reactive()返回对象的 Proxy,通过拦截属性读取和写入完成依赖追踪与更新触发。
响应式代理与原始对象并不相同。需要保持响应式连接时,应使用代理对象,并注意直接解构响应式对象的基本类型属性可能使局部变量失去这层连接。
依赖追踪与触发
Vue 官方通常用 track 和 trigger 描述响应式系统的核心过程:响应式属性被读取时,当前运行的副作用与该属性建立依赖;属性被写入时,依赖它的副作用被通知重新运行。组件渲染本身会作为响应式副作用执行,因此只需更新状态,不需要直接调用 DOM API。
派生状态与副作用
computed() 用于从其他响应式状态声明派生值,并对结果进行缓存;watch() 和 watchEffect() 用于在依赖变化后执行副作用。派生数据应优先使用 computed 表达,网络请求、持久化或与外部系统同步等操作则由 watcher、生命周期钩子或事件处理函数承担。
相关官方资料:响应式基础、深入响应式系统、计算属性、侦听器。
computed、watch 与 watchEffect 的选择
computed 适合纯粹的派生状态,不宜在 getter 中发请求或修改其他状态。watch 显式声明侦听源,适合精确控制触发条件;watchEffect 自动追踪同步执行期间读取的响应式依赖,异步函数中第一次 await 之后才读取的值不会因此被自动追踪。
侦听 reactive 对象属性时,可以传 getter,例如 watch(() => state.query, ...);直接传入当时取得的普通字符串或数字无法持续侦听属性变化。
异步副作用清理
搜索词变化后,前一次请求可能晚于新请求返回。侦听器可以在失效时中止旧请求,并避免旧结果更新视图:
watch(query, async (value, _oldValue, onCleanup) => {
const controller = new AbortController();
let active = true;
onCleanup(() => {
active = false;
controller.abort();
});
try {
const response = await fetch(
"/api/search?q=" + encodeURIComponent(value),
{ signal: controller.signal }
);
if (!response.ok) throw new Error("请求失败");
const data = await response.json();
if (active) results.value = data;
} catch (error) {
if (active) loadError.value = String(error);
}
});
示例假定 query、results、loadError 为已创建的 ref。实际界面还应在新请求开始时设置 loading、清理适当的旧错误,并仅由当前请求结束 loading。
在 setup 中同步创建的侦听器通常会随组件卸载停止;异步创建的侦听器需要注意手动停止。定时器、外部事件订阅和连接也应在相应清理函数或卸载钩子中释放。
参考:Vue 官方侦听器文档。