Vue3 使用 Proxy 来实现响应式系统,Proxy 可以允许我们定义对象或者函数的自定义行为,例如属性查找,赋值,枚举,函数调用等。Vue3 借助 Proxy 的 handler 拦截操作,收集依赖,数据变更触发依赖更新,实现了响应系统核心。
在 Vue3 中,可以使用 reactive()
创建一个响应状态
import { reactive } from 'vue'// reactive stateconst 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();}// 创建一个响应式对象 stateconst 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 执行。