Vue3源码解读(一):响应式系统 reactive/ref 核心原理图解(2026最新版)


本文是 Vue3源码系列教程,从基础到进阶,附完整代码示例,收藏备用!


一、前言

Vue3 的响应式系统是其最核心的特性之一,相比 Vue2 的 Object.defineProperty,Vue3 使用 Proxy 实现了更强大、更灵活的响应式能力。本文将深入源码,带你彻底理解 reactive 和 ref 的实现原理。

学完本文,你将掌握:

  • Proxy 与 Reflect 的基本用法
  • reactive 的创建过程与依赖收集
  • ref 的包装原理与 .value 访问
  • effect 副作用函数的执行机制
  • 手写一个极简版响应式系统

二、Vue3 响应式系统概览

2.1 核心 API

javascript 复制代码
import { reactive, ref, computed, effect } from 'vue'

// reactive: 创建对象的响应式代理
const state = reactive({ count: 0 })

// ref: 创建值的响应式引用
const count = ref(0)

// computed: 创建计算属性
const double = computed(() => count.value * 2)

// effect: 注册副作用函数
effect(() => {
  console.log('count changed:', count.value)
})

2.2 响应式系统架构

复制代码
┌─────────────────────────────────────────┐
│           用户代码 (Component)           │
├─────────────────────────────────────────┤
│  reactive() │  ref() │  computed()      │
├─────────────────────────────────────────┤
│         Proxy 代理对象 (Handlers)        │
├─────────────────────────────────────────┤
│      依赖收集 (track) / 触发更新 (trigger) │
├─────────────────────────────────────────┤
│         Effect 副作用函数队列            │
└─────────────────────────────────────────┘

三、Proxy 与 Reflect 基础

3.1 为什么用 Proxy 替代 Object.defineProperty

特性 Object.defineProperty Proxy
拦截能力 仅 get/set 13种操作
新增属性 无法监听 自动监听
数组索引 需特殊处理 原生支持
性能 递归遍历 懒代理
兼容性 IE9+ IE不支持

3.2 Proxy 基本用法

javascript 复制代码
const target = { name: 'Vue', version: 3 }

const proxy = new Proxy(target, {
  // 拦截读取操作
  get(target, key, receiver) {
    console.log('get:', key)
    return Reflect.get(target, key, receiver)
  },
  
  // 拦截设置操作
  set(target, key, value, receiver) {
    console.log('set:', key, '=', value)
    return Reflect.set(target, key, value, receiver)
  },
  
  // 拦截 in 操作符
  has(target, key) {
    console.log('has:', key)
    return Reflect.has(target, key)
  },
  
  // 拦截 delete 操作
  deleteProperty(target, key) {
    console.log('delete:', key)
    return Reflect.deleteProperty(target, key)
  }
})

proxy.name // get: name → 'Vue'
proxy.version = 4 // set: version = 4 → true
'name' in proxy // has: name → true
delete proxy.version // delete: version → true

3.3 Reflect 的作用

Reflect 提供了一套与 Proxy 处理器对应的方法,确保 this 指向正确:

javascript 复制代码
const obj = {
  _value: 0,
  get value() {
    return this._value
  }
}

const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    // receiver 是 proxy 本身
    // 使用 Reflect.get 确保 getter 中的 this 指向 proxy
    return Reflect.get(target, key, receiver)
  }
})

四、reactive 源码解析

4.1 创建 reactive 对象

javascript 复制代码
// 简化版实现
const reactiveMap = new WeakMap() // 缓存已创建的代理

function reactive(target) {
  // 1. 检查是否是对象
  if (!isObject(target)) {
    console.warn(`value cannot be made reactive: ${String(target)}`)
    return target
  }
  
  // 2. 检查是否已有代理
  if (reactiveMap.has(target)) {
    return reactiveMap.get(target)
  }
  
  // 3. 创建 Proxy 代理
  const proxy = new Proxy(target, mutableHandlers)
  
  // 4. 缓存代理对象
  reactiveMap.set(target, proxy)
  
  return proxy
}

function isObject(val) {
  return val !== null && typeof val === 'object'
}

4.2 处理器对象 mutableHandlers

javascript 复制代码
const mutableHandlers = {
  get(target, key, receiver) {
    // 收集依赖
    track(target, key)
    
    const result = Reflect.get(target, key, receiver)
    
    // 递归代理嵌套对象(懒代理)
    if (isObject(result)) {
      return reactive(result)
    }
    
    return result
  },
  
  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
  }
}

4.3 依赖收集 track 与触发 trigger

javascript 复制代码
// 存储依赖关系的数据结构
const targetMap = new WeakMap()
let activeEffect = null // 当前正在执行的 effect

// 收集依赖
function track(target, key) {
  if (!activeEffect) return
  
  // 获取 target 对应的 depsMap
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    depsMap = new Map()
    targetMap.set(target, depsMap)
  }
  
  // 获取 key 对应的 dep (Set)
  let dep = depsMap.get(key)
  if (!dep) {
    dep = new Set()
    depsMap.set(key, dep)
  }
  
  // 收集当前 effect
  dep.add(activeEffect)
  
  // 建立双向引用,用于清理
  activeEffect.deps.push(dep)
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    // 执行所有相关的 effect
    dep.forEach(effect => {
      if (effect.scheduler) {
        effect.scheduler() // 异步调度
      } else {
        effect.run() // 同步执行
      }
    })
  }
}

五、ref 源码解析

5.1 ref 的实现原理

ref 用于将基本类型包装成响应式对象:

javascript 复制代码
function ref(value) {
  return createRef(value, false)
}

function createRef(rawValue, shallow) {
  if (isRef(rawValue)) {
    return rawValue
  }
  
  return new RefImpl(rawValue, shallow)
}

class RefImpl {
  constructor(value, __v_isShallow) {
    this.__v_isShallow = __v_isShallow
    this._value = __v_isShallow ? value : toReactive(value)
    this._rawValue = value
  }
  
  get value() {
    // 收集依赖
    trackRefValue(this)
    return this._value
  }
  
  set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      // 触发更新
      triggerRefValue(this)
    }
  }
}

function toReactive(value) {
  return isObject(value) ? reactive(value) : value
}

function isRef(r) {
  return !!(r && r.__v_isRef === true)
}

function hasChanged(value, oldValue) {
  return !Object.is(value, oldValue)
}

5.2 ref 的依赖收集

javascript 复制代码
const refMap = new WeakMap()

function trackRefValue(ref) {
  if (activeEffect) {
    trackEffects(refMap.get(ref) || (refMap.set(ref, (new Set())), refMap.get(ref)))
  }
}

function triggerRefValue(ref) {
  const dep = refMap.get(ref)
  if (dep) {
    triggerEffects(dep)
  }
}

5.3 ref 与 reactive 的区别

javascript 复制代码
// reactive: 代理整个对象
const state = reactive({ count: 0 })
state.count++ // 直接修改属性

// ref: 包装单个值
const count = ref(0)
count.value++ // 通过 .value 访问

// ref 可以包装对象(自动用 reactive 转换)
const user = ref({ name: 'Tom' })
user.value.name = 'Jerry' // 响应式更新

六、effect 副作用函数

6.1 effect 的基本实现

javascript 复制代码
function effect(fn, options = {}) {
  const _effect = new ReactiveEffect(fn, options.scheduler)
  
  if (!options.lazy) {
    _effect.run() // 立即执行
  }
  
  const runner = _effect.run.bind(_effect)
  runner.effect = _effect
  
  return runner
}

class ReactiveEffect {
  constructor(fn, scheduler) {
    this.fn = fn
    this.scheduler = scheduler
    this.deps = [] // 收集的依赖集合
    this.active = true
  }
  
  run() {
    if (!this.active) {
      return this.fn()
    }
    
    // 设置当前激活的 effect
    activeEffect = this
    
    try {
      return this.fn()
    } finally {
      activeEffect = null
    }
  }
  
  stop() {
    if (this.active) {
      cleanupEffect(this)
      this.active = false
    }
  }
}

// 清理 effect 的所有依赖
function cleanupEffect(effect) {
  effect.deps.forEach(dep => {
    dep.delete(effect)
  })
  effect.deps.length = 0
}

6.2 effect 的使用示例

javascript 复制代码
const count = ref(0)

// 创建 effect
const stop = effect(() => {
  console.log('count is:', count.value)
})

// 输出: count is: 0

count.value++ // 输出: count is: 1
count.value++ // 输出: count is: 2

// 停止 effect
stop()
count.value++ // 不再输出

6.3 调度器 scheduler

javascript 复制代码
const count = ref(0)

effect(() => {
  console.log('count:', count.value)
}, {
  // 自定义调度,使用微任务队列
  scheduler: (fn) => {
    Promise.resolve().then(fn)
  }
})

count.value = 1
count.value = 2
count.value = 3

// 输出: count: 0
// 只输出一次: count: 3(合并了多次更新)

七、手写极简版响应式系统

javascript 复制代码
// ==================== 完整实现 ====================

let activeEffect = null
const targetMap = new WeakMap()

// 收集依赖
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)
}

// 触发更新
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return
  
  const dep = depsMap.get(key)
  if (dep) {
    dep.forEach(effect => effect())
  }
}

// 创建响应式对象
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      track(target, key)
      return Reflect.get(target, key, receiver)
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      trigger(target, key)
      return result
    }
  })
}

// 创建 ref
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

// 注册 effect
function effect(fn) {
  activeEffect = fn
  fn()
  activeEffect = null
}

// ==================== 使用示例 ====================

const state = reactive({ count: 0 })

effect(() => {
  console.log('state.count =', state.count)
})

state.count++ // 输出: state.count = 1
state.count++ // 输出: state.count = 2

const num = ref(10)

effect(() => {
  console.log('num.value =', num.value)
})

num.value = 20 // 输出: num.value = 20

八、computed 计算属性

8.1 computed 的实现

javascript 复制代码
function computed(getter) {
  let value
  let dirty = true // 标记是否需要重新计算
  
  const effectFn = effect(getter, {
    lazy: true,
    scheduler: () => {
      dirty = true // 依赖变化时标记为脏
    }
  })
  
  return {
    get value() {
      if (dirty) {
        value = effectFn()
        dirty = false
      }
      return value
    }
  }
}

// 使用
const count = ref(0)
const double = computed(() => count.value * 2)

console.log(double.value) // 0
count.value = 5
console.log(double.value) // 10

九、常见问题与解决方案

❌ 问题1:解构 reactive 对象失去响应式

javascript 复制代码
const state = reactive({ count: 0, name: 'Vue' })

// ❌ 错误:解构后失去响应式
const { count } = state
count++ // 不会触发更新

// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)
count.value++ // 响应式更新

❌ 问题2:数组索引和 length 修改不触发更新

javascript 复制代码
const arr = reactive([1, 2, 3])

// ✅ Vue3 支持以下操作
arr[0] = 10 // 触发更新
arr.length = 0 // 触发更新
arr.push(4) // 触发更新

❌ 问题3:reactive 不能直接替换整个对象

javascript 复制代码
let state = reactive({ count: 0 })

// ❌ 错误:替换后失去响应式连接
state = { count: 1 }

// ✅ 正确:修改原对象的属性
state.count = 1

// 或改用 ref
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 } // 正常响应

❌ 问题4:异步获取数据后赋值不更新

javascript 复制代码
const state = reactive({ list: [] })

// ❌ 可能不触发更新(如果 list 一开始不存在)
async function fetchData() {
  state.list = await fetch('/api/list')
}

// ✅ 确保属性已定义
const state = reactive({ list: [] }) // 初始化时定义

// 或使用 ref
const list = ref([])
async function fetchData() {
  list.value = await fetch('/api/list')
}

十、总结

Vue3 响应式系统的核心设计:

  1. Proxy 代理:拦截对象操作,实现深度响应式
  2. 懒代理:只在访问属性时才递归创建代理
  3. 依赖收集:通过 track 在读取时收集 effect
  4. 触发更新:通过 trigger 在修改时执行 effect
  5. ref 包装:用对象包裹基本类型,通过 .value 访问

性能优化要点:

  • 大数据列表使用 shallowRefshallowReactive 减少代理开销
  • 避免不必要的嵌套响应式对象
  • 合理使用 computed 缓存计算结果

💬 觉得有用的话,点个赞+收藏,关注我,每周持续更新实战教程!

标签:vue3 | 源码 | 响应式 | reactive | ref | proxy

相关推荐
yhole2 小时前
Nginx解决前端跨域问题
运维·前端·nginx
我爱学习好爱好爱2 小时前
Ansible 常用模块详解:hostname、selinux 、file实战
前端·chrome·ansible
爱丽_2 小时前
AQS 的 `state`:volatile + CAS 如何撑起原子性与可见性
java·前端·算法
Zik----2 小时前
Windows安装cuda
前端·ui·xhtml
王杨游戏养站系统2 小时前
3分钟搭建1个游戏下载站网站教程!SEO站长养站系统!
开发语言·前端·游戏·游戏下载站养站系统·游戏养站系统
是上好佳佳佳呀2 小时前
【前端(三)】CSS 属性梳理:从字体文本到背景表格
前端·css
gaolei_eit2 小时前
Vue3信号(Signals)深度解析:重新思考响应式编程的未来
前端
小江的记录本2 小时前
【端口号】计算机领域常见端口号汇总(完整版)
java·前端·windows·spring boot·后端·sql·spring
Reisentyan2 小时前
网站开发遇到的一个坑点
前端