Vue3中的響應式實現原理
完整 js版本簡易源碼 在最底部
ref 與 reactive 是Vue3中的兩個定義響應式對象的API,其中reactive是通過 Proxy 來實現的,它返回對象的響應式副本,而Ref則是返回一個可變的ref對象,只有一個 .value屬性指向他內部的值,本文則重點來分析一下 Ref 的實現原理
ref:接受一個內部值並返回一個響應式且可變的 ref 對象。ref 對象僅有一個
.valueproperty,指向該內部值。

-
Ref依賴收集
首先我們需要了解 ReactiveEffect 類,創建這個類需要傳入一個 副作用函數 和 scheduler,在這個類中有 active、deps 兩個重要的屬性:
active:是否為激活狀態,默認為:true
deps:所有依賴這個 effect 的響應式對象
ReactiveEffect 簡易 js 版本源代碼:
// 記錄當前活躍的對象 let activeEffect // 標記是否追蹤 let shouldTrack = false class ReactiveEffect{ active = true // 是否為激活狀態 deps = [] // 所有依賴這個 effect 的響應式對象 onStop = null // function constructor(fn, scheduler) { this.fn = fn // 回調函數,如: computed(/* fn */() => { return testRef.value ++ }) // function類型,不為空那么在 TriggerRefValue 函數中會執行 effect.scheduler,否則會執行 effect.run this.scheduler = scheduler } run() { // 如果這個 effect 不需要被響應式對象收集 if(!this.active) { return this.fn() } // 源碼這里用了兩個工具函數:pauseTracking 和 enableTracking 來改變 shouldTrack的狀態 shouldTrack = true activeEffect = this // 在設置完 activeEffect 后執行,在方法中能夠對當前活躍的 activeEffect 進行依賴收集 const result = this.fn() shouldTrack = false // 執行完副作用函數后要清空當前活躍的 effect activeEffect = undefined return result } // 暫停追蹤 stop() { if (this.active) { // 找到所有依賴這個 effect 的響應式對象 // 從這些響應式對象里面把 effect 給刪除掉 cleanupEffect(this) // 執行onStop回調函數 if (this.onStop) { this.onStop(); } this.active = false; } } }了解了 ReactiveEffect 類,再來分析 Ref
ref 簡易JS版本源代碼
class RefImpl{ // Set格式,存儲 effect dep // 存儲原始值 _rawValue // 標記這是一個 Ref 對象,isRef API就可以直接通過判斷這個內部屬性即可 __v_isRef = true // _shallow:這個值是為了實現 shallowRef API 而存在 // 目前這里沒有使用到 constructor(value, _shallow) { this._value = value // 存儲原始值,用來與新值做對比 this._rawValue = _shallow ? value : toRaw(value) // _value 內部值 // toReactive => isObject(value) ? reactive(value) : value // 對 value 進行包裝 this._value = _shallow ? value : toReactive(value) } get value() { // 在獲取這個 Ref 內部值時 進行依賴收集 // trackRefValue() 依賴收集 trackRefValue(this) return this._value } }trackRefValue方法實現
/** * activeEffect 當前活躍的 effect 對象 * shouldTrack 是否允許追蹤 * const isTracking = () => activeEffect && shouldTrack */ function trackRefValue(ref) { // 若沒有活躍的 effect 對象或 不需要進行追蹤 if(!isTracking()) { return } // 如果這個 Ref 對象還沒有對 dep 進行初始化(Ref 中 dep 屬性默認為 undefined) if(!ref.dep) { ref.dep = new Set() } trackEffects(ref.dep) } function trackEffects(dep) { // 若改 effect 對象已經收集,跳過 if(!dep.has(activeEffect)) { // 將 effect 對象添加到 Ref 對象的 dep 中 dep.add(activeEffect) // 將這個Ref的dep存放到這個 effect 的 deps 中 // 目的是為了在停止追蹤時,從 響應式對象將 effect 移除掉 activeEffect.deps.push(dep) } }寫個 testComputed 函數來回顧 和 測試一下上面的流程
const testRef = new RefImpl(1) const testComputed = (fn) => { // 創建一個 effect 對象 const testEffect = new ReactiveEffect(fn) // 默認執行 effect.run() 函數(設置 activeEffect=this -> 執行 fn(依賴收集) -> 清空 activeEffect=undefined) return testEffect.run() // 此時,依賴情況: // testRef.dep = [ testEffect:effect ] // testEffect.deps = [ testRef.dep ] } const fn = () => { console.log('textComputed', testRef.value) } testComputed(fn) -
Ref 觸發更新
在 RefImpl 類中增加 set value 方法
class RefImpl{ // ...... // 增加 set value () 方法 set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) // 對比 新老值 是否相等,這里不能簡單的使用 == 或 === // Object.is() 方法判斷兩個值是否為同一個值。 if (!Object.is(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : toReactive(newVal) // 在內部值被改變的時候會觸發依賴更新 triggerRefValue(this) // ,newValue) 在dev環境下使用了 newValue,這里忽略 } } // ...... }triggerRefValue 方法實現
function triggerRefValue(ref) { triggerEffects(ref.dep); } function triggerEffects(dep) { // 執行 Ref.dep 中收集的所有 effect for (const effect of dep) { // 這里做個判斷,執行的 effect 不是 當前活躍的 effect if(effect !== activeEffect) { if(effect.scheduler) { // effect.scheduler 文章前面有講過 /** Vue3中模板更新,就是通過創建了一個 scheduler,然后推入 微任務隊列 中去執行的 const effect = new ReactiveEffect(componentUpdateFn,() => queueJob(instance.update)) const update = (instance.update = effect.run.bind(effect) as SchedulerJob) */ effect.scheduler() } else { effect.run() } } } }修改 testComputed 函數進行測試
const testRef = new RefImpl(1) const testComputedWatch = (fn) => { // 創建一個 effect 對象 const testEffect = new ReactiveEffect(fn) // 默認執行 effect.run() 函數(設置 activeEffect=this -> 執行 fn(依賴收集) -> 清空 activeEffect=undefined) return testEffect.run() // 此時,依賴情況: // testRef.dep = [ testEffect:effect ] // testEffect.deps = [ testRef.dep ] } const fn = () => { console.log('textComputed', testRef.value) } testComputed(fn) testRef.value ++ // -> 'textComputed', 2 testRef.value ++ // -> 'textComputed', 3源代碼:
effect.js
// 記錄當前活躍的對象 let activeEffect // 標記是否追蹤 let shouldTrack = false // 存儲已經收集的依賴 // const targetMap = new WeakMap() const isTracking = () => activeEffect && shouldTrack function trackEffects(dep) { if(!dep.has(activeEffect)) { dep.add(activeEffect) activeEffect.deps.push(dep) } } function triggerEffects(dep) { for (const effect of dep) { // 這里做個判斷,執行的 effect 不是 當前活躍的 effect if(effect !== activeEffect) { if(effect.scheduler) { effect.scheduler() } else { effect.run() } } } } class ReactiveEffect{ active = true deps = [] onStop = null constructor(fn, scheduler) { this.fn = fn this.scheduler = scheduler } run() { if(!this.active) { return this.fn() } // 源碼這里用了兩個工具函數:pauseTracking 和 enableTracking 來改變 shouldTrack的狀態 shouldTrack = true activeEffect = this // 在設置完 activeEffect 后執行,在方法中能夠對當前活躍的 activeEffect 進行依賴收集 const result = this.fn() shouldTrack = false // 執行完副作用函數后要清空當前活躍的 effect activeEffect = undefined return result } // 暫停追蹤 stop() { if (this.active) { // 找到所有依賴這個 effect 的響應式對象 // 從這些響應式對象里面把 effect 給刪除掉 cleanupEffect(this) // 執行onStop回調函數 if (this.onStop) { this.onStop(); } this.active = false; } } } function cleanupEffect(effect) { const { deps } = effect if (deps.length) { for (let i = 0; i < deps.length; i++) { deps[i].delete(effect) } deps.length = 0 } } module.exports = { ReactiveEffect, isTracking, trackEffects, triggerEffects }ref.js
// const { toReactive, toRaw } = require('./reactive.js') // start reactive.js 模塊 const isObject = (val) => val !== null && typeof val === 'object' const toReactive = val => isObject(val) ? reactive(val) : val function toRaw(observed) { // __v_raw 是一個標志位,表示這個是一個 reactive const raw = observed && observed['__v_raw'] return raw ? toRaw(raw) : observed } // end reactive.js const { ReactiveEffect, isTracking, trackEffects, triggerEffects } = require('./effect.js') function trackRefValue(ref) { if(!isTracking()) { return } if(!ref.dep) { ref.dep = new Set() } trackEffects(ref.dep) } function triggerRefValue(ref) { triggerEffects(ref.dep); } class RefImpl{ dep _rawValue __v_isRef = true constructor(value, _shallow) { this._value = value // 存儲原始值,用來與新值做對比 this._rawValue = _shallow ? value : toRaw(value) this._value = _shallow ? value : toReactive(value) } get value() { // 收集依賴 trackRefValue(this) return this._value } set value(newVal) { newVal = this._shallow ? newVal : toRaw(newVal) // Object.is() 方法判斷兩個值是否為同一個值。 if (!Object.is(newVal, this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : toReactive(newVal) triggerRefValue(this) // ,newValue) 在dev環境下使用了 newValue,這里忽略 } } } function isRef(r) { return Boolean(r && r.__v_isRef === true) } function createRef(rawValue, _shallow) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, _shallow) } function ref(val) { return createRef(val, false) } module.exports = { isRef, ref }測試代碼
/** ** 測試代碼 ***/ const testRef = ref(1) const textComputed = () => { const testEffect = new ReactiveEffect(() => { console.log('textComputed', testRef.value) }) testEffect.run() console.log('testEffect.deps', testEffect.deps) } const textComputed1 = () => { const fn = () => { console.log('textComputed', testRef.value) } const testEffect = new ReactiveEffect(fn) testEffect.run() } textComputed1() textComputed() console.log('testRef', testRef) // testRef.value ++
