UNPKG

@purevue/reactivity

Version:

## 📖 Introduction

981 lines (958 loc) 33.1 kB
import { isArray, isIntegerKey, hasOwn, isSymbol, isObject as isObject$1, isFunction as isFunction$1 } from '@purevue/shared'; const isObject = (val) => val !== null && typeof val === 'object'; const isFunction = (val) => typeof val === 'function'; const hasChanged = (value, oldValue) => !Object.is(value, oldValue); function isReactive(value) { if (isReadonly(value)) { return isReactive(value['__v_raw']); } return !!(value && value['__v_isReactive']); } function isReadonly(value) { return !!(value && value['__v_isReadonly']); } function isShallow(value) { return !!(value && value['__v_isShallow']); } function isProxy(value) { return value ? !!value['__v_raw'] : false; } function isRef(val) { return !!(val && val['__v_isRef'] === true); } function toRaw(observed) { const raw = observed && observed['__v_raw']; return raw ? toRaw(raw) : observed; } /** * Vue 内部用于标记响应式对象的特殊属性 key。 */ var ReactiveFlags; (function (ReactiveFlags) { /** * 跳过响应式转换标志。 * * - 当对象上存在 `__v_skip: true` 时, * `reactive()` 不会再对该对象进行代理包装。 * - 典型场景:一些已经被处理过的对象,避免重复代理。 */ ReactiveFlags["SKIP"] = "__v_skip"; /** * 是否为响应式对象。 * * - 由 `reactive()` 包装过的对象上会自动带有该属性。 * - 常用于 Vue 内部做类型识别。 */ ReactiveFlags["IS_REACTIVE"] = "__v_isReactive"; /** * 是否为只读对象。 * * - 由 `readonly()` 包装过的对象上会自动带有该属性。 * - 修改只读对象会触发警告。 */ ReactiveFlags["IS_READONLY"] = "__v_isReadonly"; /** * 是否为浅层响应式对象。 * * - 由 `shallowReactive()` 或 `shallowReadonly()` 创建。 * - 只代理第一层属性,嵌套对象不会被递归转换。 */ ReactiveFlags["IS_SHALLOW"] = "__v_isShallow"; /** * 获取原始对象。 * * - 通过 `toRaw(obj)` 实现:返回代理背后的原始对象。 * - 内部依赖 `__v_raw` 属性来识别。 */ ReactiveFlags["RAW"] = "__v_raw"; /** * 是否为 Ref 对象。 * * - 由 `ref()` 或 `shallowRef()` 创建。 * - Ref 对象内部有 `.value` 属性,访问时会触发依赖收集。 */ ReactiveFlags["IS_REF"] = "__v_isRef"; })(ReactiveFlags || (ReactiveFlags = {})); /** * ReactiveEffect 内部使用的位标志(Bit Flags),用于表示 effect 的状态、行为和生命周期。 * * 每个标志占用一位,可以通过按位运算组合使用。 */ var EffectFlags; (function (EffectFlags) { /** * effect 当前处于激活状态,可以收集依赖并被触发。 * * 当不处于 ACTIVE 状态时,effect 被视为已停止,不会追踪依赖。 */ EffectFlags[EffectFlags["ACTIVE"] = 1] = "ACTIVE"; /** * effect 当前正在运行中。 * * 用于避免 effect 在执行过程中递归触发自身。 */ EffectFlags[EffectFlags["RUNNING"] = 2] = "RUNNING"; /** * effect 当前正在追踪依赖。 * * 在响应式属性的 getter 执行期间设置,表示可以收集依赖。 */ EffectFlags[EffectFlags["TRACKING"] = 4] = "TRACKING"; /** * effect 已经被通知,将在批量更新中执行。 * * 用于防止同一个 effect 被多次加入 flush 队列。 */ EffectFlags[EffectFlags["NOTIFIED"] = 8] = "NOTIFIED"; /** * effect 是脏的,需要重新计算。 * * 主要用于 computed 属性,用于标记缓存的值已过期。 */ EffectFlags[EffectFlags["DIRTY"] = 16] = "DIRTY"; /** * 允许 effect 自己递归触发自身。 * * 通常用于某些场景下需要自触发的 watcher 回调。 */ EffectFlags[EffectFlags["ALLOW_RECURSE"] = 32] = "ALLOW_RECURSE"; /** * effect 被暂停,不会被触发。 * * 可以临时挂起 effect,而不完全停止它。 */ EffectFlags[EffectFlags["PAUSED"] = 64] = "PAUSED"; /** * 标记 computed effect 至少已经被计算过一次。 * * 用于区分尚未运行的 computed 与已缓存的结果。 */ EffectFlags[EffectFlags["EVALUATED"] = 128] = "EVALUATED"; })(EffectFlags || (EffectFlags = {})); /** * 当前批量更新的嵌套深度 * 用于支持嵌套的 trigger 调用,如果 batchDepth > 0,则不会立即 flush */ let batchDepth = 0; /** * 同步批量执行队列 * 存储所有被 NOTIFIED 标记的 ReactiveEffect,用于同步批量触发 */ // TODO: Vue 3.5 后使用链表结构替代数组 let batchedSubs = []; /** * 将 effect 加入批量队列 * @param sub 要批量执行的 ReactiveEffect */ function batch(sub) { // 标记该 effect 已经被通知,避免重复加入队列 sub.flags |= EffectFlags.NOTIFIED; // TODO: 暂不处理 computed effect // if (isComputed) { // sub.next = batchedComputed // batchedComputed = sub // return // } // 将 effect 放入批量队列 batchedSubs.push(sub); } /** * 开始批量更新 * 每次 trigger 或 notify 时调用,增加 batchDepth * 支持嵌套 trigger 时延迟 flush */ function startBatch() { batchDepth++; } /** * 结束批量更新 * 当所有嵌套 trigger 完成后,flush 批量队列 * @remarks * 1. batchDepth > 1 表示还有外层 trigger 正在执行,不立即 flush * 2. 当 batchDepth 减为 0 时,按后进先出的顺序触发队列中的 effect * 3. 每个 effect 执行前会清除 NOTIFIED 标记 */ function endBatch() { // 如果还有外层 batch 正在进行,直接返回,延迟 flush if (--batchDepth > 0) { return; } // TODO: 暂不处理 computed effect // 按后进先出触发队列中的 effect let batchedSub = batchedSubs.pop(); while (batchedSub) { // 清除 NOTIFIED 标记,以便下次再次触发 batchedSub.flags &= ~EffectFlags.NOTIFIED; // 只有 ACTIVE 状态的 effect 才能被触发 if (batchedSub.flags & EffectFlags.ACTIVE) { try { batchedSub.trigger(); } catch (err) { throw err; } } batchedSub = batchedSubs.pop(); } } // ReactiveEffect 是 Vue 3 响应式系统中的 核心类 // 它表示一个“副作用函数”(effect),即在数据变化时需要重新执行的逻辑。 // 可以把它看作是 Vue 2 中 Watcher 的替代品,但更轻量、更灵活、且支持嵌套、调度器等高级功能。 /** 当前活跃的副作用 */ // TODO: vue 3.5+ 依赖收集重构,activeEffect 更改为 activeSub(Subscriber) let activeEffect = null; /** * 是否应该收集依赖 * 某些阶段需暂停收集依赖,如:执行 setup 函数 */ let shouldTrack = true; const trackStack = []; function pauseTracking() { trackStack.push(shouldTrack); shouldTrack = false; } function enableTracking() { trackStack.push(shouldTrack); shouldTrack = true; } function resetTracking() { const last = trackStack.pop(); shouldTrack = last === undefined ? true : last; } /** * Vue 响应式系统中的“观察者”(Observer) * - 通过 fn 表示副作用逻辑 * - 依赖响应式数据,当数据变化时被重新调度执行 */ class ReactiveEffect { fn; /** 记录当前这个 effect 被哪些响应式属性的 dep(依赖集合)引用了,用于在 stop() 或更新时清理依赖关系 */ // 双向追踪,反向记录 /* Vue 3 的响应式系统中: • dep (Set<effect>):被观察对象记录所有观察者 • effect.deps (Dep[]):观察者反过来记录自己在哪些对象中被追踪(用于清理) 二者配合,构成了可追踪 + 可解绑的响应系统,比 Vue 2 更高效可靠。 */ deps = []; /** 位标志,用于表示 effect 的状态、行为和生命周期 */ flags = EffectFlags.ACTIVE | EffectFlags.TRACKING; /** effect 的“更新调度器”。如果有,它接管 effect 的执行逻辑;如果没有,effect 默认立即运行。 */ scheduler; constructor(fn) { this.fn = fn; } /** * 执行副作用函数并收集依赖 * - 如果已经 stop,则只执行 fn,不进行依赖收集 * - 如果仍处于 active 状态,则: * 1. 清理旧依赖 * 2. 切换 activeEffect * 3. 执行 fn(触发响应式读取,从而建立新的依赖关系) * 4. 恢复 activeEffect */ run() { if (!(this.flags & EffectFlags.ACTIVE)) { // 如果 effect 已经被 stop 掉,直接执行一次 fn,不参与响应式追踪 return this.fn(); } // 依赖切换前先清理旧的依赖 /* 例子(不清理的后果) const effect = new ReactiveEffect(() => { console.log(flag.value ? count.value : 'no count') }) */ cleanupEffect(this); // 依赖收集 const prevEffect = activeEffect; const prevShouldTrack = shouldTrack; activeEffect = this; shouldTrack = true; try { // 执行副作用函数,并触发函数中响应性数据的依赖收集 return this.fn(); } finally { // 清除依赖 activeEffect = prevEffect; shouldTrack = prevShouldTrack; this.flags &= ~EffectFlags.RUNNING; } } /** * 停止 effect,使其不再收集依赖并参与更新 */ stop() { if (this.flags & EffectFlags.ACTIVE) { cleanupEffect(this); this.flags &= ~EffectFlags.ACTIVE; } } /** * 触发副作用函数的重新执行 */ trigger() { if (this.scheduler) { this.scheduler(); } else { this.run(); } } /** * 响应式依赖通知时调用(批处理队列) */ notify() { if (this.flags & EffectFlags.RUNNING && !(this.flags & EffectFlags.ALLOW_RECURSE)) { return; } if (!(this.flags & EffectFlags.NOTIFIED)) { batch(this); } } } /** * 清理 effect 的所有依赖 */ function cleanupEffect(effect) { const { deps } = effect; for (let i = 0; i < deps.length; i++) { deps[i].subs.delete(effect); } effect.deps.length = 0; } /** * 注册副作用函数,并立即执行一次,触发依赖收集 */ function effect(fn) { const eff = new ReactiveEffect(fn); eff.run(); return eff; } // Array 方法改写 // 1.只读方法的修复: include 等方法的依赖收集 // 2.修改方法的优化: push、pop 等方法暂停依赖收集,保护依赖收集不被污染 const arrayInstrumentations = { __proto__: null, push(...args) { return noTracking(this, 'push', args); }, // TODO: 其他方法的改写 }; // 数组的原生方法(push、pop、shift、unshift 等)内部会读写 length 或访问数组索引 // 改写数组方法 主要目的是防止内部访问 length 或索引时收集依赖,保护依赖收集不被污染,不是为了触发响应式更新 // 这点和 Vue2 不一样(Proxy 可以拦截数组索引和 length 的操作,而 Object.defineProperty 不行) // 触发更新(trigger)是 在 set / add / delete 里完成的,即数组真实修改索引或 length 时才触发 function noTracking(self, method, args = []) { // 暂停依赖收集,防止方法内部访问 length 或索引时触发错误的依赖 pauseTracking(); startBatch(); const res = toRaw(self)[method].apply(self, args); endBatch(); resetTracking(); return res; } /** * Dep 依赖管理器 * 每个响应式属性都会对应一个 Dep,管理它的订阅者 (effect) */ class Dep { /** 当前 dep 的版本号(触发 trigger 时递增) */ version = 0; /** 关联的 computed(如果有的话) */ // TODO: 处理 ComputedRefImpl // computed?: ComputedRefImpl /** 保存订阅该 dep 的 effects */ // TODO: 源码 Vue 3.4+ 换成了双向链表结构:能更高效地维护 effect 访问顺序,避免重复遍历 subs = new Set(); constructor() { } /** * 收集依赖 */ track(debugInfo) { // 如果没有激活的 effect,或者禁止追踪,则不收集 if (!activeEffect || !shouldTrack) { return; } // 将当前 effect 添加到 dep.subs if (!this.subs.has(activeEffect)) { debugInfo && console.log('[Dep track]', debugInfo); this.subs.add(activeEffect); // 同时反向记录 dep 到 effect 的依赖表 if (!activeEffect.deps.includes(this)) { activeEffect.deps.push(this); } } } /** * 触发依赖更新 */ trigger(debugInfo) { this.version++; this.notify(); } notify(debugInfo) { startBatch(); try { const effectsToRun = new Set(); // 收集所有订阅者(避免在运行时修改 set 导致死循环) this.subs.forEach((effect) => { if (effect !== activeEffect) { effectsToRun.add(effect); } }); // 逐个执行订阅者 effectsToRun.forEach((effect) => { debugInfo && console.log('[Dep trigger]', debugInfo); effect.notify(); }); } finally { endBatch(); } } } /** * 一个特殊的唯一 Symbol,表示对象的「遍历依赖」。 * * - 当在 effect 里写 `for...in` 或者 `Object.keys(obj)`, * Vue 就会对这个 ITERATE_KEY 建立依赖。 * - 这样一旦对象新增 / 删除属性(ADD / DELETE),就会触发这些依赖。 */ const ITERATE_KEY = Symbol('Object iterate'); /** * 一个特殊的唯一 Symbol,表示数组的「遍历依赖」。 * * - 当在 effect 里写 `for...of` 或者 `arr.forEach`, * Vue 就会对这个 ARRAY_ITERATE_KEY 建立依赖。 * - 这样一旦数组新增 / 删除索引,就会触发这些依赖。 */ const ARRAY_ITERATE_KEY = Symbol('Array iterate'); // 全局依赖图:targetMap 记录所有响应式对象 reactive 中,每个属性对应的 effect 集合 const targetMap = new WeakMap(); /** * track 收集依赖 * 每当读取响应式对象的某个属性时,触发依赖收集,把当前正在运行的 effect 与这个属性建立联系 * @param target 响应式对象 * @param key 被访问的属性键 */ function track(target, key) { // 如果当前没有正在运行的副作用函数,或者不应该收集依赖,直接跳过 if (!activeEffect || !shouldTrack) return; // 获取 target 对应的所有属性依赖映射 let depsMap = targetMap.get(target); if (!depsMap) { // 如果不存在则新建一个 Map 来存储该 target 的属性依赖 depsMap = new Map(); targetMap.set(target, depsMap); } // 获取指定 key 对应的依赖集合(一个 Dep,其实是 Set<ReactiveEffect>) let dep = depsMap.get(key); if (!dep) { // 如果不存在则新建一个 Dep 用于收集依赖 dep = new Dep(); depsMap.set(key, dep); } // 将当前 activeEffect 添加到 dep 中,实现依赖收集 dep.track(); } /** * 触发类型枚举,表示对响应式对象属性的操作类型 */ var TriggerOpTypes; (function (TriggerOpTypes) { TriggerOpTypes["SET"] = "set"; TriggerOpTypes["ADD"] = "add"; TriggerOpTypes["DELETE"] = "delete"; TriggerOpTypes["CLEAR"] = "clear"; })(TriggerOpTypes || (TriggerOpTypes = {})); /** * trigger 派发通知 * 当修改响应式对象的某个属性时,找到所有依赖这个属性的 effect,并触发它们重新运行 * @param target 响应式对象 * @param type 操作类型,参考 TriggerOpTypes * @param key 被操作的属性键 */ function trigger(target, type, key) { // 获取 target 对应的所有属性依赖映射 const depsMap = targetMap.get(target); if (!depsMap) return; // 没有依赖记录,说明没人在监听这个属性,直接返回 // 如 Map.clear() if (type === TriggerOpTypes.CLEAR) { // 如果是清空操作,触发所有依赖 depsMap.forEach((dep) => { dep.trigger(); }); } else { // 判断 target 是否为数组 const targetIsArray = isArray(target); // 判断 key 是否为数组索引(整数键) const isArrayIndex = targetIsArray && isIntegerKey(key); if (targetIsArray && key === 'length') { // TODO // 如果修改的是数组的 length 属性 // 这里的逻辑暂时注释,实际应触发长度相关依赖 // const newLength = Number(newValue) // depsMap.forEach((dep, key) => { // if (key === 'length' || key === ARRAY_ITERATE_KEY || (!isSymbol(key) && key >= newLength)) { // run(dep) // } // }) return; } // 触发指定 key 的依赖 const dep = depsMap.get(key); if (dep) dep.trigger(); // 如果是数组索引,触发数组迭代器相关依赖 if (isArrayIndex) { depsMap.get(ARRAY_ITERATE_KEY)?.trigger(); } // 根据不同操作类型,触发额外的依赖 switch (type) { case TriggerOpTypes.ADD: if (!targetIsArray) { // 新增普通对象属性,触发对象的迭代器依赖 depsMap.get(ITERATE_KEY)?.trigger(); } else if (isArrayIndex) { // 新增数组索引,触发 length 属性的通知,如 Array.push() depsMap.get('length')?.notify(); } break; case TriggerOpTypes.DELETE: if (!targetIsArray) { // 删除普通对象属性,触发对象的迭代器依赖 depsMap.get(ITERATE_KEY)?.trigger(); } break; case TriggerOpTypes.SET: // 修改已有属性,之前已经触发过对应 key 的依赖,无需额外操作 break; } } } /**x * MutableReactiveHandler 是 Vue 3 响应式系统中用于创建响应式对象的 Proxy handler。 * 它拦截对目标对象的读取、设置、删除等操作,实现依赖收集和触发更新的功能。 * 该类支持深度响应式处理,并针对数组方法做了特殊处理以优化性能和行为一致性。 */ class MutableReactiveHandler { _isReadonly; _isShallow; constructor(_isReadonly = false, _isShallow = false) { this._isReadonly = _isReadonly; this._isShallow = _isShallow; } /** * 拦截读取操作,返回对应的属性值。 * 该方法会进行依赖收集,并递归地将嵌套对象转为响应式对象。 * 对数组的特定方法做了重写以支持依赖追踪和触发。 * @param target 目标对象 * @param key 被读取的属性键 * @param receiver Proxy 或继承对象 * @returns 属性对应的值,可能是响应式对象或原始值 */ get(target, key, receiver) { if (key === ReactiveFlags.IS_REACTIVE) { return true; } else if (key === ReactiveFlags.RAW) { return target; } // Array 方法改写 // 1.只读方法的修复: include 等方法的依赖收集 // 2.修改方法的优化: push、pop 等方法的依赖触发(暂停依赖收集) if (isArray(target) && arrayInstrumentations[key]) { return arrayInstrumentations[key]; } const res = Reflect.get(target, key, isRef(target) ? target : receiver); track(target, key); // 递归响应式处理 if (isObject(res)) { return reactive(res); } return res; } /** * 拦截设置操作,设置属性值并触发相关依赖更新。 * 支持处理 ref 类型的特殊赋值逻辑,以及数组的新增或修改操作。 * @param target 目标对象 * @param key 被设置的属性键 * @param value 新的属性值 * @param receiver Proxy 或继承对象 * @returns 设置操作是否成功 */ set(target, key, value, receiver) { let oldValue = target[key]; oldValue = toRaw(oldValue); value = toRaw(value); // 如果旧值是 ref,新值不是 ref → 自动更新 .value if (!isArray(target) && isRef(oldValue) && !isRef(value)) { oldValue.value = value; return true; } // 处理新增的情况,如 obj.a = 'xx', arr.push('xx') const hadKey = isArray(target) && isIntegerKey(key) ? Number(key) < target.length : hasOwn(target, key); // 通过 Reflect.set 进行真实赋值 const result = Reflect.set(target, key, value, isRef(target) ? target : receiver); // 只有 目标对象确实是 receiver 本身 时才触发依赖,防止其子类实例的改变触发父类 if (target === toRaw(receiver)) { if (!hadKey) { trigger(target, TriggerOpTypes.ADD, key); } else if (hasChanged(value, oldValue)) { trigger(target, TriggerOpTypes.SET, key); } } return result; } /** * 拦截删除操作,删除目标对象的指定属性,并触发依赖更新。 * @param target 目标对象 * @param key 要删除的属性键 * @returns 删除操作是否成功 */ deleteProperty(target, key) { const hadKey = hasOwn(target, key); // const oldValue = target[key] const result = Reflect.deleteProperty(target, key); if (result && hadKey) { trigger(target, TriggerOpTypes.DELETE, key); } return result; } /** * 拦截 in 操作符,判断属性是否存在于目标对象中。 * 同时进行依赖收集,避免遗漏对属性存在性的响应式追踪。 * @param target 目标对象 * @param key 要检测的属性键 * @returns 属性是否存在于目标对象中 */ has(target, key) { const result = Reflect.has(target, key); if (!isSymbol(key)) { track(target, key); } return result; } /** * 拦截 Object.getOwnPropertyKeys 等操作,返回目标对象自身的所有属性键。 * 同时进行依赖收集,用于追踪对象的遍历操作。 * @param target 目标对象 * @returns 目标对象的所有自身属性键组成的数组 */ ownKeys(target) { track(target, isArray(target) ? 'length' : ITERATE_KEY); return Reflect.ownKeys(target); } } /** 用于缓存响应式对象,避免重复创建 */ const reactiveMap = new WeakMap(); // TODO: 暂不处理 shallow 和 readonly function reactive(target) { if (!isObject(target)) return target; // 已经被代理过的对象直接返回缓存 const existingProxy = reactiveMap.get(target); if (existingProxy) return existingProxy; // 创建一个新的代理对象 // TODO: 源码区分 普通对象/数组: baseHandlers , 集合类对象如Map/Set: collectionHandlers // Map/Set 的 .get()、.set()、.delete() 不是普通的属性访问,Vue 会重写方法 const handler = new MutableReactiveHandler(); const proxy = new Proxy(target, handler); // 缓存代理对象 reactiveMap.set(target, proxy); return proxy; } function toReactive(value) { return isObject(value) ? reactive(value) : value; } // 观察者模式中的Subject class RefImpl { /** 原始值 */ _rawValue; /** value值 */ _value; /** ref标识 */ __v_isRef = true; /** 中介容器,把 Subject 和多个 Observer 关联起来 */ dep = new Dep(); constructor(value) { // 保存原始值到_rawValue this._rawValue = toRaw(value); // 如果是对象(引用类型),使用reactive将对象转为响应式的(Proxy),因此将一个对象传入ref,实际上也是调用了reactive this._value = toReactive(value); } get value() { // 收集依赖 this.dep.track(); return this._value; } set value(newValue) { const oldValue = this._rawValue; newValue = toRaw(newValue); // 如果值改变,才会触发依赖 if (hasChanged(newValue, oldValue)) { // 更新值 this._rawValue = newValue; // 判断是否是对象,进行赋值 this._value = toReactive(newValue); // 派发通知 this.dep.trigger(); } } } /** * 实现 Vue3 的 proxyRefs(ref.value 自动脱壳) */ function proxyRefs(obj) { return new Proxy(obj, { get(target, key) { const val = target[key]; // 如果是 ref,就自动返回 .value return isRef(val) ? val.value : val; }, set(target, key, newVal) { const oldVal = target[key]; // 如果旧值是 ref,更新 .value,否则直接替换 if (isRef(oldVal)) { oldVal.value = newVal; return true; } else { return Reflect.set(target, key, newVal); } }, }); } function createRef(rawValue) { if (isRef(rawValue)) { return rawValue; } return new RefImpl(rawValue); } function ref(value) { return createRef(value); } function unref(ref) { return isRef(ref) ? ref.value : ref; } /** * 全局副作用调度队列 * 存储待执行的副作用函数 * 使用微任务进行统一调度,避免重复执行 */ const queue = []; /** 当前 flush 的 Promise 对象,用于防止重复调度 */ let currentFlushPromise = null; /** 当前正在执行队列的位置索引 */ let flushIndex = -1; /** * 调度队列执行 * 如果当前没有 flush 正在进行,则通过微任务调度 flushJobs */ function queueFlush() { if (!currentFlushPromise) { // 将 flushJobs 放入微任务队列,保证本轮事件循环结束后执行 currentFlushPromise = Promise.resolve().then(flushJobs); // 可选方案:宏任务(优先级低,会有延迟) // currentFlushPromise = setTimeout(flushJobs, 0) } } /** * 执行队列中的所有副作用任务 * @remarks * 1. 遍历 queue 执行每个 ReactiveEffect 的 run 方法 * 2. 执行完成后重置状态,清空队列 */ function flushJobs() { try { for (flushIndex = 0; flushIndex < queue.length; flushIndex++) { const job = queue[flushIndex]; if (job) { job(); } } } finally { // 重置索引,标记当前 flush 完成 flushIndex = -1; currentFlushPromise = null; queue.length = 0; // 清空队列 } } /** * 将一个副作用任务加入调度队列 * @param job - 待调度的 ReactiveEffect * @remarks * 1. 使用 includes 去重,避免同一 effect 在同一轮循环中重复执行 * 2. 调用 queueFlush,将 flushJobs 放入微任务队列 */ function queueJob(job) { if (!queue.includes(job)) { queue.push(job); queueFlush(); } } // 用于缓存已访问的对象,避免循环引用导致的无限递归 const seenObjects = new Set(); /** 用于递归地访问对象的所有嵌套属性 */ function traverse(value) { const res = _traverse(value, seenObjects); seenObjects.clear(); // 遍历完成后清空缓存 return res; } function _traverse(value, seen) { // 不是对象或是 null,直接返回 if (!isObject$1(value)) return value; // 如果已经访问过,直接返回,避免循环引用 if (seen.has(value)) return value; seen.add(value); if (Array.isArray(value)) { // 如果是数组,递归访问每一项 for (let i = 0; i < value.length; i++) { _traverse(value[i], seen); } } else { // 如果是对象,递归访问每个键的值 for (const key in value) { _traverse(value[key], seen); } } return value; } function doWatch(source, cb, options = {}) { let { deep = false, immediate = false } = options; let getter; // 解析 source if (isRef(source)) { // 如果是 ref,直接返回其值 getter = () => source.value; } else if (isReactive(source)) { // 如果是 reactive,对象进行深度递归 getter = () => source; // 强制深度监视 deep = true; } // 多个源:逐一解析 else if (Array.isArray(source)) { getter = () => source.map((s) => (isRef(s) ? s.value : isReactive(s) ? traverse(s) : s)); } else if (isFunction(source)) { // 如果是 getter 函数,直接使用 getter = source; } else { // NOPE getter = () => { }; } // 处理深度监视 if (deep) { const baseGetter = getter; getter = () => traverse(baseGetter()); } const INITIAL_WATCHER_VALUE = Symbol('INITIAL_WATCHER_VALUE'); let oldValue = INITIAL_WATCHER_VALUE; // 执行回调函数 const fn = () => { if (cb) { const newValue = getter(); if (deep || hasChanged(newValue, oldValue)) { if (immediate || oldValue !== INITIAL_WATCHER_VALUE) { cb(newValue, oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue); } oldValue = newValue; } } else { getter(); // 仅执行副作用,无回调 } }; // 创建 ReactiveEffect const effect = new ReactiveEffect(fn); // 加入异步调度,防止多次触发 const job = () => effect.run(); effect.scheduler = () => queueJob(job); // 激活依赖收集 effect.run(); return () => { effect.stop(); }; } function watch(source, cb, options) { return doWatch(source, cb, options); } function watchEffect(effect, options) { return doWatch(effect, null, options); } // TODO: 源码Vue 3.5+ 的 ComputedRefImpl 实际上本身是 Subscriber // 旧版 的 ComputedRefImpl 是一个特殊的 RefImpl class ComputedRefImpl { getter; setter; /** value 值 */ _value; /** 数据是否为脏数据(失效) */ _dirty = true; /** 本身作为依赖时,收集副作用 */ dep = new Dep(); /** 本身作为副作用时,被依赖收集 */ effect; /** ref标识 */ __v_isRef = true; constructor(getter, setter) { this.getter = getter; this.setter = setter; this.effect = new ReactiveEffect(() => { // 当依赖改变时,触发 if (!this._dirty) { // 标记为脏数据(需要重新计算) this._dirty = true; // TODO: 只标记,不触发,保持懒特性 this.dep.trigger(); } return getter(); }); // 启动该副作用 this._value = this.effect.run(); } get value() { if (this._dirty) { this._value = this.effect.run(); // 重新计算 this._dirty = false; // 重置脏标记 } this.dep.track(); return this._value; // 返回缓存的值 } set value(newValue) { if (this.setter) { this.setter(newValue); } else { console.warn('Write operation failed: computed value is readonly'); } } } function computed(getterOrOptions) { let getter; let setter; if (isFunction$1(getterOrOptions)) { getter = getterOrOptions; } else { getter = getterOrOptions.get; setter = getterOrOptions.set; } return new ComputedRefImpl(getter, setter); } export { ComputedRefImpl as ComputedRef, ReactiveEffect, RefImpl as Ref, activeEffect, batch, computed, effect, enableTracking, endBatch, hasChanged, isFunction, isObject, isProxy, isReactive, isReadonly, isRef, isShallow, pauseTracking, proxyRefs, queueJob, reactive, reactiveMap, ref, resetTracking, shouldTrack, startBatch, toRaw, toReactive, unref, watch, watchEffect }; //# sourceMappingURL=index.esm.js.map