首頁  >  文章  >  web前端  >  深入淺析vue處理響應式資料的方法

深入淺析vue處理響應式資料的方法

青灯夜游
青灯夜游轉載
2023-03-28 19:14:402204瀏覽

這篇文章帶大家學習vue,聊聊vue如何處理響應式資料?希望對大家有幫助!

深入淺析vue處理響應式資料的方法

關於vue的響應式數據,你可能有很多疑惑。

例如為什麼要改用proxy?

例如為什麼有reactive和ref兩個api?

例如vue是如何實現響應式的?

其實這些在原始碼中,都能找到答案。

在第一篇vue3有哪些升級中,我也提了一下使用proxy的好處,以及Object.defineProperty的缺陷。但今天,我想換個角度,讓chatGPT來回答這個問題。 【相關推薦:vuejs影片教學web前端開發

#嗯,我感覺比我總結的好。

深入淺析vue處理響應式資料的方法

那繼續下一個問題。

深入淺析vue處理響應式資料的方法

這個回答感覺就比較官方了,沒有回答到我想要的答案。

原因其實很簡單,因為proxy方法不能代理值型,只能代理物件。所以需要一個額外的方法,來處理值類型的資料。

當然你也可以一個ref走天下,vue原始碼做了相容會自動進行轉換

有了這些理解之後,我們來看今天的重頭戲,自己仿照vue3源碼,實作一個響應式系統。

相關程式碼可以點擊查看,根據文章的標題選擇不同的分支即可。

實作reactive

在前文我們也介紹了,reactive其實實用的是proxy代理物件。

我們可以使用proxy實作一個簡單的代理函數reactive。

      function reactive(target) {        const isObject = (val) =>  val !== null && typeof val === 'object'

        if (!isObject(target)) {          console.warn(`数据必须是对象: ${String(target)}`)          return target
        }        const proxy = new Proxy(target, {          get: (target, key, receiver) => {            console.log('get', key)            const res = Reflect.get(target, key, receiver)            // track(target, key)
            // 这句很关键,不然嵌套数据,里面的不会响应
            if (isObject(res)) {              return reactive(res)
            }            return res
          },          set: (target, key, value, receiver) => {            console.log('set', key, value)            const result = Reflect.set(target, key, value, receiver)            // trigger(target, key, value)
            return result
          },          deleteProperty: () => {            const result = Reflect.deleteProperty(target, key)            return result
          }
        })        return proxy
      }      const person = reactive({        name: '',        age: 18,        like: ['apple']
      })

      person.name  = 'vue test'复制代码

注意:Reflect

Reflect.get(target, key)跟直接存取target[key]是有一點差別的。

當代理物件中,有get,set等this指向的時候,可以重定向this。

舉例:

        const person = new Proxy({            name: "vue test",            age: 18,            get info() {                return 'name is :' + this.name + ' age is:' + this.age
            }
        }, {            get: (target, key, receiver) => {                console.log('get', key)                // return target[key]
                return Reflect.get(target, key, receiver)
            }
        })        console.log(person.info)复制代码

使用Reflect,我們在存取name, age的時候均可以觸發。 深入淺析vue處理響應式資料的方法

改成target之後,則只會在info觸發一次。

return target[key]复制代码

深入淺析vue處理響應式資料的方法

實作ref

在vue3中,ref是透過get和set來實現的。

跟上面類似,還是先宣告一個函數,然後透過get,set來存取資料。

      function ref(value) {        return new RefImpl(value)
      }      class RefImpl {        constructor(value) {          // 如果值是对象,则用reactive处理
          this._value = isObject(value) ? reactive(value) : value          // 记录一下初始值
          this._rawValue = value
        }        get value() {          // trackRefValue(this)
          return this._value
        }        set value(newVal) {          if (!Object.is(newVal, this._rawValue)) {            // 更新原始数据
            this._rawValue = newVal            // 更新 .value 的值
            this._value = isObject(newVal) ? reactive(newVal) : value            // triggerRefValue(this)
          }
        }
      }复制代码

原始碼也很直觀的解釋了,為什麼ref的使用一定要用.value的方式使用,因為get/set就是透過value來設定的。

新增依賴收集與觸發

資料代理程式我們已經完成了,但是資料變更之後,怎麼通知元件實作雙向綁定呢?

答案就是依賴收集與觸發,也就是當觸發get的時候,我把觸發get的這個條件【函數】給保存起來,當觸發set的時候,重新執行一下觸發這個條件【函數】不就行了嗎。

還是來看程式碼,我加一個track收集的方法,加一個target觸發的方法。 (也就是上面程式碼片段註解掉的兩行程式碼)

除此之外,還需要一個effect函數來管理觸發函數。

      let activeEffect = null
      const targetMap = new WeakMap()      // 依赖收集/触发
      function track(target, key) {        let depsMap = targetMap.get(target)        if (!depsMap) {
          targetMap.set(target, (depsMap = new Map()))
        }        let dep = depsMap.get(key)        if (!dep) {
          dep = new Set()
        }
        dep.add(activeEffect)
        depsMap.set(key, dep)
      }      function trigger(target, key, value) {        const depsMap = targetMap.get(target)        if (!depsMap) {          return
        }        const deps = depsMap.get(key)        if (!deps) {          return
        }

        deps.forEach(effectFn => {          if (effectFn.scheduler) {
            effectFn.scheduler()
          } else {            effectFn()
          }
        })
      }      function effect(fn,options = {}) {        const effectFn = () => {          try {
            activeEffect = effectFn            return fn()
          } catch (error) {
            activeEffect = null
          }
        }        if (!options.lazy) {          effectFn()
        }
        effectFn.scheduler = options.scheduler
        return effectFn
      }      const person = reactive({        name: "hello world"
      })      effect(() => {        console.log('effect person', person.name)
      })      setTimeout(() => {
        person.name = 'setTimeout world'
      }, 2000)复制代码

activeEffect用來儲存觸發的條件函數。

targetMap用來存放依賴字典,格式如下

{
    target: {
        key: []
    }
}复制代码

輸出結果為hello world,2秒之後,重新執行依賴性函數,輸出setTimeout world

深入淺析vue處理響應式資料的方法

總結

資料的代理程式並不複雜,就是在proxy的基礎上,加上一些邊界處理。而要實現響應式則需要加上依賴收集,依賴觸發的實作。

effect是一個很重要的函數,很多api是基於這個函數開發的,像是useEffect, watch。組件的更新也是基於effect函數,這個後續還會提到。

如果不太理解effect,可以梳理一下執行順序。

  • 1.呼叫effect函數,並傳入參數fn
  • #2.申明一個effectFn函數,並執行,將函數儲存為activeEffect
  • #3.執行fn,讀取person.name
  • 4.走proxy的get代理
  • 5.收集依賴,將activeEffect保存的函數存到全域map
  • #6.此時,effect函數執行完畢,等待資料更新
  • 7.2s後,走proxy的set代理
  • 8.執行全域map中儲存的函數,重新執行effect函數,再回到步驟1

相關程式碼可以點選檢視,選擇lesson3分支即可。

文章相關程式碼,可在vue/examples中查看,仿vue實作版本可在packages/reactivity中查看。

(學習影片分享:vuejs入門教學程式設計基礎影片

以上是深入淺析vue處理響應式資料的方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

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