Vue3响应式原理

目标 vue3响应式源码解析

Vue3的数据响应与劫持是基于现代浏览器所支持的代理对象Proxy实现的。
下面的例子简单说明vue3的响应式数据的原理,即通过Proxy对象分别对getset劫持,在 取值和赋值中间 分别插?劫持的?法,即 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 } } }

先用流程图简单的描述下上面代码的执行过程:
Vue3响应式原理
文章图片

组件初始化,首先声明countRef变量与执行effect函数。effect会调用传入的参数函数fnfn函数执行到countRef.value会被getter劫持,进行track。在这个过程中会有一个全局变量targetMap,将countReffn建立关联关系。当countRef的值发生变化时,会触发以下过程:
Vue3响应式原理
文章图片

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来实现tracktrigger。这也解释了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内部访问了countRefvalue属性
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 创建的数据,也有类似 的逻辑,区别就在于 Proxyhandler 部分:
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

    推荐阅读