Vue3 使用 Proxy 来实现响应式系统,Proxy 可以允许我们定义对象或者函数的自定义行为,例如属性查找,赋值,枚举,函数调用等。Vue3 借助 Proxy 的 handler 拦截操作,收集依赖,数据变更触发依赖更新,实现了响应系统核心。
在 Vue3 中,可以使用 reactive()
创建一个响应状态
import { reactive } from 'vue'
// reactive state
const state = reactive({
desc: 'Hello World!',
count: 0
});
创建响应式对象
我们在源码 core/packages/reactivity/src/reactive.ts
文件中看到了 reactive
代码实现,通过 reactReactiveObject
创建响应式数据。
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// ...
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
baseHandlers
和 collectionHandlers
分别用来处理普通对象和集合类型(如 Map、Set 等)的代理逻辑。
baseHandlers
baseHandlers
处理普通对象的代理逻辑。它定义了对象属性的 get
、set
、deleteProperty
等操作的行为。以下是一个简化的示例:
export const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
// 处理属性读取
const res = Reflect.get(target, key, receiver);
// 依赖收集逻辑
track(target, key);
return res;
},
set(target, key, value, receiver) {
// 处理属性设置
const result = Reflect.set(target, key, value, receiver);
// 触发更新逻辑
trigger(target, key);
return result;
},
deleteProperty(target, key) {
// 处理属性删除
const result = Reflect.deleteProperty(target, key);
// 触发更新逻辑
trigger(target, key);
return result;
}
};
collectionHandlers
collectionHandlers
处理集合类型(如 Map、Set 等)的代理逻辑。它定义了集合的 get
、set
、add
、delete
等操作的行为。以下是一个简化的示例:
export const mutableCollectionHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
// 处理集合的属性读取
const res = Reflect.get(target, key, receiver);
// 依赖收集逻辑
track(target, key);
return res;
},
set(target, key, value, receiver) {
// 处理集合的属性设置
const result = Reflect.set(target, key, value, receiver);
// 触发更新逻辑
trigger(target, key);
return result;
},
add(target, value) {
// 处理集合的添加操作
const result = target.add(value);
// 触发更新逻辑
trigger(target, value);
return result;
},
delete(target, key) {
// 处理集合的删除操作
const result = target.delete(key);
// 触发更新逻辑
trigger(target, key);
return result;
}
};
上述的简化代码看出,在访问属性执行了 track 动作,操作(新增,删除,更新)属性执行了 trigger 动作,那么这两个动作分别做了哪些事情。
依赖收集
track
track 函数用于跟踪对响应式属性的访问。它会检查当前正在运行的 activeEffect
并将其记录为依赖项,这样当响应式属性发生变化时,相关的 activeEffect
就会被重新运行。
/**
* 跟踪对响应式属性的访问。
* 该函数会检查当前正在运行的 effect,并将其记录为依赖项,
* 记录所有依赖于该响应式属性的 effects。
*
* @param target - 代理的对象。
* @param type - 属性的访问类型。
* @param key - 响应式属性的标识符。
*/
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = createDep(() => depsMap!.delete(key))))
}
trackEffect(
activeEffect,
dep,
__DEV__
? {
target,
type,
key,
}
: void 0,
)
}
}
trackEffect
/**
* 这个函数会检查当前正在运行的 effect,并将其记录到依赖于 reactive 的 dep 中。
*
* @param effect - 当前正在运行的 ReactiveEffect 实例。
* @param dep - 记录所有依赖于 reactive property 的 effects 的依赖集合。
*/
export function trackEffect(
effect: ReactiveEffect,
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
// 如果 dep 中没有记录当前 effect 的 _trackId
if (dep.get(effect) !== effect._trackId) {
// 将当前 effect 的 _trackId 设置到 dep 中
dep.set(effect, effect._trackId)
// 获取 effect 的 deps 数组中当前索引位置的旧 dep
const oldDep = effect.deps[effect._depsLength]
// 如果旧 dep 不等于当前 dep
if (oldDep !== dep) {
// 如果旧 dep 存在,清理旧 dep 中的 effect
if (oldDep) {
cleanupDepEffect(oldDep, effect)
}
// 将当前 dep 存储到 effect 的 deps 数组中,并增加 _depsLength
effect.deps[effect._depsLength++] = dep
} else {
// 如果旧 dep 等于当前 dep,仅增加 _depsLength
effect._depsLength++
}
}
}
触发更新
trigger
过程主要用于通知依赖于某个响应式数据的副作用函数(effect)重新运行。
trigger
/**
* 查找与目标对象(或特定属性)相关的所有依赖,并触发存储在这些依赖中的副作用。
* @param target - 响应式对象。
* @param type - 定义需要触发副作用的操作类型。
* @param key - 可用于定位目标对象中的特定响应式属性。
* @param newValue - 新值(可选)。
* @param oldValue - 旧值(可选)。
* @param oldTarget - 旧的 Map 或 Set 对象(可选)。
*/
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>,
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 从未被追踪过
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// 集合被清空
// 触发目标对象的所有副作用
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
deps.push(dep)
}
})
} else {
// 为 SET | ADD | DELETE 操作调度运行
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// 也为 ADD | DELETE | Map.SET 操作的迭代键运行
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// 数组添加新索引 -> 长度变化
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
}
}
// 触发所有收集到的副作用
triggerEffects(deps, dirtyLevel, debuggerEventExtraInfo)
}
triggerEffects
/**
* 触发依赖项的所有副作用。
*
* @param dep - 依赖项集合。
* @param dirtyLevel - 当前的脏级别。
* @param debuggerEventExtraInfo - 调试事件的额外信息(可选)。
*/
export function triggerEffects(
dep: Dep,
dirtyLevel: DirtyLevels,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
// 暂停调度
pauseScheduling()
// 遍历依赖项中的所有副作用
for (const effect of dep.keys()) {
// dep.get(effect) 非常昂贵,我们需要懒计算并重用结果
let tracking: boolean | undefined
// 如果副作用的脏级别小于当前脏级别,并且跟踪状态未定义或为真
if (
effect._dirtyLevel < dirtyLevel &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
// 如果副作用的脏级别为 NotDirty,则设置应调度标志
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
// 更新副作用的脏级别
effect._dirtyLevel = dirtyLevel
}
// 如果应调度标志为真,并且跟踪状态未定义或为真
if (
effect._shouldSchedule &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
// 触发副作用
effect.trigger()
// 如果副作用未运行或允许递归,并且脏级别不为 MaybeDirty_ComputedSideEffect
if (
(!effect._runnings || effect.allowRecurse) &&
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
) {
// 重置应调度标志
effect._shouldSchedule = false
// 如果副作用有调度器,则将其加入调度队列
if (effect.scheduler) {
queueEffectSchedulers.push(effect.scheduler)
}
}
}
}
// 重置调度
resetScheduling()
}
手写一个 reactvive 响应式系统
通过上述的概念,我们手写一个轻量的 reactive 响应式系统,当然 vue 里面包含了复杂的数据结构处理,边界情况。这里就不过多展开了。
// 当前激活的副作用函数
let activeEffect: (() => void) | undefined;
// Dep 类用来收集和通知副作用函数
class Dep {
subscribers = new Set<() => void>(); // 存储副作用函数的集合
// 依赖收集方法,将当前激活的副作用函数添加到订阅者集合中
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
// 通知方法,遍历所有订阅的副作用函数并执行
notify() {
this.subscribers.forEach((sub) => sub());
}
}
// reactive 函数用来创建响应式对象
function reactive(target: any) {
const depMap = new Map<string, Dep>(); // 存储属性名和 Dep 对象的映射
return new Proxy(target, {
get(obj, key) {
if (!depMap.has(key as string)) {
// 如果属性没有对应的 Dep 对象,则创建一个
depMap.set(key as string, new Dep());
}
const dep = depMap.get(key as string);
// 收集依赖
dep?.depend();
// 返回属性值
return obj[key];
},
set(obj, key, value) {
// 设置属性值
obj[key] = value;
// 获取属性对应的 Dep 对象
const dep = depMap.get(key as string);
// 如果属性有订阅者,则通知它们更新
dep?.notify();
return true;
},
});
}
// effect 函数用来创建副作用函数
function effect(fn: () => void) {
const effectFn = () => {
// 将当前副作用函数设置为激活状态
activeEffect = effectFn;
// 执行副作用函数
fn();
// 重置激活状态
activeEffect = undefined;
};
// 立即执行副作用函数
effectFn();
}
// 创建一个响应式对象 state
const state = reactive({
count: 0,
});
// 定义一个副作用函数,它将在 state.count 发生变化时触发
effect(() => {
console.log(`count is: ${state.count}`);
});
// 修改响应式对象的值,每次修改都会触发副作用函数
state.count++; // 触发副作用函数,输出 "count is: 1"
state.count++; // 再次触发副作用函数,输出 "count is: 2"
上述例子,当执行到副作用,会触发响应式对象的 get 回调,进行依赖收集,当变更 count
会触发响应式对象的 set 回调,触发 effect 执行。