大家好,我是作曲家种太阳,本次的专栏会带你一步步实现一个mini-vue3,每个小节都都回有一些测试,验证当前的一个逻辑,并且我已经把代码上传到github上了,可以根据每个章节去看对应的源码提交记录。
本章介绍循序渐进的介绍vue3的响应式系统,reactivity和effect都做了什么事情,没有需要写的代码,预计40分钟看完
前言
reactive 负责创建响应式数据对象,effect 则用来注册依赖于这些响应式数据的副作用函数(effect函数),两者共同实现响应式。
🌟 更通俗的解释: reactive:
用于把普通 JavaScript 对象转成具备响应式能力的对象(即 Proxy 对象)。
数据变化时,会自动触发 Proxy 的 getter 和 setter,方便追踪依赖 和 触发更新。
effect:
用于注册一个副作用函数(例如更新 DOM 视图的函数)。
当这个函数运行时,它所依赖的数据被访问,就会记录依赖关系(track)。
🚀 一.reactive 整体执行逻辑
- 调用
reactive()
→ 进入createReactiveObject()
- 内部执行
new Proxy(target, mutableHandlers)
创建代理 - 存入缓存
proxyMap
- 返回创建的
proxy
步骤一:进入 createReactiveObject 方法
// 调用reactive
const proxy = reactive(target)
// reactive
function reactive(target) {
return createReactiveObject(target, mutableHandlers)
}
- 内部调用
createReactiveObject
方法。 - 第二个参数是
mutableHandlers
,即代理对象的 handler 配置。
步骤二:执行 Proxy 构造函数
// reactive 的构造主逻辑函数
function createReactiveObject(target, baseHandlers) {
const proxy = new Proxy(target, baseHandlers)
proxyMap.set(target, proxy)
return proxy
}
proxyMap
是一个WeakMap,用来缓存target
和生成的proxy
对象。- 作用:避免重复对同一对象创建多个代理。
此处关键:
- 第一个参数:原始对象
target
- 第二个参数:
baseHandlers
(实际传入的是mutableHandlers
) mutableHandlers
来自源码文件:packages/reactivity/src/baseHandlers.ts
二. 📌 mutableHandlers 核心代码示例:
// mutableHandlers 内部结构 (baseHandlers.ts)
export const mutableHandlers = {
get: createGetter(),
set: createSetter()
}
// getter
function createGetter() {
return function get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key) // 收集依赖
return res
}
}
// setter
function createSetter() {
return function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发依赖更新
return result
}
}
⚠️ 注意:此时不会触发 getter 和 setter(因为还没有读取或修改属性)。
三. effect
源码流程深度解析
Vue3 的响应式系统中,effect
起到了关键作用,它是注册副作用函数(effect 函数)的入口。
我们以源码路径 packages/reactivity/src/effect.ts
为例,追踪一下 effect 的完整运行逻辑。
Effect 流程
effect(fn)
│
ReactiveEffect实例化
│
run()首次执行
│
副作用函数(fn)执行
│
触发getter → 依赖收集 track()
│
依赖关系存储到 targetMap
数据更新
│
setter触发 → trigger()
│
根据targetMap取出副作用函数
│
重新执行副作用函数 → 视图自动更新
🚩 1、调用 effect 函数时发生了什么?
effect(() => {
document.querySelector('#app').innerText = obj.name
})
① 调用 effect:
function effect(fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
- 创建
ReactiveEffect
实例,拥有run()
和stop()
方法 - 执行
_effect.run()
会立即运行传入的副作用函数
② ReactiveEffect 类内部实现:
class ReactiveEffect {
constructor(fn) {
this.fn = fn
this.deps = []
}
run() {
activeEffect = this
return this.fn()
}
stop() {
// 停止响应式追踪
}
}
run()
中会设置当前活跃的 effect,并执行副作用函数fn
🚩 2、effect 函数中执行副作用时发生了什么?
③ 执行 fn 时触发 getter 与依赖收集:
get(target, key, receiver) {
const res = Reflect.get(target, key, receiver)
track(target, key)
return res
}
④ track 依赖收集核心逻辑:
/**
* 响应式依赖收集函数 —— 在读取响应式数据(getter)时调用
* 用于将当前活跃的副作用函数(effect)收集到对应的依赖图中
* @param target 被读取的响应式对象
* @param key 被读取的属性名
*/
function track(target, key) {
// 如果当前没有正在执行的副作用函数,直接返回
// 即:不是在 effect(fn) 的执行过程中
if (!activeEffect) return // `activeEffect` 是一个全局变量
// 获取当前 target 的依赖映射表(depsMap)
// targetMap 是一个 WeakMap:target 对象 => Map(属性 => Set(effect))
let depsMap = targetMap.get(target)
// 如果没有 depsMap,就初始化一个并存储
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
// 获取当前属性的依赖集合(Set)
let dep = depsMap.get(key)
// 如果当前属性还没有被追踪过,创建一个新的 Set 存放 effect
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
// 把当前正在运行的 effect 函数添加到依赖集合中
dep.add(activeEffect)
// 同时反向记录:当前的 effect 也要保存它依赖了哪个 dep(用于后续清除依赖)
activeEffect.deps.push(dep)
}
🔍 收集的结构如下:
targetMap = {
target对象: {
属性key: Set([effectFn1, effectFn2])
}
}
🚩 3、数据变化触发更新的流程(setter → trigger)
setTimeout(() => {
obj.name = '李四'
}, 2000)
① setter 被触发:
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
trigger(target, key) // 触发依赖
return result
}
② trigger 执行:
/**
* 响应式数据发生变化时调用,用于触发依赖于该数据的副作用函数(effect)
* @param target 被修改的响应式对象
* @param key 修改的属性名
*/
function trigger(target, key) {
// 从全局依赖映射表中获取当前对象的依赖映射(depsMap)
const depsMap = targetMap.get(target)
if (!depsMap) return // 没有依赖说明这个对象没被响应式追踪过,直接返回
// 获取当前属性 key 对应的所有 effect 依赖(一个 Set)
const dep = depsMap.get(key)
if (!dep) return // 没有依赖这个属性的 effect,返回
// 触发所有依赖的副作用函数
triggerEffects(dep)
}
/**
* 依次执行所有依赖当前属性的副作用函数
* @param dep Set<effect>,表示一组副作用函数集合
*/
function triggerEffects(dep) {
// 遍历 Set 中的每一个 effect 实例,执行其 run 方法
dep.forEach(effect => {
effect.run() // 再次执行 effect 函数,完成响应式更新(例如更新 DOM)
})
}
- 找到之前收集过的副作用函数并重新执行,实现视图自动更新
targetMap 的数据结构
targetMap 是一个 嵌套结构的 WeakMap,用于记录每一个响应式对象的每一个属性,它们分别被哪些副作用函数(effect)所依赖,但是他到底是什么样的key与value形式呢
targetMap = WeakMap {
obj => Map {
'name' => Set { effect1, effect2 },
'age' => Set { effect1 }
}
}
// 模拟 effect 函数
function effect1() { console.log('effect1 执行') }
function effect2() { console.log('effect2 执行') }
// 目标对象(响应式对象)
const obj = { name: '张三', age: 18 }
// 全局依赖表
const targetMap = new WeakMap()
// 模拟 track 函数 —— 收集依赖
function track(target, key, effectFn) {
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(effectFn)
}
// 模拟 trigger 函数 —— 触发依赖
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (!dep) return
dep.forEach(effectFn => effectFn())
}
// 模拟依赖收集过程
track(obj, 'name', effect1)
track(obj, 'name', effect2)
track(obj, 'age', effect1)
console.log('触发 name:')
trigger(obj, 'name') // effect1, effect2 执行
console.log('触发 age:')
trigger(obj, 'age') // effect1 执行
🔐 为什么用 WeakMap 而不是 Map?
使用 WeakMap 的理由 说明
✅ 自动垃圾回收 target 对象被销毁后,自动释放内存
✅ 不会导致内存泄漏 Map 会强引用 key,WeakMap 不会
✅ target 一定是对象 响应式系统的目标对象都是对象或数组
总结
这一节我们学习了 reactivite 还有 Effect 的作用,说明了targetMap的数据结构 下一章节,我们就开始去手写这部分代码了