如何在Vue中使localStorage具有響應(yīng)式(思想實驗)
響應(yīng)式是Vue.js的最大特色之一。如果你不知道幕后情況,它也是最神秘的地方之一。例如,為什么它不能用于對象和數(shù)組,而不能用于諸如 localStorage 之類的其他東西?

讓我們回答這個問題,在解決這個問題時,讓Vue響應(yīng)式與 localStorage 一起使用。
如果運行以下代碼,則會看到計數(shù)器顯示為靜態(tài)值,并且不會像我們期望的那樣發(fā)生變化,這是因為setInterval在 localStorage 中更改了該值。
new Vue({
el: "#counter",
data: () => ({
counter: localStorage.getItem("counter")
}),
computed: {
even() {
return this.counter % 2 == 0;
}
},
template: `<div>
<div>Counter: {{ counter }}</div>
<div>Counter is {{ even ? 'even' : 'odd' }}</div>
</div>`
});
// some-other-file.js
setInterval(() => {
const counter = localStorage.getItem("counter");
localStorage.setItem("counter", +counter + 1);
}, 1000);
盡管Vue.js實例中的 counter 屬性是響應(yīng)式的,但它不會因為我們更改了它在 localStorage 中的來源而更改。
有多種解決方案,最好的也許是使用Vuex,并保持存儲值與 localStorage 同步。但如果我們需要像本例中那樣簡單的東西呢?我們要深入了解一下Vue.js的響應(yīng)式系統(tǒng)是如何工作的。
Vue 中的響應(yīng)式
當Vue初始化組件實例時,它將觀察data選項。這意味著它將遍歷數(shù)據(jù)中的所有屬性,并使用 Object.defineProperty 將它們轉(zhuǎn)換為getter/setter。通過為每個屬性設(shè)置自定義設(shè)置器,Vue可以知道屬性何時發(fā)生更改,并且可以通知需要對更改做出反應(yīng)的依賴者。它如何知道哪些依賴者依賴于一個屬性?通過接入getters,它可以在計算的屬性、觀察者函數(shù)或渲染函數(shù)訪問數(shù)據(jù)屬性時進行注冊。
// core/instance/state.js
function initData () {
// ...
observe(data)
}
// core/observer/index.js
export function observe (value) {
// ...
new Observer(value)
// ...
}
export class Observer {
// ...
constructor (value) {
// ...
this.walk(value)
}
walk (obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
export function defineReactive (obj, key, ...) {
const dep = new Dep()
// ...
Object.defineProperty(obj, key, {
// ...
get() {
// ...
dep.depend()
// ...
},
set(newVal) {
// ...
dep.notify()
}
})
}
所以,為什么 localStorage 不響應(yīng)?因為它不是具有屬性的對象。
但是等一下,我們也不能用數(shù)組定義getter和setter,但Vue中的數(shù)組仍然是反應(yīng)式的。這是因為數(shù)組在Vue中是一種特殊情況。為了擁有響應(yīng)式的數(shù)組,Vue在后臺重寫了數(shù)組方法,并與Vue的響應(yīng)式系統(tǒng)進行了修補。
我們可以對 localStorage 做類似的事情嗎?
覆蓋localStorage函數(shù)
首先嘗試通過覆蓋localStorage方法來修復(fù)最初的示例,以跟蹤哪些組件實例請求了localStorage項目。
// LocalStorage項目鍵與依賴它的Vue實例列表之間的映射。
const storeItemSubscribers = {};
const getItem = window.localStorage.getItem;
localStorage.getItem = (key, target) => {
console.info("Getting", key);
// 收集依賴的Vue實例
if (!storeItemSubscribers[key]) storeItemSubscribers[key] = [];
if (target) storeItemSubscribers[key].push(target);
// 調(diào)用原始函數(shù)
return getItem.call(localStorage, key);
};
const setItem = window.localStorage.setItem;
localStorage.setItem = (key, value) => {
console.info("Setting", key, value);
// 更新相關(guān)Vue實例中的值
if (storeItemSubscribers[key]) {
storeItemSubscribers[key].forEach((dep) => {
if (dep.hasOwnProperty(key)) dep[key] = value;
});
}
// 調(diào)用原始函數(shù)
setItem.call(localStorage, key, value);
};
new Vue({
el: "#counter",
data: function() {
return {
counter: localStorage.getItem("counter", this) // 我們現(xiàn)在需要傳遞“this”
}
},
computed: {
even() {
return this.counter % 2 == 0;
}
},
template: `<div>
<div>Counter: {{ counter }}</div>
<div>Counter is {{ even ? 'even' : 'odd' }}</div>
</div>`
});
setInterval(() => {
const counter = localStorage.getItem("counter");
localStorage.setItem("counter", +counter + 1);
}, 1000);
在這個例子中,我們重新定義了 getItem 和 setItem,以便收集和通知依賴 localStorage 項目的組件。在新的 getItem 中,我們注意到哪個組件請求了哪個項目,在 setItems 中,我們聯(lián)系所有請求該項目的組件,并重寫它們的數(shù)據(jù)屬性。
為了使上面的代碼工作,我們必須向 getItem 傳遞一個對組件實例的引用,這就改變了它的函數(shù)簽名。我們也不能再使用箭頭函數(shù)了,因為否則我們就不會有正確的 this 值。
如果我們想做得更好,就必須更深入地挖掘。例如,我們?nèi)绾卧诓伙@式傳遞依賴者的情況下跟蹤它們?
Vue如何收集依賴關(guān)系
為了獲得啟發(fā),我們可以回到Vue的響應(yīng)式系統(tǒng)。我們之前曾看到,訪問數(shù)據(jù)屬性時,數(shù)據(jù)屬性的 getter 將使調(diào)用者訂閱該屬性的進一步更改。但是它怎么知道是誰做的調(diào)用呢?當我們得到一個數(shù)據(jù)屬性時,它的 getter 函數(shù)沒有任何關(guān)于調(diào)用者是誰的輸入。Getter函數(shù)沒有輸入,它怎么知道誰要注冊為依賴者呢?
每個數(shù)據(jù)屬性維護一個需要在Dep類中進行響應(yīng)的依賴項列表。如果我們在此類中進行更深入的研究,可以看到只要在注冊依賴項時就已經(jīng)在靜態(tài)目標變量中定義了依賴項。這個目標是由一個非常神秘的Watche類確定的。實際上,當數(shù)據(jù)屬性更改時,將實際通知這些觀察程序,并且它們將啟動組件的重新渲染或計算屬性的重新計算。
但是,他們又是誰?
當Vue使 data 選項可觀察時,它還會為每個計算出的屬性函數(shù)以及所有watch函數(shù)(不應(yīng)與Watcher類混為一談)以及每個組件實例的render函數(shù)創(chuàng)建watcher。觀察者就像這些函數(shù)的伴侶。他們主要做兩件事:
- 當它們被創(chuàng)建時,它們會評估函數(shù)。這將觸發(fā)依賴關(guān)系的集合。
- 當他們被通知他們所依賴的一個值發(fā)生變化時,他們會重新運行他們的函數(shù)。這將最終重新計算一個計算出的屬性或重新渲染整個組件。
在觀察者調(diào)用其負責的函數(shù)之前,有一個重要的步驟發(fā)生了:他們將自己設(shè)置為Dep類中靜態(tài)變量的目標。這樣可以確保在訪問響應(yīng)式數(shù)據(jù)屬性時將它們注冊為從屬。
追蹤誰調(diào)用了localStorage
我們無法完全做到這一點,因為我們無法使用Vue的內(nèi)部機制。但是,我們可以使用Vue的想法,即觀察者可以在調(diào)用其負責的函數(shù)之前,將目標設(shè)置為靜態(tài)屬性。我們能否在調(diào)用 localStorage 之前設(shè)置對組件實例的引用?
如果我們假設(shè)在設(shè)置 data 選項時調(diào)用了 localStorage,則可以將其插入 beforeCreate 和 created 中。這兩個掛鉤在初始化data選項之前和之后都會被觸發(fā),因此我們可以設(shè)置一個目標變量,然后清除該變量,并引用當前組件實例(我們可以在生命周期掛鉤中訪問該實例)。然后,在我們的自定義獲取器中,我們可以將該目標注冊為依賴項。
我們要做的最后一點是使這些生命周期掛鉤成為我們所有組件的一部分,我們可以通過整個項目的全局混合來做到這一點。
// LocalStorage項目鍵與依賴它的Vue實例列表之間的映射
const storeItemSubscribers = {};
// 當前正在初始化的Vue實例
let target = undefined;
const getItem = window.localStorage.getItem;
localStorage.getItem = (key) => {
console.info("Getting", key);
// 收集依賴的Vue實例
if (!storeItemSubscribers[key]) storeItemSubscribers[key] = [];
if (target) storeItemSubscribers[key].push(target);
// 調(diào)用原始函數(shù)
return getItem.call(localStorage, key);
};
const setItem = window.localStorage.setItem;
localStorage.setItem = (key, value) => {
console.info("Setting", key, value);
// 更新相關(guān)Vue實例中的值
if (storeItemSubscribers[key]) {
storeItemSubscribers[key].forEach((dep) => {
if (dep.hasOwnProperty(key)) dep[key] = value;
});
}
// 調(diào)用原始函數(shù)
setItem.call(localStorage, key, value);
};
Vue.mixin({
beforeCreate() {
console.log("beforeCreate", this._uid);
target = this;
},
created() {
console.log("created", this._uid);
target = undefined;
}
});
現(xiàn)在,當我們運行第一個示例時,我們將獲得一個計數(shù)器,該計數(shù)器每秒增加一個數(shù)字。
new Vue({
el: "#counter",
data: () => ({
counter: localStorage.getItem("counter")
}),
computed: {
even() {
return this.counter % 2 == 0;
}
},
template: `<div class="component">
<div>Counter: {{ counter }}</div>
<div>Counter is {{ even ? 'even' : 'odd' }}</div>
</div>`
});
setInterval(() => {
const counter = localStorage.getItem("counter");
localStorage.setItem("counter", +counter + 1);
}, 1000);
我們的思想實驗結(jié)束
當我們解決了最初的問題時,請記住這主要是一個思想實驗。它缺少一些功能,例如處理已刪除的項目和未安裝的組件實例。它還具有一些限制,例如組件實例的屬性名稱需要與存儲在 localStorage 中的項目相同的名稱。就是說,主要目標是更好地了解Vue響應(yīng)式在幕后的工作方式并充分利用這一點,因此,我希望你能從所有這些事情中受益。
到此這篇關(guān)于如何在Vue中使localStorage具有響應(yīng)式的文章就介紹到這了,更多相關(guān)Vue localStorage響應(yīng)式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
理解Vue2.x和Vue3.x自定義指令用法及鉤子函數(shù)原理
這篇文章主要介紹了理解Vue2.x和Vue3.x的自定義指令的用法及鉤子函數(shù)原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2021-09-09
在vue中使用cookie記住用戶上次選擇的實例(本次例子中為下拉框)
這篇文章主要介紹了在vue中使用cookie記住用戶上次選擇的實例(本次例子中為下拉框),具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
詳解vue-cli項目中用json-sever搭建mock服務(wù)器
這篇文章主要介紹了詳解vue-cli項目中用json-sever搭建mock服務(wù)器,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-11-11
Vue實現(xiàn)下拉滾動加載數(shù)據(jù)的示例
這篇文章主要介紹了Vue實現(xiàn)下拉滾動加載數(shù)據(jù)的示例,幫助大家更好的理解和學習使用vue框架,感興趣的朋友可以了解下2021-04-04
Vue3中的?computed,watch,watchEffect的使用方法
這篇文章主要介紹了Vue3中的?computed,watch,watchEffect的使用方法,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價價值,需要得小伙伴可以參考一下2022-06-06
vue在自定義組件上使用v-model和.sync的方法實例
自定義組件的v-model和.sync修飾符其實本質(zhì)上都是vue的語法糖,用于實現(xiàn)父子組件的"數(shù)據(jù)"雙向綁定,下面這篇文章主要給大家介紹了關(guān)于vue在自定義組件上使用v-model和.sync的相關(guān)資料,需要的朋友可以參考下2022-07-07

