理解并尝试vue3源码的reactivity

🎯 核心作用

packages/reactivity 是 Vue 3 的响应式系统核心包,它提供了,是 Vue 3 架构的基础。这个包是平台无关的,可以被任何 JavaScript 环境使用。

packages/reactivity 是 Vue 3 的响应式系统核心,它提供了完整的响应式数据管理能力:

  1. 响应式数据管理:reactive、ref、computed

  2. 副作用系统:effect、watch、watchEffect

  3. 依赖追踪:自动收集和触发更新 (dep)

  4. 作用域管理:EffectScope 生命周期管理 (effectScope)

  5. 类型安全:完整的 TypeScript 支持

这边简单实现reactive、effect、dep来理解Vue3的源码。

Reactive之代码实现

这边实现最最核心的将对象转化为响应式对象,实现了一个响应式系统的基础部分,包括:

  1. Proxy代理‌:使用ES6的Proxy对象来拦截对目标对象的操作
  2. ‌get拦截器‌:
    • 在读取属性时自动追踪依赖关系(track)
    • 对嵌套对象进行递归代理,实现深层响应式
  3. ‌set拦截器‌:
    • 在设置属性时比较新旧值
    • 只有值真正改变时才触发更新(trigger)
  4. ‌响应式原理‌:
    • 通过代理模式实现对对象属性的访问和修改的拦截
    • 结合track和trigger函数实现依赖收集和派发更新
js 复制代码
// 定义一个reactive函数,用于创建响应式对象
function reactive(obj) {
  // 返回一个新的Proxy代理对象
  return new Proxy(obj, {
    // 拦截属性读取操作
    get(target, key) {
      // 追踪依赖关系(通常在响应式系统中记录哪些组件依赖此属性)
      // TODO: 追踪依赖关系
      // track(target, key)
      // 获取目标对象的属性值
      const result = target[key]
      console.log('get', key, result)
      // 如果属性值是对象,则递归地将其转换为响应式对象
      // 这样可以实现深层响应式
      return typeof result === 'object' && result !== null
        ? reactive(result)
        : result
    },

    // 拦截属性设置操作
    set(target, key, value) {
      // 保存旧值用于比较
      const oldValue = target[key]
      // 设置新值
      target[key] = value
      // 如果新值与旧值不同,则触发更新
      if (value !== oldValue) {
        // TODO: 触发更新
        // trigger(target, key)
      }
      console.log('set', key, value)
      // 返回true表示设置成功
      return true
    }
  })
}


//  测试代码
const obj = reactive({
  name: 'John',
  age: 20,
  address: {
    city: 'Beijing',
    street: '123 Main St'
  }
})

console.log(obj.name)
obj.name = 'Jane'
console.log(obj.name)
obj.address.city = 'Shanghai'
console.log(obj.address.city)

注意这里的track和trigger属于dep的内容,后面实现。

执行node self-reactive.js,get和set的内容都被打印了~

Dep之代码实现

Dep代码实现了响应式系统的依赖收集和触发代码:

  1. 数据结构 ‌:
    • targetMap: WeakMap,以原始对象为键,值为depsMap
    • depsMap: Map,以对象属性为键,值为dep集合
    • dep: Set,存储所有依赖该属性的effect
  2. track函数 ‌:
    • 建立target → key → effect的多级依赖关系
    • 采用WeakMap避免内存泄漏
    • 双向记录依赖关系(effect也记录自己依赖的dep)
  3. trigger函数 ‌:
    • 根据target和key找到所有依赖的effect
    • 支持自定义调度器(scheduler)
    • 避免当前正在执行的effect重复触发
js 复制代码
// 全局变量,存储所有响应式对象的依赖关系,键是响应式对象,值是该对象所有key的依赖关系
//「targetMap」 的结构如下:
// {
//   target1: {
//     key1: [effect1, effect2],
//     key2: [effect3]
//   },
//   target2: {
//     key3: [effect4]
//   }
// }
const targetMap = new WeakMap()

// 当前正在执行的effect
let activeEffect = null

// 依赖收集函数
function track(target, key) {
  // 如果没有活跃的effect,不需要收集
  if (!activeEffect) return

  // 获取target对应的depsMap(存储该对象所有key的依赖)
  // 举个例子,「depsMap」 的结构如下:
  // {
  //   key1: [effect1, effect2],
  //   key2: [effect3]
  // }
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 如果没有则新建一个Map并存入targetMap
    targetMap.set(target, (depsMap = new Map()))
  }

  // 获取key对应的依赖集合(存储所有依赖此key的effect)
  // 举个例子,「dep」 的结构如下:
  //  [effect1, effect2]
  let dep = depsMap.get(key)
  if (!dep) {
    // 如果没有则新建一个Set并存入depsMap
    depsMap.set(key, (dep = new Set()))
  }
  if(dep.has(activeEffect)) {
    return
  }
  // 将当前活跃effect添加到依赖集合
  dep.add(activeEffect)
  // effect也记录自己依赖的所有dep集合(用于清理)
  // 举个例子,「activeEffect.deps」 的结构如下:
  // [dep1, dep2],其中dep1和dep2是依赖此key的effect
  // [[effect1, effect2], [effect3]]
  activeEffect.deps.push(dep)
}

// 触发更新函数
function trigger(target, key) {
  // 获取target对应的所有key依赖
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  // 获取该key对应的所有effect
  const dep = depsMap.get(key)
  if (dep) {
    // 遍历执行所有依赖此key的effect
    dep.forEach(effect => {
      // 避免当前正在执行的effect重复触发
      if (effect !== activeEffect) {
        if (effect.scheduler) {
          // 如果有调度器则使用调度器执行
          effect.scheduler()
        } else {
          // 否则直接运行effect
          effect.run()
        }
      }
    })
  }
}

Effect之代码实现

ReactiveEffect 类‌:

  • 每个effect实例对应一个需要响应式执行的函数

  • 通过deps数组维护与所有Dep集合的双向引用

  • 提供run()方法实现依赖收集的上下文管理

关键设计点‌:

  • 活性标记 ‌:active控制是否参与依赖收集
  • 清理机制‌:每次执行前清理旧依赖,避免无效引用
  • 错误隔离‌:try-finally确保always清理上下文
js 复制代码
// 响应式副作用类(核心调度单元)
class ReactiveEffect {
  constructor(fn) {
    this.fn = fn        // 原始副作用函数
    this.deps = []      // 依赖集合数组(用于双向绑定)
    this.active = true  // 激活状态标记
  }

  // 执行副作用函数
  run() {
    if (!this.active) return this.fn() // 非激活状态直接执行

    try {
      activeEffect = this  // 设置当前活跃effect
      console.log('🔄 执行副作用...')
      return this.fn()     // 执行原始函数(期间会触发track)
    } finally {
      activeEffect = null  // 执行完成后清除引用
      
    }
  }

  // 停止响应式追踪
  stop() {
    this.active = false  // 标记为非激活状态
    this.cleanup()       // 清理所有依赖关系
  }

  // 清理依赖关系(双向解除绑定)
  cleanup() {
    this.deps.forEach(dep => dep.delete(this)) // 从所有dep中移除自己
    this.deps.length = 0  // 清空依赖数组
  }
}

// 创建并运行副作用的工厂函数
function effect(fn) {
  const _effect = new ReactiveEffect(fn) // 创建effect实例
  _effect.run()                         // 立即执行首次收集依赖
  return _effect                        // 返回可管理的effect对象
}

三小只合体 - Reactivity

将以上代码合体,简单的reactive系统初步形成!

js 复制代码
// ==================== 完整的响应式系统实现 ====================

// ==================== 依赖追踪系统 (dep.js) ====================
// 全局变量,存储所有响应式对象的依赖关系
const targetMap = new WeakMap()

// 当前正在执行的effect
let activeEffect = null

// 设置当前活跃的 effect(供外部使用)
function setActiveEffect(effect) {
  activeEffect = effect
}

// 依赖收集函数
function track(target, key) {
  // 如果没有活跃的effect,不需要收集
  if (!activeEffect) return

  // 获取target对应的depsMap(存储该对象所有key的依赖)
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    // 如果没有则新建一个Map并存入targetMap
    targetMap.set(target, (depsMap = new Map()))
  }

  // 获取key对应的依赖集合(存储所有依赖此key的effect)
  let dep = depsMap.get(key)
  if (!dep) {
    // 如果没有则新建一个Set并存入depsMap
    depsMap.set(key, (dep = new Set()))
  }
  if (dep.has(activeEffect)) {
    return
  }
  // 将当前活跃effect添加到依赖集合
  dep.add(activeEffect)
  // effect也记录自己依赖的所有dep集合(用于清理)
  activeEffect.deps.push(dep)

}

// 触发更新函数
function trigger(target, key) {
  // 获取target对应的所有key依赖
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  // 获取该key对应的所有effect
  const dep = depsMap.get(key)
  if (dep) {
    console.log('⚡ 触发更新:', key, '-> 执行', dep.size, '个副作用')
    // 遍历执行所有依赖此key的effect
    dep.forEach(effect => {
      // 避免当前正在执行的effect重复触发
      if (effect !== activeEffect) {
        if (effect.scheduler) {
          // 如果有调度器则使用调度器执行
          effect.scheduler()
        } else {
          // 否则直接运行effect
          effect.run()
        }
      }
    })
  }
}

// ==================== 响应式对象系统 (reactive.js) ====================
// 定义一个reactive函数,用于创建响应式对象
function reactive(obj) {
  // 返回一个新的Proxy代理对象
  return new Proxy(obj, {
    // 拦截属性读取操作
    get(target, key) {
      // 追踪依赖关系(通常在响应式系统中记录哪些组件依赖此属性)
      track(target, key)
      // 获取目标对象的属性值
      const result = target[key]
      // 如果属性值是对象,则递归地将其转换为响应式对象
      // 这样可以实现深层响应式
      return typeof result === 'object' && result !== null
        ? reactive(result)
        : result
    },

    // 拦截属性设置操作
    set(target, key, value) {
      // 保存旧值用于比较
      const oldValue = target[key]
      // 设置新值
      target[key] = value
      // 如果新值与旧值不同,则触发更新
      if (value !== oldValue) {
        // 触发更新
        trigger(target, key)
      }
      // 返回true表示设置成功
      return true
    }
  })
}

// ==================== 副作用系统 (effect.js) ====================
// 响应式副作用类(核心调度单元)
class ReactiveEffect {
  constructor(fn) {
    this.fn = fn        // 原始副作用函数
    this.deps = []      // 依赖集合数组(用于双向绑定)
    this.active = true  // 激活状态标记
  }

  // 执行副作用函数
  run() {
    if (!this.active) return this.fn() // 非激活状态直接执行

    try {
      setActiveEffect(this)  // 设置当前活跃effect
      console.log('🔄 执行effect函数...')
      return this.fn()     // 执行原始函数(期间会触发track)
    } finally {
      setActiveEffect(null)  // 执行完成后清除引用
      // 注意:这里不应该调用 cleanup(),因为会清空依赖关系
      // 只有在 stop() 时才应该清理依赖
    }
  }

  // 停止响应式追踪
  stop() {
    this.active = false  // 标记为非激活状态
    this.cleanup()       // 清理所有依赖关系
  }

  // 清理依赖关系(双向解除绑定)
  cleanup() {
    this.deps.forEach(dep => dep.delete(this)) // 从所有dep中移除自己
    this.deps.length = 0  // 清空依赖数组
  }
}

// 创建并运行副作用的工厂函数
function effect(fn) {
  const _effect = new ReactiveEffect(fn) // 创建effect实例
  _effect.run()                         // 立即执行首次收集依赖
  return _effect                        // 返回可管理的effect对象
}

// ==================== 测试代码 ====================
console.log('🚀 开始测试响应式系统...\n')

const obj = reactive({ count: 0, name: 'John' })

// 创建响应式effect
const myEffect = effect(() => {
  console.log('📊 Count:', obj.count)
})
const myEffect2 = effect(() => {
  console.log('📊 Count2:', obj.count)
})
const myEffect1 = effect(() => {
  console.log('👤 Name:', obj.name)
})

// 修改触发更新
obj.count++
obj.count++
obj.name = 'Jane1'
obj.name = 'Jane2'

// 手动停止响应
myEffect.stop()

obj.count = 100

console.log('\n✅ 测试完成!')

执行

shell 复制代码
node xx.js
相关推荐
前端小张同学2 小时前
基础需求就用AI写代码,你会焦虑吗?
java·前端·后端
小满zs2 小时前
Next.js第一章(入门)
前端
摇滚侠2 小时前
CSS(层叠样式表)和SCSS(Sassy CSS)的核心区别
前端·css·scss
不爱吃糖的程序媛2 小时前
Electron 桌面应用开发入门指南:从零开始打造 Hello World
前端·javascript·electron
Dontla2 小时前
前端状态管理,为什么要状态管理?(React状态管理、zustand)
前端·react.js·前端框架
编程猪猪侠2 小时前
前端根据文件后缀名智能识别文件类型的实用函数
前端
宋辰月2 小时前
学习react第一天
javascript·学习·react.js
yinuo2 小时前
基于 Git Submodule 的代码同步融合方案
前端
伶俜monster3 小时前
大模型 “万能接口” MCP 横空出世!打破数据孤岛,重塑 AI 交互新规则
前端·mcp
你听得到113 小时前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter