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ù)進(jìn)行顯示。
那么響應(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 默認(rèn)并不是這樣的。如果我們用 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 屬性,進(jìn)而對 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ù)來進(jìn)行監(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官方指明謹(jǐn)慎使用的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、我們認(rèn)識了什么是響應(yīng)式,其實就是頁面的模版內(nèi)容能隨著數(shù)據(jù)變化而重新渲染。
2、我們總結(jié)了Vue2和Vue3各自的響應(yīng)式實現(xiàn)原理以及開發(fā)需要注意的事項。
以上就是Vue3和Vue2的響應(yīng)式原理的詳細(xì)內(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-10vue-cli開發(fā)環(huán)境實現(xiàn)跨域請求的方法
本篇文章主要介紹了vue-cli開發(fā)環(huán)境實現(xiàn)跨域請求的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-04-04vant steps流程圖的圖標(biāo)使用slot自定義方式
這篇文章主要介紹了vant steps流程圖的圖標(biāo)使用slot自定義方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06vue中使用refs定位dom出現(xiàn)undefined的解決方法
本篇文章主要介紹了vue中使用refs定位dom出現(xiàn)undefined的解決方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-12-12vue.js在標(biāo)簽屬性中插入變量參數(shù)的方法
這篇文章主要介紹了vue.js在標(biāo)簽屬性中插入變量參數(shù)的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2018-03-03