一、reactive 創建的響應式對象解構后為什么會失去響應式
Vue 擁有一個響應式系統,可以讓它在數據更新的時候自動進行視圖的更新。在Vue3.0中,可以使用 reactive 聲明響應式狀態。文檔說不要解構 reactive 創建的響應式對象,為什么?因為會失去響應式的功能,那為什么會失去,我們就來簡單研究下。
1、進入reactive方法:
可以看到它在內部調用了createReactiveObject方法,這名字很好理解就是創建響應式對象,調試進去:
它在這里做了一些處理,細節暫時不探究,重點在這:
創建了一個代理對象將其保存在內部並返回,學習Vue3或多或少都聽過Vue3如何進行數據視圖更新的,Proxy對數據進行了攔截,當數據發生變化,通知Vue進行視圖變化。
可以看出 reactive 方法其實就是創建了一個Proxy對象,以及進行了一系列處理,其中並沒有找到它可能會失去響應式的情況,也就是說它失去響應式不在於Vue而是在於Proxy對象本身,那么可以簡化一下。
2、Proxy 解構
(1)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); } });
我們可以看到不解構的話,count 賦值 2 可以做到響應式更改。而解構賦值,count 就是 5 ,proxy 還是 { count: 2 }
是因為解構相當於重新賦值給另一個變量的原因嗎,也就是說它變成了一個新值。
(2)Proxy 解構嵌套對象:我們再看下嵌套對象
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)直接賦值:proxy 有響應變化
(2)解構一次:proxy 也有響應變化
(3)解構2次到底:proxy 不再響應變化
(4)總結原因:為什么會不同,其實很好理解,只解構一次,其實新的對象的 count 仍然是被代理的,
而解構兩次,直接獲取了count,相當於繞過了代理 a,直接拿到基本類型的值。
那么就很好理解了,解構為什么會失去響應,用這個圖就可以解釋:
因為解構賦值相當於直接跳過了代理那一層,在下面直接獲取值,所以 get 和 set 無法被調用。
二、ref 和 reactive + toRefs 響應式實現原理
對於基本數據類型,函數傳遞或者對象解構時,會丟失原始數據的引用。換言之,我們沒法讓基本數據類型,或者解構后的變量(如果它的值也是基本數據類型的話),成為響應式的數據。
我們沒辦法讓 a 或 x 這樣的基本數據類型成為響應式的數據的,Proxy 也無法劫持基本數據。
但是有時候,我們確實就是想一個數字、一個字符串是響應式的,或者就是想利用解構的寫法。那怎么辦呢?只能通過創建一個對象,也即是源碼中的 Ref 數據,然后將原始數據保存在 Ref 的屬性value當中,再將它的引用返回給使用者。
既然是我們自己創造出來的對象,也就沒必要使用 Proxy 再做代理了,直接劫持這個 value 的 get/set 即可,這就是 ref 函數與 Ref 類型的由來。
不過單靠 ref 還沒法解決對象解構的問題,它只是將基本數據保持在一個對象的 value 中,以實現數據響應式。對於對象的解構還需要另外一個函數:toRefs。通過遍歷對象,將每個屬性值都轉成 Ref 數據,這樣解構出來的還是 Ref 數據,自然就保持了響應式數據的引用。