目标
vue3响应式源码解析
Vue3的数据响应与劫持是基于现代浏览器所支持的代理对象Proxy实现的。下面的例子简单说明vue3的响应式数据的原理,即通过
Proxy
对象分别对get
和set
劫持,在 取值和赋值中间 分别插?劫持的?法,即 track 和 trigger ——依赖的跟踪和副作?的触发。const initData = https://www.it610.com/article/{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 = https://www.it610.com/article/_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 = https://www.it610.com/article/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 = https://www.it610.com/article/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 做的封装,不再赘述。
参考文章 【Vue3响应式原理】Proxy