首頁 >web前端 >Vue.js >vue3原始值回應方案及回應遺失問題怎麼解決

vue3原始值回應方案及回應遺失問題怎麼解決

PHPz
PHPz轉載
2023-05-12 15:52:063010瀏覽

    一、ref 的引入

    ref 是解決 proxy 無法直接代理原始值的問題。我們先來看 ref 的使用:

    const name = ref('小黑子')

    ref 是怎麼實現的呢?其實就是用物件「包裹」原始值。我們再來看一下 ref 的實作:

    function ref(val){
        // 使用对象包裹原始值
        const wrapper = {
            value:val    
        }
        // 利用 reactive 将对象变成响应式数据
        return reactive(wrapper)
    }

    ref 的實作就是這麼簡單。

    ref 對原始值回應主要就做了這兩件事:

    • #1、使用物件包裹原始值。

    • 2、使用 reactive 將包裹物件變成響應式資料。

    二、isref 的實作

    我們使用 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

    這就相當於重新定義了新的數據,而不再是原來的回應數據了,自然也就不具有響應式能力。

    1、toRef登場

    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 就解決了回應遺失的問題。

    2、toRefs 加入

    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了

    3、詭異的其它響應式丟失情況

    當屬性為非原始值的時候,解構之後還是依然能回應

    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 將其變成響應式資料。

    四、unref 自動脫 ref       

    #我們使用 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 了。

    
     
    

    其实原因很简单,在组件 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中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除