指标
vue3 响应式源码解析
Vue3 的数据响应与劫持是基于古代浏览器所反对的代理对象 Proxy 实现的。
上面的例子简略阐明 vue3 的响应式数据的原理,即通过 Proxy
对象别离对 get
和set
劫持,在 取值和赋值两头 别离插⼊劫持的⽅法,即 track 和 trigger ——依赖的跟踪和副作⽤的触发。
const initData = {value: 1}
const proxy = new Proxy(
initData, // 被代理对象
{
// handler 对象
get(target, key) {
// 进行 track
return target[key]
}
set(target, key, value) {
// 进行 trigger
return Reflect.set(target, key, value);
}
}
)
// proxy 即 间接拜访批改对象
// 也能够称为响应式数据(reactive/ref)
根本阐明
track: 收集依赖
trigger: 触发副作用函数
代码剖析
通过上面的 vue3 实例代码运行,逐渐剖析源码。
// setup
import {ref, reactive, effect, computed} from "vue"
export default {
...
setup(props, context) {
const countRef = 0;
const number = reactive({num: 1})
effect(() => {console.log(countRef.value)
})
const increment = () => {countRef.value++}
const result = computed(() => number.num ** 2)
return {countRef, number, result, increment}
}
}
先用流程图简略的形容下下面代码的执行过程:
组件初始化,首先申明 countRef
变量与执行 effect
函数。effect
会调用传入的参数函数 fn
,fn
函数执行到 countRef.value
会被 getter
劫持,进行 track
。在这个过程中会有一个全局变量targetMap
, 将countRef
与fn
建设关联关系。当 countRef
的值发生变化时,会触发以下过程:
当 countRef
的值发生变化时,会被 setter
劫持,进行 trigger
。简略来讲就是从全局变量targetMap
来获取 countRef
对应的 fn
, 而后执行fn
函数的过程,当然这里省略了许多细节。
初始化阶段
- 创立响应式数据
const countRef = ref(0)
const number = reactive({num: 1})
function ref(value) {return createRef(value, false);
}
function createRef(rawValue, shallow) {
// 判断是否为 Ref 类型对象,如果是则间接返回
if (isRef(rawValue)) {return rawValue;}
return new RefImpl(rawValue, shallow);
}
createRef
函数判断传入参数 rawValue
是否为援用类型,如果不是,则返回 RefImpl
类包装的实例。
RefImpl
类
class RefImpl {constructor(value, _shallow) {
this._shallow = _shallow;
this.dep = undefined;
this.__v_isRef = true;
this._rawValue = _shallow ? value : toRaw(value);
this._value = _shallow ? value : convert(value);
}
get value() {trackRefValue(this); // track, 进行依赖收集
return this._value;
}
set value(newVal) {newVal = this._shallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : convert(newVal);
triggerRefValue(this, newVal); // trigger,值更改时触发更新
}
}
}
而 RefImpl
类,也是通过 get
/set
来实现 track
和trigger
。这也解释了 ref 的包装值 – 通过 value
属性进行拜访。基于同样思路来看 reactive
办法
function reactive(target) {
// if trying to observe a readonly proxy, return the readonly version.
// 如果指标对象的__v_isReadonly 属性为 true,间接返回原对象
if (target && target["__v_isReadonly" /* IS_READONLY */]) {return target;}
return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers, reactiveMap);
}
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys
};
const mutableCollectionHandlers = {get: /*#__PURE__*/ createInstrumentationGetter(false, false)
};
// 全局 map
const reactiveMap = new WeakMap();
// 主逻辑
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers, proxyMap) {if (!isObject(target)) {
{console.warn(`value cannot be made reactive: ${String(target)}`);
}
return target;
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (target["__v_raw" /* RAW */] &&
!(isReadonly && target["__v_isReactive" /* IS_REACTIVE */])) {return target;}
// 以下代码逻辑为重点:// target already has corresponding Proxy
const existingProxy = proxyMap.get(target);
if (existingProxy) {return existingProxy;}
// only a whitelist of value types can be observed.
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {return target;}
const proxy = new Proxy(target, targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers);
proxyMap.set(target, proxy);
return proxy;
}
createReactive
办法是主逻辑,⽽且创立浅层响应的⽅法 shallowReactive
,只读⽅法 readonly
都⽤到该函数。
这⾥能看到,target: {num: 1}
在此处被代理。如果之前曾经被代理过(proxyMap
中有缓存),则间接返 回,否则缓存起来并返回。reactive
⽅法使⽤了 Proxy
来实现代理。
数据追踪
effect
执行,并调用回调办法 fn
, 因为fn
外部拜访了 countRef
的value
属性
effect(() =>{console.log(countRef.value)
})
这里触发了类 RefImpl
定义的 get
办法
class RefImpl {get value() {trackRefValue(this);
return this._value;
}
}
// 这⾥有条件地使⽤ trackEffects 保护着 ref 实例属性 dep 与
// 沉闷的 effet 的映射,说人话就是:包装的数据在第一次被 effect 内
// fn 函数拜访时,包装对象顺便把这个 fn 也存了下来
function trackRefValue(ref) {if (isTracking()) {ref = toRaw(ref);
// 如果是第一次拜访,dep 属性为 undefined,则创立 dep 属性收集依赖
if (!ref.dep) {ref.dep = createDep();
} {
// 重点:触发副作用函数
trackEffects(ref.dep, {
target: ref,
type: "get" /* GET */ ,
key: 'value'
});
}
}
}
function trackEffects(dep, debuggerEventExtraInfo) {
let shouldTrack = false;
if (effectTrackDepth <= maxMarkerBits) {if (!newTracked(dep)) {
dep.n |= trackOpBit; // set newly tracked
shouldTrack = !wasTracked(dep);
}
} else {
// Full cleanup mode.
shouldTrack = !dep.has(activeEffect);
}
// activeEffect 是全局变量,在执⾏ effect 时会指向⼀个蕴含了 fn 的实例。// 换句话说,此处 dep.add(activeEffect)
// 等效于 ref.dep.add(wrapper(fn)),wrapper 是过程的简化
if (shouldTrack) {dep.add(activeEffect); // 做个标记 coordinate1
activeEffect.deps.push(dep);
if (activeEffect.onTrack) {
activeEffect.onTrack(
Object.assign(
{effect: activeEffect,},
debuggerEventExtraInfo
)
);
}
}
}
状态更新阶段
以 ref 创立的数据源为例,countRef.value++ 从下⾯开始
class RefImpl {set value(newVal) {newVal = this._shallow ? newVal : toRaw(newVal);
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : convert(newVal);
triggerRefValue(this, newVal); // 值得更新触发 trigger
}
}
}
function triggerRefValue(ref, newVal) {ref = toRaw(ref);
// 如果该 ref 没有 dep 属性,阐明之前没有触发依赖收集。if (ref.dep) { // 回到上⾯标记的地⽅ coordinate1
{
triggerEffects(ref.dep, {
target: ref,
type: "set" /* SET */,
key: "value",
newValue: newVal,
});
}
}
}
标记的地位证实包装值 ref(0) 通过 dep 对将来要执⾏的 fn 是存在引⽤关系的,⽽ triggerEffect ⽅法就 依据这个存在的关系,⼀旦 set 时就触发它!
function triggerEffects(dep, debuggerEventExtraInfo) {
// spread into array for stabilization
for (const effect of isArray(dep) ? dep : [...dep]) {if (effect !== activeEffect || effect.allowRecurse) {if (effect.onTrigger) {effect.onTrigger(extend({ effect}, debuggerEventExtraInfo));
}
if (effect.scheduler) {effect.scheduler(); // 这是 fn 在微工作队列中执⾏的地⽅
} else {effect.run(); // 这是 fn 同步执⾏的地⽅
}
}
}
}
在数据跟踪阶段,通过 activeEffect
把 ref 的 dep
属性将执行的 fn
关联起来,再到状态更新阶段,从新遍历执行 ref 的 def
关联的fn
. 缕清主线后,再略微关注⼀下 effect
的逻辑,就能把 scheduler,run 与 fn 联 系起来了:
function effect(fn, options) {if (fn.effect) {fn = fn.effect.fn;}
const _effect = new ReactiveEffect(fn);
if (options) {extend(_effect, options);
if (options.scope) recordEffectScope(_effect, options.scope);
}
// effect(fn)首次执行
if (!options || !options.lazy) {_effect.run();
}
const runner = _effect.run.bind(_effect);
runner.effect = _effect;
return runner;
}
这里传入的 fn
函数参数,被 ReactiveEffect
类包装成实例
const effectStack = [];
class ReactiveEffect {constructor(fn, scheduler = null, scope) {
this.fn = fn;
this.scheduler = scheduler;
this.active = true;
this.deps = [];
recordEffectScope(this, scope);
}
run() {if (!this.active) {return this.fn();
}
if (!effectStack.includes(this)) { // 实例不在栈中时执行,防止反复触发执行 fn
try {effectStack.push((activeEffect = this));
enableTracking();
trackOpBit = 1 << ++effectTrackDepth;
if (effectTrackDepth <= maxMarkerBits) {initDepMarkers(this);
} else {cleanupEffect(this);
}
return this.fn();} finally {if (effectTrackDepth <= maxMarkerBits) {finalizeDepMarkers(this);
}
trackOpBit = 1 << --effectTrackDepth;
resetTracking();
effectStack.pop();
const n = effectStack.length;
activeEffect = n > 0 ? effectStack[n - 1] : undefined;
}
}
}
stop() {if (this.active) {cleanupEffect(this);
if (this.onStop) {this.onStop();
}
this.active = false;
}
}
}
上⾯的 ref ⽅法创立数据与更新的⼀整套流程,其实 reactive 创立的数据,也有相似 的逻辑,区别就在于 Proxy
的 handler
局部:
const proxy = new Proxy(
target,
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
);
以 baseHandlers
为例(这⾥是形参),找到实参 mutableHandlers
,
const mutableHandlers = {
get,
set,
deleteProperty,
has,
ownKeys,
};
// 咱们能够判定,这⾥的 get/set 就是进⾏ track 和 trigger 的地⽅。找到它
const get = /*#__PURE__*/ createGetter();
function createGetter(isReadonly = false, shallow = false) {return function get(target, key, receiver) {if (key === "__v_isReactive" /* IS_REACTIVE */) {return !isReadonly;} else if (key === "__v_isReadonly" /* IS_READONLY */) {return isReadonly;} else if (
key === "__v_raw" /* RAW */ &&
receiver ===
(isReadonly
? shallow
? shallowReadonlyMap
: readonlyMap
: shallow
? shallowReactiveMap
: reactiveMap
).get(target)
) {return target;}
const targetIsArray = isArray(target);
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// arrayInstrumentations 内也有 track,不再展现,关注主线
return Reflect.get(arrayInstrumentations, key, receiver);
}
const res = Reflect.get(target, key, receiver);
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {return res;}
if (!isReadonly) {track(target, "get" /* GET */, key); // 呈现了与 ref 拦挡⼀样的逻辑
}
...
return res;
};
}
function track(target, type, key) {if (!isTracking()) {return;}
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()));
}
const eventInfo = {effect: activeEffect, target, type, key};
trackEffects(dep, eventInfo); // 与 trackRefValue 必由之路,略
}
- set
const set = /*#__PURE__*/ createSetter()
function createSetter(shallow = false) {return function set(target, key, value, receiver) {let oldValue = target[key];
if (!shallow) {value = toRaw(value);
oldValue = toRaw(oldValue);
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value;
return true;
}
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key);
const result = Reflect.set(target, key, value, receiver);
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {if (!hadKey) {trigger(target, "add" /* ADD */, key, value); // 与 ref 一样触发 trigger
} else if (hasChanged(value, oldValue)) {trigger(target, "set" /* SET */, key, value, oldValue);
}
}
return result;
};
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {const depsMap = targetMap.get(target);
if (!depsMap) {
// never been tracked
return;
}
let deps = [];
if (type === "clear" /* CLEAR */) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()];
} else if (key === "length" && isArray(target)) {depsMap.forEach((dep, key) => {if (key === "length" || key >= newValue) {deps.push(dep);
}
});
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {deps.push(depsMap.get(key));
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case "add" /* 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)) {
// new index added to array -> length changes
deps.push(depsMap.get("length"));
}
break;
case "delete" /* DELETE */:
if (!isArray(target)) {deps.push(depsMap.get(ITERATE_KEY));
if (isMap(target)) {deps.push(depsMap.get(MAP_KEY_ITERATE_KEY));
}
}
break;
case "set" /* SET */:
if (isMap(target)) {deps.push(depsMap.get(ITERATE_KEY));
}
break;
}
}
function trigger(target, type, key, newValue, oldValue, oldTarget) {
...
const eventInfo = {target, type, key, newValue, oldValue, oldTarget};
if (deps.length === 1) {if (deps[0]) {
{triggerEffects(deps[0], eventInfo); // 与 triggerRefValue 必由之路,略
}
}
} else {const effects = [];
for (const dep of deps) {if (dep) {effects.push(...dep);
}
}
{triggerEffects(createDep(effects), eventInfo);
}
}
}
其实 watch ⽅法,也是基于 effect 做的封装,不再赘述。
参考文章
Proxy