Vue3和Vue2的響應(yīng)式原理
什么是響應(yīng)式?
簡單來說,響應(yīng)式就是頁面的模版內(nèi)容能隨著數(shù)據(jù)變化而重新渲染。我們來看一個案例:
<template>
<div id="app">
<div>Count: {{ count }}</div>
<button class="btn" @click="onAdd">Add</button>
</div>
</template>
<script setup>
import { ref } from "vue";
const count = ref(1);
const onAdd = () => {
count.value++;
};
</script>在這段代碼中,ref 是 Vue3 官方提供的響應(yīng)式 API,count 就是通過響應(yīng)式 API 聲明的響應(yīng)式數(shù)據(jù)。count 這個數(shù)據(jù)可以直接在模板里使用,例如上述代碼中我們使用了count 數(shù)據(jù)進行顯示。
那么響應(yīng)式體現(xiàn)在哪呢?上述代碼有一個點擊事件的函數(shù) onAdd,這個函數(shù)實現(xiàn)了對 count 數(shù)值的自增操作。每當(dāng)點擊這個 onAdd 事件,count 就會自動加 1,對應(yīng)使用到 count 的模板也會隨之重新渲染最新的數(shù)據(jù)。
上述功能代碼里的這種視圖隨著數(shù)據(jù)的變化,就是響應(yīng)式的特征?;?Vue的響應(yīng)式 API 生成的數(shù)據(jù),就是響應(yīng)式數(shù)據(jù),如果響應(yīng)式數(shù)據(jù)發(fā)生了變化,那么依賴了數(shù)據(jù)的視圖也會跟著變化。
那響應(yīng)式原理是如何實現(xiàn)的呢?接下來我們就一起來看下。
響應(yīng)式原理
我們以一個經(jīng)常被拿來當(dāng)作典型例子的用例即是 Excel 表格:

這里單元格 A2 中的值是通過公式 = 5 * A1 來定義的 ,我們期望更改 A1, A2 也隨即自動更新。
而 JavaScript 默認并不是這樣的。如果我們用 JavaScript 寫類似的邏輯:
let A1 = 1; let A2 = 5 * A1; console.log(A2); // 5A1 = 2;console.log(A2); // 仍然是 5
我們現(xiàn)在的目標(biāo)是,在 A1 變化時會調(diào)用 effect() (產(chǎn)生作用)。在具體實現(xiàn)時,Vue2和Vue3采取了不同的實現(xiàn)方案,我們一一看下【備注:更完善的響應(yīng)式原理會在專欄后續(xù)補充】。
Vue2響應(yīng)式方案
Vue2的實現(xiàn)方案主要是借助于Object.defineProperty。
這里來看下如下通過Object.defineProperty ,來實現(xiàn)我們的目標(biāo):
let obj = {};
let A1;
let A2;
function effect() {
A2 = 5 * A1;
}
Object.defineProperty(obj, "A1", {
get() {
return A1;
},
set(val) {
A1 = val;
effect();
},
});
obj.A1 = 1;
console.log(A2); // 打印5obj.A1 = 2;console.log(A2); // 打印10在上面的實現(xiàn)中,我們定義個一個對象 obj,使用 Object.defineProperty 代理 A1 屬性,進而對 obj 對象的 value 屬性實現(xiàn)了攔截,讀取 A1 屬性的時候執(zhí)行 get 函數(shù),修改 A1 屬性的時候執(zhí)行 set 函數(shù),在 set 函數(shù)內(nèi)部調(diào)用 effect() 函數(shù),從而實現(xiàn)響應(yīng)式。
接下來我們來看下Vue3的響應(yīng)式方案,不過它分非原始值的響應(yīng)式和原始值的響應(yīng)式。
非原始值的響應(yīng)式方案
什么是非原始值呢?其實就是對象,而并非數(shù)字、字符串。
非原始值的響應(yīng)式數(shù)據(jù)是基于Proxy實現(xiàn)的,它允許我們攔截并重新定義一個對象的基本操作,具體API 我們可以訪問MDN 文檔。
這里來看下如下通過Proxy ,來實現(xiàn)我們的目標(biāo):
let obj = {};
let A1;
let A2;
function effect(val) {
A2 = 5 * val;
}
let proxy = new Proxy(obj, {
get: function (target, prop) {
return target[prop];
},
set: function (target, prop, value) {
target[prop] = value;
effect(value);
},
});
proxy.A1 = 1;
console.log(A2); // 打印5proxy.A1 = 2;console.log(A2); // 打印10在上面的實現(xiàn)中,我們定義了一個對象 obj,使用 Proxy 代理 obj,實現(xiàn)了和Vue2相同的功能 ,讀取 A1 屬性的時候執(zhí)行 get 函數(shù),修改 A1 屬性的時候執(zhí)行 set 函數(shù),在 set 函數(shù)內(nèi)部調(diào)用 effect() 函數(shù),從而實現(xiàn)響應(yīng)式。
與Object.defineProperty所不同的是Proxy 是針對對象來監(jiān)聽,而不是針對某個具體屬性,所以不僅可以代理那些定義時不存在的屬性,還可以代理更豐富的數(shù)據(jù)結(jié)構(gòu),比如 Map、Set 等等。
原始值的響應(yīng)式方案
什么是原始值?原始值指的是Boolean、Number、String、null等類型的值。
在JavaScript中,原始值是按值傳遞的,而非按引用傳遞。如果一個函數(shù)接收原始值作為參數(shù),那么形參和實參之間沒有引用關(guān)系,是兩個完全獨立的值。此外,JavaScript中的proxy無法提供對原始值的代理,想要將原始值變成響應(yīng)式數(shù)據(jù),就必須對其做一層包裹,借助對象的 get 和 set 函數(shù)來實現(xiàn):
let A1 = 1;
let A2;
function effect(val) {
A2 = 5 * val;
}
let obj = {
get value() {
return A1;
},
set value(val) {
A1 = val;
effect(val);
},
};
obj.value = 1;
console.log(A2); // 打印5obj.value = 2;console.log(A2); // 打印10在上面的實現(xiàn)中,我們利用對象的 get 和 set 函數(shù)來進行監(jiān)聽,這種響應(yīng)式的實現(xiàn)方式,只能攔截某一個屬性,這也是 Vue 3 中 ref 這個 API 的實現(xiàn)。
以上我們了解了響應(yīng)式原理,不過我們想在工作中更好地運用響應(yīng)式API,我們還需要知道在響應(yīng)式開發(fā)中可能會遇到什么“坑”。
響應(yīng)式原理注意事項
我們先來了解一下 Vue 2的注意事項,由于 JavaScript 的限制,Vue 不能檢測數(shù)組和對象的變化。盡管如此我們還是有一些辦法來回避這些限制并保證它們的響應(yīng)性。
Vue 2注意事項
對于對象,Vue2 無法檢測 property 的添加或移除。由于 Vue 會在初始化實例時對 property 執(zhí)行 getter/setter 轉(zhuǎn)化,所以 property 必須在 data 對象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的。例如:
var vm = new Vue({ data: { a: 1 } }); // `vm.a` 是響應(yīng)式的vm.b = 2// `vm.b` 是非響應(yīng)式的對于已經(jīng)創(chuàng)建的實例,Vue 不允許動態(tài)添加根級別的響應(yīng)式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套對象添加響應(yīng)式 property。例如:
Vue.set(vm.someObject, "b", 2);
您還可以使用 vm.$set 實例方法,這也是全局 Vue.set 方法的別名:
this.$set(this.someObject, "b", 2);
對于數(shù)組,Vue2 不能檢測以下數(shù)組的變動:
- 當(dāng)你利用索引直接設(shè)置一個數(shù)組項時,例如:
vm.items[index] = newValue - 當(dāng)你修改數(shù)組的長度時,例如:
vm.items.length = newLength
舉個例子:
var vm = new Vue({
data: {
items: ["a", "b", "c"]
}
});
vm.items[1] = "x"; // 不是響應(yīng)性的vm.items.length = 2 // 不是響應(yīng)性的為了解決第一類問題,以下方式都可以實現(xiàn)和 vm.items[indexOfItem] = newValue 相同的效果,同時也將在響應(yīng)式系統(tǒng)內(nèi)觸發(fā)狀態(tài)更新:
// Vue.setVue.set(vm.items, indexOfItem, newValue) // Array.prototype.splicevm.items.splice(indexOfItem, 1, newValue) // 該方法是全局方法 Vue.set 的一個別名 vm.$set(vm.items, indexOfItem, newValue);
為了解決第二類問題,你可以使用 splice:
vm.items.splice(newLength);
Vue 3注意事項
Vue.js 3 的響應(yīng)式開發(fā)有什么需要注意的?
第一個注意事項是響應(yīng)式數(shù)據(jù)解構(gòu),這可能會丟失響應(yīng)式聯(lián)系,例如:
<template>
<div id="app">
<input v-model="text" placeholder="文本信息" /> 文本信息:{{ text }}
</div>
</template>
<script setup>
import { reactive } from "vue";
const state = reactive({ text: "hello world" });
const { text } = state;
</script>在上述代碼中,text 的響應(yīng)式聯(lián)系并不會生效,修改 text 內(nèi)容后,不會觸發(fā)頁面的展示文本 text 的視圖更新渲染。這是為什么呢?
這里我們用 reactive 定義了 state 響應(yīng)式數(shù)據(jù),但是在之后又把其中的 state.text 解構(gòu)賦值給了變量 text,這就會斷掉響應(yīng)式聯(lián)系,導(dǎo)致再怎么更新 text 都不會觸發(fā)視圖重新更新渲染。
那如果我們就想用解構(gòu)的方法來使用text變量怎么辦?可以借助toRefs 來實現(xiàn):
<script setup>
import { reactive, toRefs } from "vue";
const state = reactive({ text: "hello world" });
const { text } = toRefs(state);
</script>第二個注意事項是Vue3官方指明謹慎使用的API,例如 shallowReactive 、 shallowReadonly等等,示例:
const state = shallowReactive({
foo: 1,
nested: {
bar: 2
}
})
// 更改狀態(tài)自身的屬性是響應(yīng)式的
state.foo++
// ...但下層嵌套對象不會被轉(zhuǎn)為響應(yīng)式
isReactive(state.nested) // false
// 不是響應(yīng)式的
state.nested.bar++異步更新隊列
異步隊列更新,這是Vue2和Vue3都共存的注意事項,當(dāng)你在 Vue 中更改響應(yīng)式狀態(tài)時,最終的 DOM 更新并不是同步生效的,而是由 Vue 將它們緩存在一個隊列中,直到下一個“tick”才一起執(zhí)行。這樣是為了確保每個組件無論發(fā)生多少狀態(tài)改變,都僅執(zhí)行一次更新。
多數(shù)情況我們不需要關(guān)心這個過程,但是如果你想基于更新后的 DOM 狀態(tài)來做點什么,這就可能會有些棘手。雖然 Vue.js 通常鼓勵開發(fā)人員使用數(shù)據(jù)驅(qū)動的方式思考,避免直接接觸 DOM,但是有時我們必須要這么做。
nextTick() 可以在狀態(tài)改變后立即使用,以等待 DOM 更新完成。你可以傳遞一個回調(diào)函數(shù)作為參數(shù),或者 await 返回的 Promise。示例:
<script setup>
import { ref, nextTick } from 'vue'
const count = ref(0)
async function increment() {
count.value++
// DOM 還未更新
console.log(document.getElementById('counter').textContent) // 0
await nextTick()
// DOM 此時已經(jīng)更新
console.log(document.getElementById('counter').textContent) // 1
}
</script>
<template>
<button id="counter" @click="increment">{{ count }}</button>
</template>總結(jié)
今天的知識點我們來做個總結(jié):
1、我們認識了什么是響應(yīng)式,其實就是頁面的模版內(nèi)容能隨著數(shù)據(jù)變化而重新渲染。
2、我們總結(jié)了Vue2和Vue3各自的響應(yīng)式實現(xiàn)原理以及開發(fā)需要注意的事項。
以上就是Vue3和Vue2的響應(yīng)式原理的詳細內(nèi)容,更多關(guān)于Vue3和Vue2 響應(yīng)式原理的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
elementui?Select選擇器嵌套tree實現(xiàn)TreeSelect方式
這篇文章主要介紹了elementui?Select選擇器嵌套tree實現(xiàn)TreeSelect方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10
vue-cli開發(fā)環(huán)境實現(xiàn)跨域請求的方法
本篇文章主要介紹了vue-cli開發(fā)環(huán)境實現(xiàn)跨域請求的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04
vant steps流程圖的圖標(biāo)使用slot自定義方式
這篇文章主要介紹了vant steps流程圖的圖標(biāo)使用slot自定義方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
vue中使用refs定位dom出現(xiàn)undefined的解決方法
本篇文章主要介紹了vue中使用refs定位dom出現(xiàn)undefined的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12
vue.js在標(biāo)簽屬性中插入變量參數(shù)的方法
這篇文章主要介紹了vue.js在標(biāo)簽屬性中插入變量參數(shù)的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-03-03

