在 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 是 主动监听数据变化并执行回调 的机制,核心是 “监听指定源” 并在变化时触发副作用:

  • 监听源:可以是响应式数据(refreactive 对象的属性)、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

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐