理解并尝试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
相关推荐
星_离3 分钟前
vue指令也就那样,拿捏
前端
符方昊5 分钟前
Taro小程序冷构建优化
前端
Winslei5 分钟前
【加密专栏】OpenHarmony应用开发-加解密之AES128_CBC_PKCS5
前端
Mrxyy6 分钟前
所有 TypeScript 中内置的泛型类型(Generic Utility Types)
前端
aiwery6 分钟前
CI/CD 实战全解析:原理、流程与落地探索
前端·ci/cd
悠然小熠10 分钟前
WPF中实现单例窗口解决方案
前端
南囝coding1 小时前
这个仓库堪称造轮子的鼻祖,建议看看!
前端·后端
suedar1 小时前
关于工程化的随想
前端
安琪吖1 小时前
微前端:qiankun框架在开发中遇到的问题
前端·vue·element-ui
不爱说话郭德纲1 小时前
🔥产品:"这功能很常见,不用原型,参考竞品就行!" 你会怎么做
前端·产品经理·产品