Skip to content

这部分是整个 Vue3 的灵魂,比 VDOM 更重要。

🍀 1. Vue3 响应式的核心思想(Proxy-based)

Vue2 用 Object.defineProperty,有硬伤:

  • 无法监听数组下标
  • 无法监听对象新增属性
  • 深层对象依赖难以追踪
  • 性能受大量 getter/setter 影响

Vue3 改为 Proxy,解决所有上述痛点,并支持:

  • ✔ 深度监听
  • ✔ 数组 / Map / Set 原生监听
  • ✔ 惰性访问
  • ✔ WeakMap 缓存,提升性能

核心结构:

state → Proxy(wrapper) → get → track → effect → trigger → update

🔥 2. 响应式的重要组成部分(最核心的三块)

Vue3 reactivity 包包含三大核心:

effect()

作用:注册副作用函数(例如组件 render)

本质是:

把当前执行的函数登记为“依赖”,当数据变动时重新执行它。

track()

作用:依赖收集 触发场景:getter

当你访问:

js
state.count

Vue3 会调用:

ts
track(target, key)

将当前活跃的 effect 绑定到这个 key 上。

trigger()

作用:派发更新 触发场景:setter

当执行:

js
state.count++

Vue 会调用:

ts
trigger(target, key)

触发所有依赖这个 key 的 effect 重新执行(更新视图)。

🧠 3. Vue3 响应式数据结构(非常重要)

依赖收集关系如下:

targetMap = WeakMap()

target (state 对象)

depsMap = Map()

key → Dep(Set of effect)

可以画成这样:

WeakMap
└── target1 (reactive 对象)
      └── Map
           ├── key1 → Set(effectA, effectB)
           ├── key2 → Set(effectC)
└── target2
      └── ...

解释:

  • 每个 reactive 对象是一个 target
  • 每个 key 对应一个依赖集合 Dep
  • Dep 是一个 Set,里面装多个 effect

🎯 4. reactive() 的实现机制

Vue3 的 reactive() 内部创建一个 Proxy:

ts
function reactive(target) {
  return new Proxy(target, mutableHandlers)
}

其中最关键的是 get/set:

get → track

ts
get(target, key, receiver) {
  const res = Reflect.get(target, key, receiver)
  track(target, key)
  return res
}

set → trigger

ts
set(target, key, value, receiver) {
  const oldValue = target[key]
  const result = Reflect.set(target, key, value, receiver)

  if (oldValue !== value) {
    trigger(target, key)
  }

  return result
}

Proxy 的 handler 中只有两件事:

get → track set → trigger

🚀 5. effect 核心逻辑(依赖收集的大脑)

effect(fn) 会:

  1. 把 fn 存到全局变量 activeEffect
  2. 执行 fn()
  3. track 时把 activeEffect 收集进去

源码伪代码:

ts
let activeEffect = null

export function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn
    fn()
    activeEffect = null
  }
  effectFn()
  return effectFn
}

⚡ 6. track 实现(依赖收集)

ts
function track(target, key) {
  if (!activeEffect) return

  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }

  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }

  dep.add(activeEffect)
}

总结: 访问数据 → 把当前 effect 加到依赖表里

💥 7. trigger 实现(派发更新)

当数据变动时:

ts
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effectFn => effectFn())
  }
}

本质是:

key 绑定的所有 effect 都重新执行(重新渲染)

🧨 8. 依赖追踪如何避免死循环?

Vue 3 使用 “调度器 scheduler” 来避免 effect 递归触发:

ts
effect(fn, {
  scheduler(job) {
    queueJob(job)
  }
})

一般组件渲染的 effect 都会经过 scheduler,从而批量更新、避免重复渲染。

📌 9. ref() 的本质

ref() 也是 reactive 的一层封装:

ts
export function ref(value) {
  const wrapper = {
    value
  }
  return reactive(wrapper)
}

也就是说:

js
const count = ref(0)

等价于:

js
reactive({ value: 0 })

访问 count.value → get → track 修改 count.value → set → trigger

📦 10. computed 的内部机制(很巧妙)

computed 使用 lazy effect + 缓存

伪代码:

ts
function computed(getter) {
  let dirty = true
  let value

  const effectFn = effect(getter, {
    lazy: true,
    scheduler() {
      dirty = true
    }
  })

  return {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
}

computed 特性:

  • getter 内部依赖变化 → dirty = true
  • 下次访问 value 时才重新计算
  • 没访问就不计算(惰性)

非常高效。

🏆 11. 响应式完整流程(超清晰)

第一次渲染:

  1. effect(render)
  2. render 中访问 state.xxx → track
  3. 建立依赖关系

数据变化:

  1. state.xxx = newValue → trigger
  2. 找到所有 effect
  3. scheduler → queueJob
  4. 重新渲染(effect 执行)
  5. patch → 更新 DOM

流程图:

get → track → 收集依赖
set → trigger → effect → patch → DOM 更新

🔥 12. 你可以直接运行的 mini-reactive 实现(核心 30 行)

js
let activeEffect = null
const targetMap = new WeakMap()

function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn
    fn()
    activeEffect = null
  }
  effectFn()
}

function track(target, key) {
  if (!activeEffect) return
  let depsMap = targetMap.get(target)
  if (!depsMap) targetMap.set(target, (depsMap = new Map()))
  let dep = depsMap.get(key)
  if (!dep) depsMap.set(key, (dep = new Set()))
  dep.add(activeEffect)
}

function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  const dep = depsMap.get(key)
  if (dep) dep.forEach(fn => fn())
}

function reactive(target) {
  return new Proxy(target, {
    get(t, k, r) {
      const res = Reflect.get(t, k, r)
      track(t, k)
      return res
    },
    set(t, k, v, r) {
      const old = t[k]
      const result = Reflect.set(t, k, v, r)
      if (old !== v) trigger(t, k)
      return result
    }
  })
}

你可以用它:

js
const state = reactive({ count: 0 })

effect(() => {
  console.log("count changed:", state.count)
})

state.count++

🎓 13. 到这你已经掌握:

  • ✔ reactive 原理
  • ✔ effect / track / trigger
  • ✔ WeakMap → Map → Set 数据结构
  • ✔ ref/computed 内部机制
  • ✔ scheduler 调度系统
  • ✔ Proxy 拦截逻辑

已经达到可以看 Vue3 源码的级别。