Glittering's blog Glittering's blog
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)

Glitz Ma

前端开发工程师
Home
  • 学习手册

    • 《JavaScript教程》
    • 《ES6 教程》
    • 《TypeScript 从零实现 axios》
    • 《Git》
    • 《Vite》
    • 《Vue3》
    • TypeScript
    • CSS
  • 技术文档
  • 算法
  • 工作总结
  • 实用技巧
  • collect
About
  • Classification
  • Label
GitHub (opens new window)
  • vue3学习路径
  • Vue3 Virtual DOM & 渲染机制
  • Vue3 响应式核心
    • 🍀 1. Vue3 响应式的核心思想(Proxy-based)
    • 🔥 2. 响应式的重要组成部分(最核心的三块)
      • ① effect()
      • ② track()
      • ③ trigger()
    • 🧠 3. Vue3 响应式数据结构(非常重要)
    • 🎯 4. reactive() 的实现机制
      • get → track
      • set → trigger
    • 🚀 5. effect 核心逻辑(依赖收集的大脑)
    • ⚡ 6. track 实现(依赖收集)
    • 💥 7. trigger 实现(派发更新)
    • 🧨 8. 依赖追踪如何避免死循环?
    • 📌 9. ref() 的本质
    • 📦 10. computed 的内部机制(很巧妙)
    • 🏆 11. 响应式完整流程(超清晰)
      • 第一次渲染:
      • 数据变化:
    • 🔥 12. 你可以直接运行的 mini-reactive 实现(核心 30 行)
    • 🎓 13. 到这你已经掌握:
  • Vue3数组、Map、Set 特殊处理
  • Vue3与vue2响应式处理的不同
  • 写一个Mini-vue
  • Mini-vue template增强版
  • 《Vue3》学习笔记
mamingjuan
2025-02-20
目录

Vue3 响应式核心

这部分是整个 Vue3 的灵魂,比 VDOM 更重要。 我会带你从概念 → 机制 → 数据结构 → 源码流程 → 实现 mini-reactive,达到可直接读 Vue 源码的程度。


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

Vue2 用 Object.defineProperty,有硬伤:

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

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

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

核心结构:

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

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

Vue3 reactivity 包包含三大核心:

# ① effect()

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

本质是:

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

# ② track()

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

当你访问:

state.count
1

Vue3 会调用:

track(target, key)
1

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

# ③ trigger()

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

当执行:

state.count++
1

Vue 会调用:

trigger(target, key)
1

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


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

依赖收集关系如下:

targetMap = WeakMap()
       ↓
target (state 对象)
       ↓
depsMap = Map()
       ↓
key → Dep(Set of effect)
1
2
3
4
5
6
7

可以画成这样:

WeakMap
└── target1 (reactive 对象)
      └── Map
           ├── key1 → Set(effectA, effectB)
           ├── key2 → Set(effectC)
└── target2
      └── ...
1
2
3
4
5
6
7

解释:

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

# 🎯 4. reactive() 的实现机制

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

function reactive(target) {
  return new Proxy(target, mutableHandlers)
}
1
2
3

其中最关键的是 get/set:

# get → track

get(target, key, receiver) {
  const res = Reflect.get(target, key, receiver)
  track(target, key)
  return res
}
1
2
3
4
5

# set → trigger

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
}
1
2
3
4
5
6
7
8
9
10

Proxy 的 handler 中只有两件事:

get → track set → trigger


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

effect(fn) 会:

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

源码伪代码:

let activeEffect = null

export function effect(fn) {
  const effectFn = () => {
    activeEffect = effectFn
    fn()
    activeEffect = null
  }
  effectFn()
  return effectFn
}
1
2
3
4
5
6
7
8
9
10
11

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

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)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

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


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

当数据变动时:

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

  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effectFn => effectFn())
  }
}
1
2
3
4
5
6
7
8
9

本质是:

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


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

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

effect(fn, {
  scheduler(job) {
    queueJob(job)
  }
})
1
2
3
4
5

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


# 📌 9. ref() 的本质

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

export function ref(value) {
  const wrapper = {
    value
  }
  return reactive(wrapper)
}
1
2
3
4
5
6

也就是说:

const count = ref(0)
1

等价于:

reactive({ value: 0 })
1

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


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

computed 使用 lazy effect + 缓存:

伪代码:

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
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

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 更新
1
2

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

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
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

你可以用它:

const state = reactive({ count: 0 })

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

state.count++
1
2
3
4
5
6
7

# 🎓 13. 到这你已经掌握:

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

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


上次更新: 2025/12/10, 08:07:53
Vue3 Virtual DOM & 渲染机制
Vue3数组、Map、Set 特殊处理

← Vue3 Virtual DOM & 渲染机制 Vue3数组、Map、Set 特殊处理→

Copyright © 2015-2025 Glitz Ma
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式