🎯 核心作用
packages/reactivity 是 Vue 3 的响应式系统核心包,它提供了,是 Vue 3 架构的基础。这个包是平台无关的,可以被任何 JavaScript 环境使用。
packages/reactivity 是 Vue 3 的响应式系统核心,它提供了完整的响应式数据管理能力:
-
响应式数据管理:reactive、ref、computed
-
副作用系统:effect、watch、watchEffect
-
依赖追踪:自动收集和触发更新 (dep)
-
作用域管理:EffectScope 生命周期管理 (effectScope)
-
类型安全:完整的 TypeScript 支持
这边简单实现reactive、effect、dep来理解Vue3的源码。
Reactive之代码实现
这边实现最最核心的将对象转化为响应式对象,实现了一个响应式系统的基础部分,包括:
- Proxy代理:使用ES6的Proxy对象来拦截对目标对象的操作
- get拦截器:
- 在读取属性时自动追踪依赖关系(track)
- 对嵌套对象进行递归代理,实现深层响应式
- set拦截器:
- 在设置属性时比较新旧值
- 只有值真正改变时才触发更新(trigger)
- 响应式原理:
- 通过代理模式实现对对象属性的访问和修改的拦截
- 结合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代码实现了响应式系统的依赖收集和触发代码:
- 数据结构 :
targetMap
: WeakMap,以原始对象为键,值为depsMapdepsMap
: Map,以对象属性为键,值为dep集合dep
: Set,存储所有依赖该属性的effect
- track函数 :
- 建立target → key → effect的多级依赖关系
- 采用WeakMap避免内存泄漏
- 双向记录依赖关系(effect也记录自己依赖的dep)
- 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
