指标
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实例代码运行,逐渐剖析源码。
// setupimport {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)};// 全局mapconst 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