1. 程式人生 > 其它 >Vue3 響應式原理

Vue3 響應式原理

Vue3中響應式模組是如何工作的呢?
比如三個屬性:價格price,數量quantity ,總價格total 。

let price = 5
let quantity = 2
let total = price * quantity

我們想要做到響應式,即更新price 時,網頁上的price 隨之更新,而計算後的total 也隨之更新。

定義一個計算函式:

function getTotal (price, quantity ){
  let total = price * quantity 
}

我們想要在更新了price 後,再次執行這個 getTotal 函式,這樣就能得到一個響應式的 total。



在Vue3中,使用了多種資料結構來執行整個機制,例如:

  • 定義 track 用於儲存這段函式程式碼。
  • 定義 effect 用於執行程式碼。
  • 定義 trigger 用於執行所有儲存的程式碼。

首先將 上面的 getTotal 用匿名函式的方式寫入effect:

let effect = () => {
    total = price * quantity
}

用一個集合 dep 來儲存所有的effect。

let dep = new Set()

track 作為函式,往集合dep 裡新增effect :
function track() {
    dep.add(effct)
}

trigger 作為函式,作用是遍歷 dep 中所有 effect ,並逐一執行。

function trigger() {
    dep.forEach( effct => effct() )
}


單個響應式物件

通常來說,一個物件有多個property ,例如上面提到的price和quantity。

let product = { price : 5, quantity : 2  }

我們想讓每個property 都擁有自己的dep ,這樣無論修改哪個屬性,total都可以變得“響應式”。


可以定義一個dep map (ES6 map), map 的 key 是屬性的名字,比如price 或者quantity 。map 的 value 是一個dep ,即一個effect 集合。

track函式和trigger函式需要改寫成:

const depMap = new Map()

function track(key) {
    let dep = depMap.get(key)   // 獲取property的dep
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //如果不存在dep,則建立一個
    }
    dep.add(effect)   // 往集合中新增effect
}

function trigger(key) {
    let dep = depMap.get(key)   // 獲取property的dep
    if(dep) {				
        dep.forEach(effect => {   // dep存在,則遍歷dep中的所有effect
            effect()
        })
    }
}

現在彙總所有的程式碼,讓我們看看響應式是否生效:

let product = { price : 5, quantity : 2 }
let total = 0

let effect = () => {
    total = product.price * product.quantity
}

const depMap = new Map()

function track(key) {
    let dep = depMap.get(key)   // 獲取property的dep
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //如果不存在dep,則建立一個
    }
    dep.add(effect)   // 往集合中新增effect
}

function trigger(key) {
    let dep = depMap.get(key)   // 獲取property的dep
    if(dep) {				
        dep.forEach(effect => {   // dep存在,則遍歷dep中的所有effect
            effect()
        })
    }
}

track('quantity')
effect()
console.log(total)   //10

product.quantity = 3
trigger('quantity')
console.log(total)    //15

你也可以複製這段程式碼到控制檯,測試執行結果,筆者測試成功。


很好!!!現在我們就得到了一個響應式的物件product了,修改product 的任何屬性,只要呼叫以上方法,total都會發生變化。



多個響應式物件

但上述程式碼中的depMap只是存放了單個物件product的屬性及dep,如果現在我們需要多個響應式的物件呢?比如一個user物件:

let user = {
    name : 'LUJT',
    age  : 19
}

既然一個depMap只能處理一個響應式物件,如果存在多個物件,一種顯而易見的思路是,再定義一個Map,key為某個物件,value為一個depMap。


在此處我們使用WeakMap。MDN中關於WeakMap的定義是:

WeakMap 物件是一組鍵/值對的集合,其中的鍵是弱引用的。其鍵必須是物件,而值可以是任意的。


非常符合我們的需要!因此我們定義一個WeakMap如下:

const targetMap = new WeakMap

我們的核心程式碼可以改寫為:

const targetMap = new WeakMap   //targetMap用於儲存物件及其depMap

function track(target, key) {
    let depMap = targetMap.get(target)   // 獲取物件的depMap
   	if(!depMap) {
        targetMap.set(target, (depMap = new Map()))   //如果不存在depMap,則建立一個
    }
   
    let dep = depMap.get(key)   // 獲取property的dep
    
    if(!dep) {
        depMap.set(key, (dep = new Set()))   //如果不存在dep,則建立一個
    }
    
    dep.add(effect)   // 往集合中新增effect
}

function trigger(target, key) {
    const depMap = targetMap.get(target)
    if(!depMap){return}
    
    let dep = depMap.get(key)   // 獲取property的dep
    if(dep) {				
        dep.forEach(effect => {   // dep存在,則遍歷dep中的所有effect
            effect()
        })
    }
}

如此,我們設定多個物件的響應式工作就完成了。


總結

最後總結一下筆者對“響應式”的理解:

所謂響應式,就是對某個東西的區域性進行修改時,整體也隨之而變化。

在Web開發中,響應式設計為我們提供了一種“動態變化”的特性。

無論是整個網頁大小的變化,導致整個網頁佈局的更新,還是Vue中物件屬性的改動,導致物件整體的變化,都是一種“響應式”。


Vue3用effect函式存放動態變化的執行條件

用dep集合儲存多個effect

用depMap儲存物件的屬性和dep

用targetMap存放物件和depMap