Computed 与 Watch 对比

Vue 中 computed 和 watch 的区别、原理及使用场景

问题

Vue 中 computedwatch 有什么区别?各自的原理是什么?分别适用于哪些场景?

解答

基本区别

特性computedwatch
返回值必须有返回值不需要返回值
缓存有缓存,依赖不变不重新计算无缓存,每次变化都执行
异步不支持异步支持异步操作
用途派生数据监听变化执行副作用

computed 示例

import { ref, computed } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

// computed 会缓存结果,只有依赖变化时才重新计算
const fullName = computed(() => {
  console.log('computed 执行') // 依赖不变时不会打印
  return firstName.value + lastName.value
})

// 多次访问 fullName.value,只会计算一次
console.log(fullName.value) // 张三
console.log(fullName.value) // 张三(使用缓存,不重新计算)

watch 示例

import { ref, watch } from 'vue'

const searchText = ref('')

// watch 用于执行副作用,如异步请求
watch(searchText, async (newVal, oldVal) => {
  if (newVal) {
    // 支持异步操作
    const result = await fetchSearchResult(newVal)
    console.log('搜索结果:', result)
  }
}, {
  immediate: false, // 是否立即执行
  deep: false       // 是否深度监听
})

实现原理

computed 原理

// 简化版 computed 实现
function computed(getter) {
  let value
  let dirty = true // 标记是否需要重新计算
  
  // 创建响应式 effect
  const effect = new ReactiveEffect(getter, () => {
    // 依赖变化时,标记为脏数据,但不立即计算
    dirty = true
  })
  
  return {
    get value() {
      // 只有脏数据才重新计算(缓存机制)
      if (dirty) {
        value = effect.run()
        dirty = false
      }
      return value
    }
  }
}

watch 原理

// 简化版 watch 实现
function watch(source, callback, options = {}) {
  let oldValue
  
  // 获取监听的值
  const getter = () => source.value
  
  // 调度器:值变化时执行回调
  const scheduler = () => {
    const newValue = effect.run()
    callback(newValue, oldValue)
    oldValue = newValue
  }
  
  const effect = new ReactiveEffect(getter, scheduler)
  
  // immediate: 立即执行一次
  if (options.immediate) {
    scheduler()
  } else {
    oldValue = effect.run()
  }
}

使用场景对比

import { ref, computed, watch } from 'vue'

// ✅ computed:根据已有数据计算新数据
const price = ref(100)
const quantity = ref(2)
const total = computed(() => price.value * quantity.value)

// ✅ computed:过滤/排序列表
const list = ref([3, 1, 2])
const sortedList = computed(() => [...list.value].sort())

// ✅ watch:执行异步操作
const userId = ref(1)
watch(userId, async (id) => {
  const user = await fetchUser(id)
  // 更新用户信息...
})

// ✅ watch:执行 DOM 操作或其他副作用
const count = ref(0)
watch(count, (val) => {
  document.title = `计数: ${val}`
})

// ❌ 错误用法:用 watch 做 computed 的事
const a = ref(1)
const b = ref(0)
// 不推荐
watch(a, (val) => {
  b.value = val * 2
})
// 推荐
const b = computed(() => a.value * 2)

watchEffect

import { ref, watchEffect } from 'vue'

const count = ref(0)

// watchEffect:自动收集依赖,立即执行
watchEffect(() => {
  console.log('count:', count.value)
  // 自动追踪 count,变化时重新执行
})

// 对比 watch:需要显式指定监听源
watch(count, (val) => {
  console.log('count:', val)
})

关键点

  • computed 有缓存:依赖不变时返回缓存值,适合计算派生数据
  • watch 无缓存:每次变化都执行回调,适合执行副作用(异步请求、DOM 操作)
  • computed 必须同步:getter 函数不能是异步的
  • watch 支持配置immediate 立即执行,deep 深度监听
  • 优先用 computed:能用 computed 解决的不要用 watch,代码更简洁、性能更好