vue3解構(gòu)賦值失去響應(yīng)式引發(fā)的問(wèn)題思考
前言
vue3
發(fā)布以來(lái)經(jīng)歷兩年風(fēng)頭正盛,現(xiàn)在大有和react 平分天下的勢(shì)頭,我們知道他是基于proxy 實(shí)現(xiàn)響應(yīng)式的能力, 解決了vue2
所遺留下來(lái)的一些問(wèn)題,同時(shí)也正由于proxy的特性,也提高了運(yùn)行時(shí)的性能
凡事有利有弊, proxy
雖然無(wú)敵,但是他也有本身的局限,從而產(chǎn)生一些我認(rèn)為的弊端(其實(shí)就是不符合js語(yǔ)言的自然書寫方式,有的人覺(jué)得就是個(gè)特殊寫法,他不屬于弊端)
- 1、 原始值的響應(yīng)式系統(tǒng)的實(shí)現(xiàn) 導(dǎo)致必須將他包裝為一個(gè)對(duì)象, 通過(guò)
.value
的方式訪問(wèn) - 2、 ES6 解構(gòu),不能隨意使用。會(huì)破壞他的響應(yīng)式特性
好奇心驅(qū)使,研究琢磨了一下,為什么他會(huì)造成這兩個(gè)弊端
原始值的響應(yīng)式系統(tǒng)的實(shí)現(xiàn)
在理解原始值的響應(yīng)式系統(tǒng)的實(shí)現(xiàn),我們先來(lái)溫習(xí)一下proxy 的能力!
const obj = { name: 'win' } const handler = { get: function(target, key){ console.log('get--', key) return Reflect.get(...arguments) }, set: function(target, key, value){ console.log('set--', key, '=', value) return Reflect.set(...arguments) } } const data = new Proxy(obj, handler) data.name = 'ten' console.log(data.name,'data.name22')
上述代碼中,我們發(fā)現(xiàn),proxy 的使用本身就是對(duì)于 對(duì)象的攔截, 通過(guò)new Proxy
的返回值,攔截了obj 對(duì)象如此一來(lái),當(dāng)你 訪問(wèn)對(duì)象中的值的時(shí)候,他會(huì)觸發(fā) get
方法, 當(dāng)你修改對(duì)象中的值的時(shí)候 他會(huì)觸發(fā) set
方法但是到了原始值的時(shí)候,他沒(méi)有對(duì)象啊,咋辦呢,new proxy
排不上用場(chǎng)了。無(wú)奈之下,我們只能包裝一下了,所以就有了使用.value
訪問(wèn)了
我們來(lái)看看具體實(shí)現(xiàn):
import { reactive } from "./reactive"; import { trackEffects, triggerEffects } from './effect' export const isObject = (value) => { return typeof value === 'object' && value !== null } // 將對(duì)象轉(zhuǎn)化為響應(yīng)式的 function toReactive(value) { return isObject(value) ? reactive(value) : value } class RefImpl { public _value; public dep = new Set; // 依賴收集 public __v_isRef = true; // 是ref的標(biāo)識(shí) // rawValue 傳遞進(jìn)來(lái)的值 constructor(public rawValue, public _shallow) { // 1、判斷如果是對(duì)象 使用reactive將對(duì)象轉(zhuǎn)為響應(yīng)式的 // 淺ref不需要再次代理 this._value = _shallow ? rawValue : toReactive(rawValue); } get value() { // 取值的時(shí)候依賴收集 trackEffects(this.dep) return this._value; } set value(newVal) { if (newVal !== this.rawValue) { // 2、set的值不等于初始值 判斷新值是否是對(duì)象 進(jìn)行賦值 this._value = this._shallow ? newVal : toReactive(newVal); // 賦值完 將初始值變?yōu)楸敬蔚? this.rawValue = newVal triggerEffects(this.dep) } } }
上述代碼,就是對(duì)于原始值,的包裝,他被包裝為一個(gè)對(duì)象,通過(guò)get value
和set value
方法來(lái)進(jìn)行原始值的訪問(wèn),從而導(dǎo)致必須有.value
的操作 ,這其實(shí)也是個(gè)無(wú)奈的選擇
相當(dāng)于兩瓶毒藥,你得選一瓶 魚與熊掌不可兼得
為什么ES6 解構(gòu),不能隨意使用會(huì)破壞他的響應(yīng)式特性
第一個(gè)問(wèn)題終于整明白了,那么我們來(lái)看看最重要的第二個(gè)問(wèn)題,為什么結(jié)構(gòu)賦值,會(huì)破壞響應(yīng)式特性
proxy背景
在開始之前,我們先來(lái)討論一下為什么要更改響應(yīng)式方案
vue2 基于Object.defineProperty ,但是他有很多缺陷,比如 無(wú)法監(jiān)聽(tīng)數(shù)組基于下標(biāo)的修改,不支持 Map、Set、WeakMap 和 WeakSet等缺陷 ,
其實(shí)這些也也不耽誤我們開發(fā), vue2到現(xiàn)在還是主流,
我的理解就是與時(shí)俱進(jìn)
, 新一代的版本,一定要緊跟語(yǔ)言的特性,一定要符合新時(shí)代的書寫風(fēng)格,雖然proxy
相對(duì)于Object.defineProperty 有很多進(jìn)步, 但是也不是一點(diǎn)缺點(diǎn)都沒(méi)有,你比如說(shuō) 不兼容IE
天底下的事情,哪有完美的呢?尤大的魄力就在于,舍棄一點(diǎn)現(xiàn)在,博一個(gè)未來(lái)!
實(shí)現(xiàn)原理
在理解了背景之后,我們?cè)賮?lái)假模假式的溫習(xí)一下proxy
原理,雖然這個(gè)都被講爛了。
但是,寫水文,講究什么:倆字-連貫
const obj = { count: 1 }; const proxy = new Proxy(obj, { get(target, key, receiver) { console.log("這里是get"); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("這里是set"); return Reflect.set(target, key, value, receiver); } }); console.log(proxy) console.log(proxy.count)
以上代碼就是Proxy的具體使用方式,通過(guò)和Reflect 的配合, 就能實(shí)現(xiàn)對(duì)于對(duì)象的攔截
如此依賴,就能實(shí)現(xiàn)響應(yīng)式了,大家可以發(fā)現(xiàn),這個(gè)obj的整個(gè)對(duì)象就被攔截了,但是你發(fā)現(xiàn)對(duì)象在嵌套深一層
比如:
const obj = { count: 1, b: { c: 2 } }; console.log(proxy.b) console.log(proxy.b.c)
他就無(wú)法攔截了,我們必須要來(lái)個(gè)包裝
const obj = { a: { count: 1 } }; function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { console.log("這里是get"); // 判斷如果是個(gè)對(duì)象在包裝一次,實(shí)現(xiàn)深層嵌套的響應(yīng)式 if (typeof target[key] === "object") { return reactive(target[key]); }; return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { console.log("這里是set"); return Reflect.set(target, key, value, receiver); } }); }; const proxy = reactive(obj);
好了,原理搞完了,我們來(lái)正式研究一下現(xiàn)在列舉一下我知道的響應(yīng)式失去的幾個(gè)情況:
- 1、解構(gòu)
props
對(duì)象,因?yàn)樗鼤?huì)失去響應(yīng)式 - 2、 直接賦值
reactive
響應(yīng)式對(duì)象 - 3、
vuex
中組合API賦值
解構(gòu) props 對(duì)象,因?yàn)樗鼤?huì)失去響應(yīng)式
const obj = { a: { count: 1 }, b: 1 }; //reactive 是上文中的reactive const proxy = reactive(obj); const { a, b } = proxy; console.log(a) console.log(b) console.log(a.count)
上述代碼中,我們發(fā)現(xiàn), 解構(gòu)賦值,b 不會(huì)觸發(fā)響應(yīng)式,a如果你訪問(wèn)的時(shí)候,會(huì)觸發(fā)響應(yīng)式
這是為什么呢?別急我們一個(gè)個(gè)解釋?先來(lái)討論為什么解構(gòu)賦值,會(huì)丟失響應(yīng)式呢?我們知道解構(gòu)賦值,區(qū)分原始類型的賦值,和引用類型的賦值,
原始類型的賦值相當(dāng)于按值傳遞, 引用類型的值就相當(dāng)于按引用傳遞
就相當(dāng)于:
// 假設(shè)a是個(gè)響應(yīng)式對(duì)象 const a={ b:1} // c 此時(shí)就是一個(gè)值跟當(dāng)前的a 已經(jīng)不沾邊了 const c=a.b // 你直接訪問(wèn)c就相當(dāng)于直接訪問(wèn)這個(gè)值 也就繞過(guò)了 a 對(duì)象的get ,也就像原文中說(shuō)的失去響應(yīng)式
那為啥a
具備響應(yīng)式呢?
因?yàn)?code>a 是引用類型,我們還記得上述代碼中的一個(gè)判斷嗎。如果他是個(gè)object
那么就重新包裝為響應(yīng)式
正式由于當(dāng)前特性,導(dǎo)致,如果是引用類型, 你再去訪問(wèn)其中的內(nèi)容的時(shí)候并不會(huì)失去響應(yīng)式
// 假設(shè)a是個(gè)響應(yīng)式對(duì)象 const a={ b:{c:3}} // 當(dāng)你訪問(wèn)a.b的時(shí)候就已經(jīng)重新初始化響應(yīng)式了,此時(shí)的c就已經(jīng)是個(gè)代理的對(duì)象 const c=a.b // 你直接訪問(wèn)c就相當(dāng)于訪問(wèn)一個(gè)響應(yīng)式對(duì)象,所以并不會(huì)失去響應(yīng)式
以上就大致解釋了為什么解構(gòu)賦值,可能會(huì)失去響應(yīng)式,我猜的文檔中懶得解釋其中緣由,索性就定了個(gè)規(guī)矩,您??!
就別用了,省的以為是vue
的bug,提前改變用戶的使用習(xí)慣!不慣著
直接賦值reactive響應(yīng)式對(duì)象
我們最初使用vue3的時(shí)候,指定會(huì)寫出以下代碼
const vue = reactive({ a: 1 }) vue = { b: 2 }
然后就發(fā)出疑問(wèn)reactive
不是響應(yīng)式的嗎? 為啥我賦值了以后,他的響應(yīng)式就沒(méi)了 ,接著破口大罵,垃圾vue
其實(shí)啊,這就是您對(duì)于js 原生的概念不清除,其實(shí)尤大
已經(jīng)做了最大的努力,來(lái)防止你進(jìn)行錯(cuò)誤操作了
比如,由于解構(gòu)賦值的問(wèn)題, 他直接禁止了reactive的解構(gòu)賦值
當(dāng)你用解構(gòu)賦值操作的時(shí)候,他直接禁用了那有人又問(wèn)了, 為啥props 不給禁用了呢?因?yàn)槟愕膒rops 的數(shù)據(jù)可能不是響應(yīng)式的啊,不是響應(yīng)式的,我得能啊,尤大他也不能干涉用戶使用新語(yǔ)法啊
所以還是那句話:框架現(xiàn)在的呈現(xiàn),其實(shí)充滿了取舍,有時(shí)候真是兩瓶毒藥,挑一瓶!
回歸正題,我們?cè)賮?lái)說(shuō)說(shuō) 原生js 語(yǔ)法,首先需要確認(rèn)的是,原生js 的引用類型的賦值,其實(shí)是 按照引用地址賦值!
// 當(dāng)reactive 之后返回一個(gè)代理對(duì)象的地址被vue 存起來(lái), // 用一個(gè)不恰當(dāng)?shù)谋扔鱽?lái)說(shuō),就是這個(gè)地址具備響應(yīng)式的能力 const vue = reactive({ a: 1 }) // 而當(dāng)你對(duì)于vue重新賦值的時(shí)候不是將新的對(duì)象賦值給那個(gè)地址,而是將vue 換了個(gè)新地址 // 而此時(shí)新地址不具備響應(yīng)式,可不就失去響應(yīng)式了嗎 vue = { b: 2 }
以上就是reactive
失去響應(yīng)式的解釋,所以這個(gè)也是很多用戶罵罵咧咧的原因。不符合他的使用習(xí)慣了,這都是被vue2 培養(yǎng)起來(lái)的一代
在這里我要替,尤大說(shuō)句公道話,人家又沒(méi)收你錢,還因?yàn)樗?,你有口飯吃,您自己不能與時(shí)俱進(jìn),擁抱新事物,那是您沒(méi)能耐
,這是典型的端起碗吃肉,放下筷子罵娘
vuex中組合API賦值
在vuex 用賦值也可能會(huì)失去響應(yīng)式:
import { computed } from 'vue' import { useStore } from 'vuex' export default { setup () { const store = useStore() return { // 在 computed 函數(shù)中訪問(wèn) state count: computed(() => store.state.count), // 在 computed 函數(shù)中訪問(wèn) getter double: computed(() => store.getters.double) } } }
以上代碼中我們發(fā)現(xiàn)store.getters.double
必須用computed
包裹起來(lái),其實(shí)道理是一樣的,也是變量賦值的原因,在這里我們就不再贅述!
結(jié)語(yǔ)
到此這篇關(guān)于vue3結(jié)構(gòu)賦值失去響應(yīng)式引發(fā)的問(wèn)題思考的文章就介紹到這了,更多相關(guān)vue3結(jié)構(gòu)賦值內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Vue.js實(shí)戰(zhàn)之通過(guò)監(jiān)聽(tīng)滾動(dòng)事件實(shí)現(xiàn)動(dòng)態(tài)錨點(diǎn)
監(jiān)聽(tīng)事件是我們?cè)谑褂胿ue.js的時(shí)候經(jīng)常使用的一個(gè)功能,下面這篇文章主要介紹了Vue.js實(shí)戰(zhàn)之通過(guò)監(jiān)聽(tīng)滾動(dòng)事件實(shí)現(xiàn)動(dòng)態(tài)錨點(diǎn) 的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),相信對(duì)大家具有一定的參考價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-04-04詳解Vue串聯(lián)過(guò)濾器的使用場(chǎng)景
這篇文章主要介紹了詳解Vue串聯(lián)過(guò)濾器的使用場(chǎng)景,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04簡(jiǎn)單實(shí)現(xiàn)vue中的依賴收集與響應(yīng)的方法
這篇文章主要介紹了簡(jiǎn)單實(shí)現(xiàn)vue中的依賴收集與響應(yīng)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-02-02vue項(xiàng)目完成后如何實(shí)現(xiàn)項(xiàng)目?jī)?yōu)化的示例
本文主要介紹了vue項(xiàng)目完成后如何實(shí)現(xiàn)項(xiàng)目?jī)?yōu)化的示例,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12詳解vue-cli項(xiàng)目中的proxyTable跨域問(wèn)題小結(jié)
這篇文章主要介紹了詳解vue-cli項(xiàng)目中的proxyTable跨域問(wèn)題小結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02