Vue3.0「十七」-- vue3.0升級新特性及Proxy重寫響應式講解

vue3.0 升級內容

全部用TS重寫的(響應式、vdom、模本編譯)
性能提升,減少代碼量
會調整部分API
Proxy重寫響應式

vue2.x 馬上要過時了嗎

vue3.0從正式發佈到推廣,還需要一段時間
vue2.x應用範圍廣,有大量項目需要維護升級
proxy存在兼容性問題,且不能ployfill

社區熱門知識點:Proxy重寫響應式講解

回顧vue2.*的響應式原理 [object.defindeProperty]

object.defindeProperty缺點:

  • 深度監聽需要一次性遞歸
  • 無法監聽新增屬性/刪除屬性(vue.set/vue.delete)
  • 無法原生監聽數組,需要特殊處理

vue3_Proxy實現響應式原理

前置知識

Proxy ES6語法 對象用於定義基本操作的自定義行爲(如屬性查找、賦值、枚舉、函數調用等)。
Proxy 可以理解成, 在目標對象之前架設一層“ 攔截”, 外界對該對象的訪問, 都必須先通過這層攔截, 因此提供了一種機制, 可以對外界的訪問進行過濾和改寫。

語法:const p = new Proxy(target, handler)
target 要使用 Proxy 包裝的目標對象(可以是任何類型的對象,包括原生數組,函數,甚至另一個代理)
handler 一個通常以函數作爲屬性的對象, 各屬性中的函數分別定義了在執行各種操作時代理 p 的行爲。
handler.get() 方法用於攔截對象的讀取屬性操作。
handler.set() 方法是設置屬性值操作的捕獲器。
handler.deleteProperty() 方法用於攔截對對象屬性的 delete 操作

Reflect 是一個內置的對象,它提供攔截 JavaScript 操作的方法這些方法與proxy handlers的方法相同。

Reflect.get(target, propertyKey[, receiver])
Reflect.deleteProperty(target, propertyKey)
Reflect.set(target, propertyKey, value[, receiver])
target: 需要取值的目標對象; key: 需要獲取的值的鍵值;value::設置的值。
如果target對象中指定了getter, receiver則爲getter調用時的this值

1. Proxy對數據攔截監聽的基本使用

1、用Proxy將目標對象data進行包裝攔截處理如下:

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        const result = Reflect.get(target, key, receiver)
        console.log('get', key) //監聽
        return result //返回結果
    },
    set(target, key, value, receiver) {
        const result = Reflect.set(target, key, value, receiver)
        console.log('set', key, value) //set age 30
        return result //是否設置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key)
        console.log('delete property', key) //delete property name
        return result //是否刪除成功
    },
})

2、定義data爲對象,並對data對象進行操作時

const data = {
    name: 'lili',
    age: 20,
}
// 對象操作
proxyData.age // get操作  :  get age
proxyData.age = 30 // set操作 :  set age 30
proxyData.sex = "女" // set操作 :  set sex 女
delete proxyData.name // 刪除操作 : delete property name

不足:
在對象設置屬性時,無法確定是新增屬性還是原有屬性;

3、定義data爲數組,並對data數組進行操作

const data =['a','b','c']

proxyData.push('d') 
// get push  push()方法觸發
// get length //獲取數組長度
// set 3 d //設置值
// set length 4 設置數組長度

不足:
給數組添加元素時,沒必監聽 原型的屬性,如push(),只需要監聽本身(非原型)的屬性,
set 3 d,set length 4 爲重複處理同一個數據,set length 4多餘

2. Proxy對數據攔截監聽使用的優化

針對以上問題,對Proxy對數據攔截監聽使用進行優化

1.在對象設置屬性時,無法確定是新增屬性還是原有屬性
在set方法中判斷

if (ownKeys.includes(key)) {
    console.log('已有的 key') //監聽
} else {
    console.log('新增的 key')
}

2、在監聽屬性時,只監聽本身(非原型)的屬性

在get方法中,判斷如果是自身的屬性,才進行監聽

// Reflect.ownKeys()方法可以返回包含Symbol屬性在內的自有屬性。
const ownKeys = Reflect.ownKeys(target)
if (ownKeys.includes(key)) {
    console.log('get',key)//監聽
}

3、重複的數據不處理

在 set方法中,重複數據不處理

const oldVal=target[key]
if (value === oldVal) {
    return true
}

以上問題,Proxy對數據攔截監聽使用的優化後:

const proxyData = new Proxy(data, {
  // target:目標對象、 key:被捕獲的屬性名、receiver:Proxy或者繼承Proxy的對象
  get(target, key, receiver) {
    // 只監聽 處理本身(非原型)的屬性
    const ownKeys = Reflect.ownKeys(target)
    if (ownKeys.includes(key)) {
      console.log('get', key) //監聽
    }
    const result = Reflect.get(target, key, receiver)
    return result //返回結果
  },
  // value 新屬性值。
  set(target, key, value, receiver) {
    if (ownKeys.includes(key)) {
      console.log('已有的 key') //監聽
    } else {
      console.log('新增的 key')
    }
    // 重複的數據不處理
    if (value === target[key]) {
      return true
    }
    const result = Reflect.set(target, key, value, receiver)
    console.log('set', key, value) //set age 30
    return result //是否設置成功
  },
  deleteProperty(target, key) {
    const result = Reflect.deleteProperty(target, key)
    console.log('delete property', key) //delete property name
    return result //是否刪除成功
  },
})

定義data爲數組,並對data數組進行操作時

const data =['a','b','c']

proxyData.push('d') 
// get length //獲取數組長度
// set 3 d //設置值

實現了只保留了對自身屬性的監聽,重複數據沒有重複設置

3. Proxy實現響應式

實現思路:
① 創建響應式方法reactive(data),該方法可以傳入需要處理的數據對象data
函數內邏輯:
② 判斷 data 是否爲 對象或者數組,不是直接返回
③ 創建 Proxy 代理對象,Proxy對象中傳入data
Proxy 代理對象中的方法配置:
④ 在get()方法對數據的進行監聽:只監聽 處理本身(非原型)的屬性;在返回結果中採用遞歸調用reactive(),實現對數據的深度監聽
⑤ 在 set() 方法中進行數據的新增和更新:判斷是否是新增數據;重複的數據不處理;
⑥ 在 deleteProperty() 方法中對數據進行刪除操作;
實例:
⑦ 定義數據data,傳入響應式方法中,返回的值proxyData爲實現響應式的可操作數據

// 創建響應式
function reactive(target = {}) {
  if (typeof target != 'object' || target == null) {
    // 不是對象或者數組,則返回
    return target
  }
  // 代理配置 生成代理對象
  return observed = new Proxy(target, {
    // target:目標對象、 key:被捕獲的屬性名、receiver:Proxy或者繼承Proxy的對象
    get(target, key, receiver) {
      // 只監聽 處理本身(非原型)的屬性 ,如push()
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        console.log('get', key) //監聽
      }
      const result = Reflect.get(target, key, receiver)
      // return result //返回結果
      // 深度監聽
      // 性能如何提升的?
      return reactive(result) //遞歸get處理 實現深度監聽
    },
    // value 新屬性值。
    set(target, key, value, receiver) {
      // 判斷是否是新增屬性
      const ownKeys = Reflect.ownKeys(target)
      if (ownKeys.includes(key)) {
        console.log('已有的 key') //監聽
      } else {
        console.log('新增的 key')
      }
      // 重複的數據不處理
      const oldVal = target[key]
      if (value === oldVal) {
        return true
      }
      const result = Reflect.set(target, key, value, receiver)
      console.log('set', key, value) //set age 30
      return result //是否設置成功
    },
    deleteProperty(target, key) {
      const result = Reflect.deleteProperty(target, key)
      console.log('delete property', key) //delete property name
      console.log('result', result) //result true
      return result //是否刪除成功
    },
  })

}


// 測試數據
const data = {
  name: 'lili',
  age: 20,
  info: {
    city: "beijing"
  }
}
const proxyData = reactive(data)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章