vue 中 computed 和 watch 的区别
·
在 Vue 中,computed(计算属性)和 watch(监听器)都是用于处理数据变化的工具,但它们的设计目标、使用场景和底层逻辑存在显著差异。以下从核心区别、底层原理、使用场景等方面详细解析。
一、核心区别概览
| 维度 | computed(计算属性) |
watch(监听器) |
|---|---|---|
| 本质 | 基于依赖数据动态计算的 “衍生值” | 监听指定数据变化并执行副作用(如异步操作) |
| 触发时机 | 依赖数据变化时自动更新,且有缓存 | 监听的数据变化时触发,无缓存 |
| 返回值 | 必须有返回值(用于模板或逻辑中使用) | 无强制返回值(通常用于执行操作) |
| 适用场景 | 数据派生(如拼接字符串、过滤列表) | 数据变化后的异步操作(如请求接口)、复杂逻辑 |
| 默认深度监听 | 自动支持(依赖数据为对象时) | 需手动开启(deep: true) |
二、底层原理对比
1. computed 的实现逻辑
computed 本质是一个 带缓存的响应式计算,其核心是通过 “惰性求值” 和 “依赖追踪” 实现高效更新:
- 依赖收集:初始化时,
computed会创建一个特殊的Effect(副作用函数),并标记为 “懒执行”。当计算属性被访问时,会触发依赖数据的getter,将自身的Effect与依赖数据关联。 - 缓存机制:计算属性的结果会被缓存,只有当依赖的响应式数据发生变化时,才会重新计算;若依赖未变,多次访问会直接返回缓存值,避免重复计算。
- 惰性执行:计算属性在初始化时不会立即执行,第一次被访问时才会计算结果;若依赖变化后未被访问,也不会重新计算(节省性能)。
示例:
javascript
运行
const count = ref(1);
const doubleCount = computed(() => {
console.log('计算执行'); // 仅在 count 变化且 doubleCount 被访问时触发
return count.value * 2;
});
- 当
count变化后,若doubleCount未被使用(如模板中未引用),则不会重新计算。 - 若
doubleCount被多次访问,且count未变,则仅第一次访问时执行计算,后续直接返回缓存值。
2. watch 的实现逻辑
watch 是 主动监听数据变化并执行回调 的机制,核心是 “监听指定源” 并在变化时触发副作用:
- 监听源:可以是响应式数据(
ref、reactive对象的属性)、getter 函数或数组(监听多个源)。 - 回调执行:当监听的源发生变化时,会立即执行回调函数(默认是异步执行,等待所有 DOM 更新完成后触发)。
- 深度监听:对于对象类型的源,默认仅监听引用变化,若需监听深层属性变化,需手动设置
deep: true。 - 即时执行:默认首次加载时不执行回调,若需初始化时立即执行,可设置
immediate: true。
示例:
javascript
运行
const user = reactive({ name: 'vue', age: 3 });
watch(
() => user.age, // 监听源(getter 函数)
(newVal, oldVal) => { // 回调函数
console.log(`年龄从 ${oldVal} 变为 ${newVal}`);
// 可执行异步操作(如请求接口)
},
{ deep: false, immediate: false } // 配置项
);
- 当
user.age变化时,回调会触发;若仅修改user.name,则不会触发(因监听的是age)。
三、关键特性对比
1. 数据依赖与更新时机
-
computed:依赖于内部使用的响应式数据,当且仅当依赖变化时才可能更新(且需被访问)。例如:javascript
运行
const a = ref(1); const b = ref(2); const sum = computed(() => a.value + b.value);只有
a或b变化,且sum被访问时,sum才会重新计算。 -
watch:明确指定监听的数据源,当数据源变化时立即触发回调(无论是否被使用)。例如:javascript
运行
watch(a, (newVal) => { console.log('a 变了'); });只要
a变化,无论是否有地方使用a,回调都会执行。
2. 缓存机制
computed:有缓存,这是其核心优势。例如一个复杂的计算(如过滤长列表),只要依赖不变,多次访问只会计算一次,大幅提升性能。watch:无缓存,每次监听的数据源变化都会执行回调,即使前后值相同(可通过deep和比较函数优化)。
3. 适用场景
-
computed适合:- 数据派生:从现有数据计算出新值(如
fullName = firstName + lastName)。 - 数据过滤 / 转换:如从列表中筛选符合条件的项(
filteredList = list.filter(...))。 - 简化模板:将复杂表达式(如
{{ a + b * c / d }})抽为计算属性,提高可读性。
- 数据派生:从现有数据计算出新值(如
-
watch适合:- 异步操作:数据变化后需要请求接口、延迟执行等(如搜索框输入后延迟发请求)。
- 复杂副作用:数据变化后需要执行多步操作(如更新 DOM、操作第三方库)。
- 监听深层数据:需对对象的深层属性变化做出反应(如
user.address.city变化)。
4. 深度监听与默认行为
-
computed:自动支持深层依赖监听。例如:javascript
运行
const user = reactive({ address: { city: 'beijing' } }); const city = computed(() => user.address.city);当
user.address.city变化时,city会自动更新(无需手动配置)。 -
watch:默认不监听深层属性,需手动开启deep: true。例如:javascript
运行
// 监听 user.address.city,需开启 deep watch( () => user.address, (newVal) => { console.log('city 变了'); }, { deep: true } // 必须设置,否则仅监听 address 引用变化 );
四、使用示例对比
1. computed 示例:数据派生
vue
<template>
<div>
firstName: <input v-model="firstName" />
lastName: <input v-model="lastName" />
<p>全名:{{ fullName }}</p> <!-- 自动更新 -->
</div>
</template>
<script setup>
import { ref, computed } from 'vue';
const firstName = ref('');
const lastName = ref('');
// 从 firstName 和 lastName 派生 fullName
const fullName = computed(() => `${firstName.value} ${lastName.value}`);
</script>
2. watch 示例:异步操作
vue
<template>
<div>
搜索:<input v-model="keyword" />
<p>结果:{{ result }}</p>
</div>
</template>
<script setup>
import { ref, watch } from 'vue';
const keyword = ref('');
const result = ref('');
// 监听 keyword 变化,延迟发请求
watch(keyword, (newVal) => {
const timer = setTimeout(async () => {
// 模拟接口请求
result.value = await fetch(`/search?keyword=${newVal}`).then(res => res.text());
}, 500); // 防抖:500ms 内连续输入不重复请求
return () => clearTimeout(timer); // 清理副作用
});
</script>
总结
computed 和 watch 的核心区别在于 “计算” vs “监听”:
computed是 “被动的”,专注于从现有数据派生新值,依赖缓存提升性能,适合同步、简单的计算逻辑。watch是 “主动的”,专注于监听数据变化并执行副作用(尤其是异步操作),适合复杂逻辑或需要主动触发的场景。
实际开发中,应根据具体需求选择:数据派生优先用 computed,异步操作或复杂副作用优先用 watch。
更多推荐



所有评论(0)