ref 是解決 proxy 無法直接代理原始值的問題。我們先來看 ref 的使用:
const name = ref('小黑子')
ref 是怎麼實現的呢?其實就是用物件「包裹」原始值。我們再來看一下 ref 的實作:
function ref(val){ // 使用对象包裹原始值 const wrapper = { value:val } // 利用 reactive 将对象变成响应式数据 return reactive(wrapper) }
ref 的實作就是這麼簡單。
ref 對原始值回應主要就做了這兩件事:
#1、使用物件包裹原始值。
2、使用 reactive 將包裹物件變成響應式資料。
我們使用 ref 建立一個響應式對象,但是我們要怎麼區別一個物件是普通物件還是 ref 物件呢?於是我們的 isref 出現了。
我們來看它的使用:
const name = ref('cj') console.log(isRef(name)); // true
那麼它的實作原理是怎麼樣的呢?主要實作還是在 ref API內部,我們來看看具體實作程式碼:
function ref(val){ const wrapper = { value:val } Object.defineProperty(warpper,'__v_isRef',{ value:true }) return reactive(wrapper) }
原來就是在 ref 內部為 包裹物件新增一個不可枚舉不可寫的屬性,且值為 true 。這樣我們就可以檢查該屬性來判斷是不是 ref 了。
function isRef(val) { return val.__v_isRef ?? false }
什麼是回應遺失?響應遺失就是響應式資料不進行回應了。我們來看下方程式碼:
const obj = reactive({foo:1,bar:2}) const {foo,bar} = obj obj.foo++ // foo不会改变,还是 1
上面的 obj 已經回應遺失了,也不會觸發重新渲染。為什麼會這樣呢?其實就是因為使用了結構賦值,展開運算子也會使其失效。
const obj = reactive({foo:1,bar:2}) const newObj = {...obj} obj.foo++ // newObj.foo不会改变,还是 1
這就相當於重新定義了新的數據,而不再是原來的回應數據了,自然也就不具有響應式能力。
toRef 就是為了解決回應遺失的問題。我們來看看它的實作:
function toRef(obj,key) { const wrapper = { get value() { return obj[key] }, set value(val) { obj[key] = val } } Object.defineProperty(wrapper,'__v_isRef',{ value:true }) return wrapper }
傳入兩個參數,第一個是響應式數據,第二個是 obj 的一個鍵。
第一部分就是設定宣告一個對象,物件先設定了value 屬性的get 用於將存取toRef 值時,將返回傳入的回應式資料對應的屬性值,然後設定了value屬性的set 用來將設定toRef 值時,就拿取設定的新值更新響應式資料對應的屬性值。也就是說,toRef 傳回的物件還是利用的響應式資料。
第二部分用來設定傳回的資料是 ref 資料。因為 toRef 回傳的數據類似 ref 數據,為了統一就直接認定為是一個 ref 數據。
第三部分就是回傳響應式資料對應宣告的屬性物件
#這樣 toRef 就解決了回應遺失的問題。
toRefs 就是將整個響應式物件進行解構響應化。實作程式碼如下:
function toRefs() { const ret = {} for (const key in obj) { ret[key] = toRef(obj,key) } return ret }
使用 for 迴圈逐一對屬性進行轉換。這下我們再來看一下使用:
const obj = reactive({foo:1,bar:2}) const {foo,bar} = toRefs(obj) obj.foo++ // foo.value变为2了
當屬性為非原始值的時候,解構之後還是依然能回應
const obj = reactive({foo:{age:18},bar:2}) const {foo,bar} = obj obj.foo.age++ // foo.age变为2了 obj.bar++ // bar没有改变,还是1
這是為什麼?原因其實很簡單,因為非原始值賦值的是引用位址,也就是說解構後的變數其實還是指向原始響應式資料的屬性。而原始值就是單純的賦值,就不會進行回應。
reactive 响应数据重新赋值后不再响应,ref 响应数据赋值后依然响应 let obj1 = reactive({foo:1,bar:2}) let obj2 = ref({foo:1,bar:2}) // 假如 obj1 与 obj2 直接展示在页面上 obj1 = {boo:2,far:3} // 页面 obj1 还是 {foo:1,bar:2} obj2.value = {boo:2,far:3} // 页面 obj2 变为 {boo:2,far:3} 了
這又是什麼原因? reactive 重新賦值響應遺失,就是重新賦值了新的對象,自然就變成普通資料了,不再回應。而 ref 還是能響應,是因為 ref 在內部進行 set 處理。程式碼如下:
function ref(val){ const wrapper = { value:val set value(val) { // isObject 这里代表判断是否是非原始值的一个方法 value = isObject(val) === 'Object' ? reactive(val) : val } } Object.defineProperty(warpper,'__v_isRef',{ value:true }) return reactive(wrapper) }
我們明白了,其實 ref 在 set 中判斷了設定的新值是否是非原始值,如果是就呼叫 reactive 將其變成響應式資料。
#我們使用 ref 響應式資料時,會覺得總是需要 .value 來取得價值,增加了使用者的心智負擔。
那可不可以不透過 .value 存取值,而時直接就能夠存取值呢?
這樣用於也不用關心某個數據到底是不是 ref 數據,需不需要透過 value 屬性去取得值。
這就到了我們的 unref 出手了。 unref 實作了自動脫 ref 能力,自動脫 ref 就是如果讀取的屬性是 ref,則直接將該 ref 對應的 value 屬性值傳回。
我們來看看 unref 的實作:
function unref(target) { return new Proxy(target,{ get(target,key,receiver) { const value = Reflect.get(target,key,receiver) return value.__v_isRef ? value.value : value }, set(target,key,newValue,receiver) { const value = target[key] if (value.__v_isRef) { value.value = newValue return true } return Reflect.set(target,key,newValue,receiver) } }) }
我們發現 unref 內部使用 Proxy 代理了目標對象,接收一個物件作為參數,並傳回該物件的代理物件。當我們存取 unref 的資料時,觸發 get 擷取器,然後再擷取器內部判斷了傳入物件是否是 ref 對象,如果是就直接返回 ref 的 .value 值。如果不是則直接傳回代理物件。
當對 unref 傳回的代理物件設定值時,觸發 set 擷取器,如果代理程式的物件時 ref ,就將需要設定的新值賦值給 .value,不是則直接進行賦值處理。
當我們使用 ref 建立響應式資料後,將其在模板中展示,為什麼不用 .value 了。
{{ name }}
其实原因很简单,在组件 setup 中声明的 ref 响应式数据会传递给 unref 函数进行处理。所以在模板中访问 ref 的值,无需通过 value 属性来访问。
我们使用的 reactive 其实也是有 自动脱 ref 功能的,看一下下方例子:
const count = ref(0) const obj = reactive({conut}) obj.count // 0
我们可以看见 obj.count 是一个 ref 响应式数据。在 count 外层包裹一层对象,再传递给 reactive 后,再访问 obj.count 时就不需要再通过 value 属性访问值了。
也正是因为 reactive 内部也同样实现了自动脱 ref 的能力。
以上是vue3原始值回應方案及回應遺失問題怎麼解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!