目标
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 做的封装,不再赘述。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。