文章目录
- 前言
- 🔍 一、`computed` vs `watch`
- ✅ 示例对比
- 1. `computed` 示例(适合模板绑定、衍生数据)
- 2. `watch` 示例(副作用,如调用接口)
- 🧠 二、源码实现原理(简化理解)
- 1. `computed` 原理
- 2. `watch` 原理
- 📌 三、使用建议
- 扩展:
- 🧠 四、Vue 3 响应式系统核心:`effect` / `track` / `trigger`
- 1. `effect(fn)`:响应式副作用收集器
- 2. `track(target, key)`:依赖追踪
- 3. `trigger(target, key)`:依赖触发
- 🔁 总结:响应式机制流程
- 🔁 五、`watchEffect` 是什么?
- 🌟 特点:
- 📦 内部工作机制(简化版)
- 🧪 应用场景对比
- ✅ 实战案例:watch vs watchEffect
- `watch` 示例(明确监听)
- `watchEffect` 示例(更简洁)
前言
Vue 3 中 computed
和 watch
的区别与源码实现逻辑(Composition API 版本)。
🔍 一、computed
vs watch
项目 | computed | watch |
---|---|---|
类型 | 派生状态(缓存) | 响应式副作用 |
用途 | 根据已有响应式变量派生出新数据 | 监听某个响应式数据的变化后执行副作用逻辑 |
是否缓存 | ✅ 是 | ❌ 否 |
返回值 | Ref (值类型) | void (返回值无意义) |
使用场景 | 显示用、模板绑定 | API 调用、定时器、调试、数据同步等 |
✅ 示例对比
1. computed
示例(适合模板绑定、衍生数据)
const price = ref(100)
const tax = ref(0.1)
const total = computed(() => price.value * (1 + tax.value))
// total.value = 110
2. watch
示例(副作用,如调用接口)
watch(price, (newVal, oldVal) => {
console.log(`价格变化:${oldVal} -> ${newVal}`)
// 比如向服务器汇报价格变动
})
🧠 二、源码实现原理(简化理解)
1. computed
原理
- Vue 内部使用了
effect
与Ref
实现computed
。 - 默认是 lazy effect,只有在访问
.value
时才执行计算。 - Vue 自动追踪依赖(依赖收集),只在依赖变化时重新执行。
function computed(getter) {
const result = ref()
const runner = effect(getter, {
lazy: true,
scheduler: () => {
dirty = true
}
})
return {
get value() {
if (dirty) {
result.value = runner()
dirty = false
}
return result.value
}
}
}
关键点:缓存 + 懒执行 + 自动依赖追踪
2. watch
原理
watch
是对effect
的进一步封装。- 内部会递归地跟踪传入的响应式数据,建立响应关系。
- 依赖变动时,会执行用户提供的回调函数。
function watch(source, cb) {
let oldValue = source()
effect(() => {
const newValue = source()
if (newValue !== oldValue) {
cb(newValue, oldValue)
oldValue = newValue
}
})
}
关键点:不缓存 + 只执行副作用 + 深层依赖可配置
📌 三、使用建议
需求 | 推荐 |
---|---|
只用来展示(派生数据) | computed |
需要执行副作用(发请求、console.log、同步) | watch |
复杂对象监听 | watch 配合 deep: true |
多个数据合并监听 | watch([a, b]) |
扩展:
- 对
watchEffect
的解释 - 或深入理解 Vue 的响应式核心
effect
、track
、trigger
机制
继续深入讲解 Vue 3 响应式系统的核心:effect
、track
、trigger
,并说明 watchEffect
是如何工作的。
🧠 四、Vue 3 响应式系统核心:effect
/ track
/ trigger
1. effect(fn)
:响应式副作用收集器
- 用于包裹副作用函数(如
computed
的 getter 或watch
的监听函数)。 - 在函数中读取响应式数据时,会被自动“追踪依赖”。
effect(() => {
console.log(state.count) // 依赖 `state.count`
})
2. track(target, key)
:依赖追踪
- 当你读取响应式对象的属性时,会触发
track
。 track
会记录当前活跃的effect
,让这个effect
依赖这个属性。
// 简化版
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let deps = depsMap.get(key)
if (!deps) {
deps = new Set()
depsMap.set(key, deps)
}
deps.add(activeEffect)
}
3. trigger(target, key)
:依赖触发
- 当你修改响应式数据时,会触发
trigger
。 - 它会找到依赖这个属性的所有
effect
,然后重新执行它们。
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const effects = depsMap.get(key)
effects.forEach(effect => effect())
}
🔁 总结:响应式机制流程
effect(fn)
执行时读取响应式数据 →track
收集依赖- 数据变化时 →
trigger
调用依赖的effect
→ 重新执行
🔁 五、watchEffect
是什么?
🌟 特点:
- 类似
computed
,但用于副作用。 - 自动收集依赖,无需显式声明。
- 依赖变更时自动重新执行副作用函数。
watchEffect(() => {
console.log(`count 是 ${count.value}`)
})
📦 内部工作机制(简化版)
function watchEffect(effectFn) {
const runner = effect(effectFn)
return () => stop(runner)
}
watchEffect
调用时立即执行effectFn
。- 响应式依赖被收集。
- 当依赖变更时,会重新调用
effectFn
。 - 返回的函数可用于停止监听。
🧪 应用场景对比
需求场景 | 推荐工具 |
---|---|
计算派生值 | computed |
副作用 + 明确监听字段 | watch |
副作用 + 自动依赖收集 | watchEffect |
✅ 实战案例:watch vs watchEffect
watch
示例(明确监听)
watch(() => user.id, (newId) => {
fetchUserDetail(newId)
})
watchEffect
示例(更简洁)
watchEffect(() => {
fetchUserDetail(user.id)
})