@purevue/reactivity
Version:
## 📖 Introduction
1,011 lines (987 loc) • 33.8 kB
JavaScript
'use strict';
var shared = require('@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)
exports.activeEffect = null;
/**
* 是否应该收集依赖
* 某些阶段需暂停收集依赖,如:执行 setup 函数
*/
exports.shouldTrack = true;
const trackStack = [];
function pauseTracking() {
trackStack.push(exports.shouldTrack);
exports.shouldTrack = false;
}
function enableTracking() {
trackStack.push(exports.shouldTrack);
exports.shouldTrack = true;
}
function resetTracking() {
const last = trackStack.pop();
exports.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 = exports.activeEffect;
const prevShouldTrack = exports.shouldTrack;
exports.activeEffect = this;
exports.shouldTrack = true;
try {
// 执行副作用函数,并触发函数中响应性数据的依赖收集
return this.fn();
}
finally {
// 清除依赖
exports.activeEffect = prevEffect;
exports.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 (!exports.activeEffect || !exports.shouldTrack) {
return;
}
// 将当前 effect 添加到 dep.subs
if (!this.subs.has(exports.activeEffect)) {
debugInfo && console.log('[Dep track]', debugInfo);
this.subs.add(exports.activeEffect);
// 同时反向记录 dep 到 effect 的依赖表
if (!exports.activeEffect.deps.includes(this)) {
exports.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 !== exports.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 (!exports.activeEffect || !exports.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 = shared.isArray(target);
// 判断 key 是否为数组索引(整数键)
const isArrayIndex = targetIsArray && shared.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 (shared.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 (!shared.isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
// 处理新增的情况,如 obj.a = 'xx', arr.push('xx')
const hadKey = shared.isArray(target) && shared.isIntegerKey(key) ? Number(key) < target.length : shared.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 = shared.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 (!shared.isSymbol(key)) {
track(target, key);
}
return result;
}
/**
* 拦截 Object.getOwnPropertyKeys 等操作,返回目标对象自身的所有属性键。
* 同时进行依赖收集,用于追踪对象的遍历操作。
* @param target 目标对象
* @returns 目标对象的所有自身属性键组成的数组
*/
ownKeys(target) {
track(target, shared.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 (!shared.isObject(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 (shared.isFunction(getterOrOptions)) {
getter = getterOrOptions;
}
else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter);
}
exports.ComputedRef = ComputedRefImpl;
exports.ReactiveEffect = ReactiveEffect;
exports.Ref = RefImpl;
exports.batch = batch;
exports.computed = computed;
exports.effect = effect;
exports.enableTracking = enableTracking;
exports.endBatch = endBatch;
exports.hasChanged = hasChanged;
exports.isFunction = isFunction;
exports.isObject = isObject;
exports.isProxy = isProxy;
exports.isReactive = isReactive;
exports.isReadonly = isReadonly;
exports.isRef = isRef;
exports.isShallow = isShallow;
exports.pauseTracking = pauseTracking;
exports.proxyRefs = proxyRefs;
exports.queueJob = queueJob;
exports.reactive = reactive;
exports.reactiveMap = reactiveMap;
exports.ref = ref;
exports.resetTracking = resetTracking;
exports.startBatch = startBatch;
exports.toRaw = toRaw;
exports.toReactive = toReactive;
exports.unref = unref;
exports.watch = watch;
exports.watchEffect = watchEffect;
//# sourceMappingURL=index.cjs.js.map