[學習vue3]響應式原理[二]

響應式原理V2 vs V3

vue2的方式使用Object.defineProperty(),攔截每一個key,需要遞歸遍歷對象所有key,速度慢,數組響應式需要額外實現'push', 'pop', 'shift', 'unshift', 'splice', 'reverse', 'sort',新增或刪除屬性無法監聽,需要使用特殊api,不支持Map、Set、Class等數據結構。

vue3的方式使用Proxy代理整個對象,從而偵查數據變化。

造個輪子

首先實現reactive(obj),借助Proxy代理傳入的obj,這樣可以攔截對obj的各種訪問;

function reactive(obj) {
    if (typeof obj !== 'object' && obj != null) {
        return obj
    }
    // Proxy相當于在對象外層加攔截
    // http://es6.ruanyifeng.com/#docs/proxy const observed = new Proxy(obj, baseHandler) return observed
}

下面是依賴收集的實現,原理如下圖:


相關api有

  • effect(fn):傳入fn,返回的函數將是響應式的,內部代理的數據發生變化,它會再次執行
  • track(target, key):建立響應式函數與其訪問的目標(target)和鍵(key)之間的映射關系
  • trigger(target, key):根據track()建立的映射關系,找到對應響應式函數并執行它

基本結構:

// 臨時存儲響應式函數 const effectStack = []
// 將傳入fn轉換為一個響應式函數
function effect(fn, options = {}) { }
// 存放響應式函數和目標、鍵之間的映射關系 const targetMap = new WeakMap()
// 依賴收集,創建映射關系
function track(target, key) { }
// 根據映射關系獲取響應函數
function trigger(target, key) { }

實現effect()/track()/trigger()

function effect(fn, options = {}) {
    // 創建reactiveEffect
    const e = createReactiveEffect(fn, options)
    // 執行一次觸發依賴收集 
    e()
    return e
}
function createReactiveEffect(fn, options) {
    // 封裝一個高階函數,除了執行fn,還要將自己放入effectStack為依賴收集做準備 const effect = function reactiveEffect(...args) {
    if (!effectStack.includes(effect)) {
        try {
            // 1.effect入棧 effectStack.push(effect) // 2.執行fn
            return fn(...args)
        } finally {
            // 3.effect出棧 effectStack.pop()
        }
    }

    return effect
}
function track(target, key) {
    // 獲取響應式函數
    const effect = effectStack[effectStack.length - 1]
    if (effect) {
        // 獲取target映射關系map,不存在則創建 
        let depMap = targetMap.get(target)
        if (!depMap) {
            depMap = new Map()
            targetMap.set(target, depMap)
        }
        // 獲取key對應依賴集合,不存在則創建 
        let deps = depMap.get(key)
        if (!deps) {
            deps = new Set()
            depMap.set(key, deps)
        }
        // 將響應函數添加到依賴集合

        deps.add(effect)
    }
}
function trigger(target, key) {
    // 獲取target對應依賴map
    const depMap = targetMap.get(target)
    if (!depMap) {
        return
    }
    // 獲取key對應集合
    const deps = depMap.get(key)
    if (deps) {
        // 執行所有響應函數
        deps.forEach(dep => dep())
    }
}
// 測試
const state = reactive({ foo: 'foo' })
effect(() => {
    console.log('effect', state.foo);
})

結合視圖驗證一下

<script src="03-reactivity.js"></script>
<script>
    const obj = { name: 'kkb', age: 8 }
    const data = reactive(obj)
    // effect()定義我們的更新函數 
    effect(() => {
        app.innerHTML = ` <h1>${data.name}今年${data.age}歲了</h1>`
    })
    // 修改一下數值 
    setInterval(() => {
        data.age++
    }, 1000);
</script>

計算屬性也很常用,可以基于effect實現

const double = computed(() => data.age * 2)
// effect()定義我們的更新函數
effect(() => {
    app.innerHTML = ` <h1>${data.name}今年${data.age}歲了</h1> <p>乘以2是${double.value}歲</p>`
})

computed(fn):可以使傳入fn使之成為響應式函數,fn內部依賴的數值發生變化,該函數應該重新執行 獲得最新的計算結果。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容