Vue3 解構賦值失去響應式引發的思考!
前言
vue3
發佈以來經歷兩年風頭正盛,現在大有和 react 平分天下的勢頭,我們知道他是基於 proxy 實現響應式的能力, 解決了vue2
所遺留下來的一些問題,同時也正由於 proxy 的特性,也提高了運行時的性能
凡事有利有弊, proxy
雖然無敵,但是他也有本身的侷限,從而產生一些我認爲的弊端(其實就是不符合 js 語言的自然書寫方式,有的人覺得就是個特殊寫法,他不屬於弊端)
-
1、 原始值的響應式系統的實現 導致必須將他包裝爲一個對象, 通過
.value
的方式訪問 -
2、 ES6 解構,不能隨意使用。會破壞他的響應式特性
好奇心驅使,研究琢磨了一下,爲什麼他會造成這兩個弊端
原始值的響應式系統的實現
在理解原始值的響應式系統的實現,我們先來溫習一下 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')
複製代碼
上述代碼中,我們發現,proxy 的使用本身就是對於 對象的攔截, 通過new Proxy
的返回值,攔截了 obj 對象
如此一來,當你 訪問對象中的值的時候,他會觸發 get
方法, 當你修改對象中的值的時候 他會觸發 set
方法
但是到了原始值的時候,他沒有對象啊,咋辦呢,new proxy
排不上用場了。
無奈之下,我們只能包裝一下了,所以就有了使用.value
訪問了
我們來看看具體實現
import { reactive } from "./reactive";
import { trackEffects, triggerEffects } from './effect'
export const isObject = (value) => {
return typeof value === 'object' && value !== null
}
// 將對象轉化爲響應式的
function toReactive(value) {
return isObject(value) ? reactive(value) : value
}
class RefImpl {
public _value;
public dep = new Set; // 依賴收集
public __v_isRef = true; // 是ref的標識
// rawValue 傳遞進來的值
constructor(public rawValue, public _shallow) {
// 1、判斷如果是對象 使用reactive將對象轉爲響應式的
// 淺ref不需要再次代理
this._value = _shallow ? rawValue : toReactive(rawValue);
}
get value() {
// 取值的時候依賴收集
trackEffects(this.dep)
return this._value;
}
set value(newVal) {
if (newVal !== this.rawValue) {
// 2、set的值不等於初始值 判斷新值是否是對象 進行賦值
this._value = this._shallow ? newVal : toReactive(newVal);
// 賦值完 將初始值變爲本次的
this.rawValue = newVal
triggerEffects(this.dep)
}
}
}
複製代碼
上述代碼,就是對於原始值,的包裝,他被包裝爲一個對象,通過get value
和set value
方法來進行原始值的訪問,從而導致必須有.value
的操作 ,這其實也是個無奈的選擇
相當於兩瓶毒藥,你得選一瓶
魚與熊掌不可兼得
爲什麼 ES6 解構,不能隨意使用會破壞他的響應式特性
第一個問題終於整明白了,那麼我們來看看最重要的第二個問題,爲什麼結構賦值,會破壞響應式特性
proxy 背景
在開始之前,我們先來討論一下爲什麼要更改響應式方案
vue2 基於 Object.defineProperty ,但是他有很多缺陷,比如 無法監聽數組基於下標的修改,不支持 Map、Set、WeakMap 和 WeakSet 等缺陷 ,
其實這些也也不耽誤我們開發, vue2 到現在還是主流,
我的理解就是與時俱進
, 新一代的版本,一定要緊跟語言的特性,一定要符合新時代的書寫風格
,雖然proxy
相對於 Object.defineProperty 有很多進步, 但是也不是一點缺點都沒有,你比如說 不兼容IE
天底下的事情,哪有完美的呢?
尤大的魄力就在於,捨棄一點現在,博一個未來!
實現原理
在理解了背景之後,我們再來假模假式的溫習一下proxy
原理,雖然這個都被講爛了。
但是,寫水文,講究什麼:倆字 - 連貫
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 的具體使用方式,通過和 Reflect 的配合, 就能實現對於對象的攔截
如此依賴,就能實現響應式了,大家可以發現,這個 obj 的整個對象就被攔截了,但是你發現對象在嵌套深一層
比如:
const obj = {
count: 1,
b: {
c: 2
}
};
console.log(proxy.b)
console.log(proxy.b.c)
複製代碼
他就無法攔截了,我們必須要來個包裝
const obj = {
a: {
count: 1
}
};
function reactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
console.log("這裏是get");
// 判斷如果是個對象在包裝一次,實現深層嵌套的響應式
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);
複製代碼
好了,原理搞完了,我們來正式研究一下
現在列舉一下我知道的響應式失去的幾個情況:
-
1、解構
props
對象,因爲它會失去響應式 -
2、 直接賦值
reactive
響應式對象 -
3、
vuex
中組合 API 賦值
解構 props
對象,因爲它會失去響應式
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)
複製代碼
上述代碼中,我們發現, 解構賦值,b 不會觸發響應式
,a如果你訪問的時候
,會觸發響應式
這是爲什麼呢?
別急我們一個個解釋?
先來討論爲什麼解構賦值,會丟失響應式呢?
我們知道解構賦值,區分原始類型的賦值,和引用類型的賦值,
原始類型的賦值相當於按值傳遞
, 引用類型的值就相當於按引用傳遞
就相當於
// 假設a是個響應式對象
const a={ b:1}
// c 此時就是一個值跟當前的a 已經不沾邊了
const c=a.b
// 你直接訪問c就相當於直接訪問這個值 也就繞過了 a 對象的get ,也就像原文中說的失去響應式
複製代碼
那爲啥a
具備響應式呢?
因爲a
是引用類型,我們還記得上述代碼中的一個判斷嗎。如果他是個object
那麼就重新包裝爲響應式
正式由於當前特性,導致,如果是引用類型, 你再去訪問其中的內容的時候並不會失去響應式
// 假設a是個響應式對象
const a={ b:{c:3}}
// 當你訪問a.b的時候就已經重新初始化響應式了,此時的c就已經是個代理的對象
const c=a.b
// 你直接訪問c就相當於訪問一個響應式對象,所以並不會失去響應式
複製代碼
以上就大致解釋了爲什麼解構賦值,可能會失去響應式,我猜的文檔中懶得解釋其中緣由,索性就定了個規矩,您啊!
就別用了,省的以爲是vue
的 bug,提前改變用戶的使用習慣!不慣着
直接賦值reactive
響應式對象
我們最初使用 vue3 的時候, 指定會寫出以下代碼
const vue = reactive({ a: 1 })
vue = { b: 2 }
複製代碼
然後就發出疑問reactive
不是響應式的嗎?爲啥我賦值了以後,他的響應式就沒了 ,接着破口大罵,垃圾 vue
其實啊,這就是您對於 js 原生的概念不清除,其實尤大
已經做了最大的努力,來防止你進行錯誤操作了
比如,由於解構賦值的問題, 他直接禁止了 reactive 的解構賦值
當你用解構賦值操作的時候,他直接禁用了
那有人又問了, 爲啥props 不給禁用了呢
?
因爲你的props 的數據可能不是響應式的啊,不是響應式的,我得能啊
,尤大他也不能干涉用戶使用新語法啊
所以還是那句話:框架現在的呈現,其實充滿了取捨
, 有時候真是兩瓶毒藥,挑一瓶!
迴歸正題,我們再來說說 原生 js 語法
首先需要確認的是,原生 js 的引用類型的賦值,其實是 按照引用地址賦值!
// 當reactive 之後返回一個代理對象的地址被vue 存起來,
// 用一個不恰當的比喻來說,就是這個地址具備響應式的能力
const vue = reactive({ a: 1 })
// 而當你對於vue重新賦值的時候不是將新的對象賦值給那個地址,而是將vue 換了個新地址
// 而此時新地址不具備響應式,可不就失去響應式了嗎
vue = { b: 2 }
複製代碼
以上就是reactive
失去響應式的解釋,所以這個也是很多用戶罵罵咧咧的原因。不符合他的使用習慣了,這都是被vue2 培養起來的一代
在這裏我要替,尤大說句公道話,人家又沒收你錢,還因爲他,你有口飯喫
,
您自己不能與時俱進,擁抱新事物,那是您沒能耐
,
這是典型的端起碗喫肉,放下筷子罵娘
vuex
中組合 API 賦值
在 vuex 用賦值也可能會失去響應式
import { computed } from 'vue'
import { useStore } from 'vuex'
export default {
setup () {
const store = useStore()
return {
// 在 computed 函數中訪問 state
count: computed(() => store.state.count),
// 在 computed 函數中訪問 getter
double: computed(() => store.getters.double)
}
}
}
複製代碼
以上代碼中我們發現store.getters.double
必須用computed
包裹起來,其實道理是一樣的,也是變量賦值的原因,在這裏我們就不再贅述!
最後
本文爲,在使用 vue3 過程中,採坑後的一些心得,以及探究,希望對各位大佬有幫助,能讓各位大佬在工作中升職加薪!
最後,最後,推廣下我的 vue 源碼解析,分析的可能不太好,但是都是一行行看的,可能您看了哪天能想起個一句半句, 有所幫助!
源碼解析地址 [1]
歡迎 star
關於本文
作者:老驥 farmer
https://juejin.cn/post/7114596904926740493
程序員成長指北 專注 Node.js 技術棧分享,從 前端 到 Node.js 再到 後端數據庫,祝您成爲優秀的高級 Node.js 全棧工程師。一個有趣的且樂於分享的人。座右銘:今天未完成的,明天更不會完成。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PFVd4gQixYTvXMg4lwgQRA