W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗值獎勵
現(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)性原理的視頻。
這個術(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)這一點呢?
當(dāng)把一個普通的 JavaScript 對象作為 data
選項傳給應(yīng)用或組件實例的時候,Vue 會使用帶有 getter 和 setter 的處理程序遍歷其所有 property 并將其轉(zhuǎn)換為 Proxy。這是 ES6 僅有的特性,但是我們在 Vue 3 版本也使用了 Object.defineProperty
來支持 IE 瀏覽器。兩者具有相同的 Surface API,但是 Proxy 版本更精簡,同時提升了性能。
該部分需要稍微地了解下 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ù)可以傳入 target
和 key
兩個參數(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 如何處理這些更改的答案:
effect
trigger
Proxy 對象對于用戶來說是不可見的,但是在內(nèi)部,它們使 Vue 能夠在 property 的值被訪問或修改的情況下進行依賴跟蹤和變更通知。從 Vue 3 開始,我們的響應(yīng)性現(xiàn)在可以在獨立的包中使用。需要注意的是,記錄轉(zhuǎn)換后的數(shù)據(jù)對象時,瀏覽器控制臺輸出的格式會有所不同,因此你可能需要安裝 vue-devtools,以提供一種更易于檢查的界面。
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 的使用確實引入了一個需要注意的新警告:在身份比較方面,被代理對象與原始對象不相等 (===
)。例如:
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 時,它會通知偵聽器,從而使得組件重新渲染。
將對象作為數(shù)據(jù)傳遞給組件實例時,Vue 會將其轉(zhuǎn)換為 Proxy。這個 Proxy 使 Vue 能夠在 property 被訪問或修改時執(zhí)行依賴項跟蹤和更改通知。每個 property 都被視為一個依賴項。
首次渲染后,組件將跟蹤一組依賴列表——即在渲染過程中被訪問的 property。反過來,組件就成為了其每個 property 的訂閱者。當(dāng) Proxy 攔截到 set 操作時,該 property 將通知其所有訂閱的組件重新渲染。
如果你使用的是 Vue2.x 及以下版本,你可能會對這些版本中存在的一些更改檢測警告感興趣,在這里進行更詳細(xì)的探討。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報電話:173-0602-2364|舉報郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: