Vue 3.0 響應(yīng)性 深入響應(yīng)性原理

2021-07-16 11:42 更新

現(xiàn)在是時候深入了!Vue 最獨特的特性之一,是其非侵入性的響應(yīng)性系統(tǒng)。數(shù)據(jù)模型是被代理的 JavaScript 對象。而當(dāng)你修改它們時,視圖會進行更新。這讓狀態(tài)管理非常簡單直觀,不過理解其工作原理同樣重要,這樣你可以避開一些常見的問題。在這個章節(jié),我們將研究一下 Vue 響應(yīng)性系統(tǒng)的底層的細(xì)節(jié)。

在 Vue Mastery 上免費觀看關(guān)于深入響應(yīng)性原理的視頻。

#什么是響應(yīng)性

這個術(shù)語在程序設(shè)計中經(jīng)常被提及,但這是什么意思呢?響應(yīng)性是一種允許我們以聲明式的方式去適應(yīng)變化的一種編程范例。人們通常展示的典型例子,是一份 excel 電子表格 (一個非常好的例子)。

點擊此處看視頻

如果將數(shù)字 2 放在第一個單元格中,將數(shù)字 3 放在第二個單元格中并要求提供 SUM,則電子表格會將其計算出來給你。不要驚奇,同時,如果你更新第一個數(shù)字,SUM 也會自動更新。

JavaScript 通常不是這樣工作的——如果我們想用 JavaScript 編寫類似的內(nèi)容:

var val1 = 2
var val2 = 3
var sum = val1 + val2


// sum
// 5


val1 = 3


// sum
// 5

如果我們更新第一個值,sum 不會被修改。

那么我們?nèi)绾斡?JavaScript 實現(xiàn)這一點呢?

  • 檢測其中某一個值是否發(fā)生變化
  • 用跟蹤 (track) 函數(shù)修改值
  • 用觸發(fā) (trigger) 函數(shù)更新為最新的值

#Vue 如何追蹤變化?

當(dāng)把一個普通的 JavaScript 對象作為 data 選項傳給應(yīng)用或組件實例的時候,Vue 會使用帶有 getter 和 setter 的處理程序遍歷其所有 property 并將其轉(zhuǎn)換為 Proxy。這是 ES6 僅有的特性,但是我們在 Vue 3 版本也使用了 Object.defineProperty 來支持 IE 瀏覽器。兩者具有相同的 Surface API,但是 Proxy 版本更精簡,同時提升了性能。

點擊此處實現(xiàn)

該部分需要稍微地了解下 Proxy 的某些知識!所以,讓我們深入了解一下。關(guān)于 Proxy 的文獻很多,但是你真正需要知道的是 Proxy 是一個包含另一個對象或函數(shù)并允許你對其進行攔截的對象。

我們是這樣使用它的:new Proxy(target, handler)

const dinner = {
  meal: 'tacos'
}


const handler = {
  get(target, prop) {
    return target[prop]
  }
}


const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)


// tacos

好的,到目前為止,我們只是包裝這個對象并返回它。很酷,但還不是那么有用。請注意,我們把對象包裝在 Proxy 里的同時可以對其進行攔截。這種攔截被稱為陷阱。

const dinner = {
  meal: 'tacos'
}


const handler = {
  get(target, prop) {
    console.log('intercepted!')
    return target[prop]
  }
}


const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)


// tacos

除了控制臺日志,我們可以在這里做任何我們想做的事情。如果我們愿意,我們甚至可以不返回實際值。這就是為什么 Proxy 對于創(chuàng)建 API 如此強大。

此外,Proxy 還提供了另一個特性。我們不必像這樣返回值:target[prop],而是可以進一步使用一個名為 Reflect 的方法,它允許我們正確地執(zhí)行 this 綁定,就像這樣:

const dinner = {
  meal: 'tacos'
}


const handler = {
  get(target, prop, receiver) {
    return Reflect.get(...arguments)
  }
}


const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)


// tacos

我們之前提到過,為了有一個 API 能夠在某些內(nèi)容發(fā)生變化時更新最終值,我們必須在內(nèi)容發(fā)生變化時設(shè)置新的值。我們在處理器,一個名為 track 的函數(shù)中執(zhí)行此操作,該函數(shù)可以傳入 targetkey 兩個參數(shù)。

const dinner = {
  meal: 'tacos'
}


const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    return Reflect.get(...arguments)
  }
}


const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)


// tacos

最后,當(dāng)某些內(nèi)容發(fā)生改變時我們會設(shè)置新的值。為此,我們將通過觸發(fā)這些更改來設(shè)置新 Proxy 的更改:

const dinner = {
  meal: 'tacos'
}


const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    return Reflect.get(...arguments)
  },
  set(target, key, value, receiver) {
    trigger(target, key)
    return Reflect.set(...arguments)
  }
}


const proxy = new Proxy(dinner, handler)
console.log(proxy.meal)


// tacos

還記得幾段前的列表嗎?現(xiàn)在我們有了一些關(guān)于 Vue 如何處理這些更改的答案:

  • 當(dāng)某個值發(fā)生變化時進行檢測:我們不再需要這樣做,因為 Proxy 允許我們攔截它
  • 跟蹤更改它的函數(shù):我們在 Proxy 中的 getter 中執(zhí)行此操作,稱為 effect
  • 觸發(fā)函數(shù)以便它可以更新最終值:我們在 Proxy 中的 setter 中進行該操作,名為 trigger

Proxy 對象對于用戶來說是不可見的,但是在內(nèi)部,它們使 Vue 能夠在 property 的值被訪問或修改的情況下進行依賴跟蹤和變更通知。從 Vue 3 開始,我們的響應(yīng)性現(xiàn)在可以在獨立的包中使用。需要注意的是,記錄轉(zhuǎn)換后的數(shù)據(jù)對象時,瀏覽器控制臺輸出的格式會有所不同,因此你可能需要安裝 vue-devtools,以提供一種更易于檢查的界面。

#Proxy 對象

Vue 在內(nèi)部跟蹤所有已被設(shè)置為響應(yīng)式的對象,因此它始終會返回同一個對象的 Proxy 版本。

從響應(yīng)式 Proxy 訪問嵌套對象時,該對象在返回之前被轉(zhuǎn)換為 Proxy:

const handler = {
  get(target, prop, receiver) {
    track(target, prop)
    const value = Reflect.get(...arguments)
    if (isObject(value)) {
      return reactive(value)
    } else {
      return value
    }
  }
  // ...
}

#Proxy vs 原始標(biāo)識

Proxy 的使用確實引入了一個需要注意的新警告:在身份比較方面,被代理對象與原始對象不相等 (===)。例如:

const obj = {}
const wrapped = new Proxy(obj, handlers)


console.log(obj === wrapped) // false

在大多數(shù)情況下,原始版本和包裝版本的行為相同,但請注意,它們在依賴嚴(yán)格比對的操作下將是失敗的,例如 .filter().map()。使用選項式 API 時,這種警告不太可能出現(xiàn),因為所有響應(yīng)式都是從 this 訪問的,并保證已經(jīng)是 Proxy。

但是,當(dāng)使用合成 API 顯式創(chuàng)建響應(yīng)式對象時,最佳做法是不要保留對原始對象的引用,而只使用響應(yīng)式版本:

const obj = reactive({
  count: 0
}) // no reference to original

#偵聽器

每個組件實例都有一個相應(yīng)的偵聽器實例,該實例將在組件渲染期間把“觸碰”的所有 property 記錄為依賴項。之后,當(dāng)觸發(fā)依賴項的 setter 時,它會通知偵聽器,從而使得組件重新渲染。

點擊此處實現(xiàn)

將對象作為數(shù)據(jù)傳遞給組件實例時,Vue 會將其轉(zhuǎn)換為 Proxy。這個 Proxy 使 Vue 能夠在 property 被訪問或修改時執(zhí)行依賴項跟蹤和更改通知。每個 property 都被視為一個依賴項。

首次渲染后,組件將跟蹤一組依賴列表——即在渲染過程中被訪問的 property。反過來,組件就成為了其每個 property 的訂閱者。當(dāng) Proxy 攔截到 set 操作時,該 property 將通知其所有訂閱的組件重新渲染。

如果你使用的是 Vue2.x 及以下版本,你可能會對這些版本中存在的一些更改檢測警告感興趣,在這里進行更詳細(xì)的探討。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號