vue中this.$set()原理

vue中this.$set()原理

在vue開發中,經常會遇到這樣的問題:當我們給響應式的對象新增屬性時,新增的屬性並不會顯示到頁面中;同樣的對於響應式的數組,增加元素、修改數組長度時,數組的這些變化也不會反映到頁面中(響應式對象和響應式數組是指在vue初始化時期,利用Object.defineProperty()方法對其進行監聽,這樣在修改數據時會及時體現在頁面上)。如下所示:

<div id="app">
  {{ obj }}
  {{ arr }}
</div>
let vm = new Vue({
  el: '#app',
  data() {
    return {
      obj: {
        a: '哈哈哈',
      },
      arr: [1, 3]
    }
  },
  mounted() {
    this.changeObj()
    this.changeArr()
  },
  methods: {
    changeObj() {
      this.obj.b = '新增的屬性b'
      console.log(this.obj)
    },
    changeArr() {
      this.arr[4] = 7
      this.arr.length = 5
      console.log(this.arr)
    }
  }
})
vm.$data.obj.c = '新增屬性c'
vm.$data.arr[2] = 5

從上面例子中可以看出在頁面加載後,我們通過this.obj.b新增屬性b時,頁面中並沒有將屬性b顯示出來;通過vm.$data.obj.c新增屬性c時,頁面中也沒有將屬性c顯示出來;但是通過打印對象obj,可以看出obj上存在屬性b和c,爲什麼呢?

這是由於對象obj及其屬性a在vue初始化時已處理成響應式的,即當我們改變對象obj的值或屬性a的值時,會觸發其在頁面上的更新,但是當頁面加載完成後,新增的屬性b和c就不是響應式的,雖然通過打印this.obj可以看出對象obj上確實增加了屬性b和c,但是由於b和c不是響應式的,所以新增的屬性不會體現在頁面上。

對於數組arr也存在類似的問題,當我們通過索引值添加元素或更改數組長度時,數組本身是發生變化了,但是沒有及時體現在頁面上。

對於上述問題,我們通常使用vue中的this.$set()來解決,那麼this.$set()的原理是什麼呢?

在使用this.$set(target, key, value)時,target爲需要添加屬性的對象,key是要添加的屬性名,value爲屬性key對應的值。

點擊查看vue中set源碼

// src/core/observer/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {
  if (process.env.NODE_ENV !== 'production' &&
    (isUndef(target) || isPrimitive(target))
  ) {
    warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
  }
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    target.length = Math.max(target.length, key)
    target.splice(key, 1, val)
    return val
  }
  if (key in target && !(key in Object.prototype)) {
    target[key] = val
    return val
  }
  const ob = (target: any).__ob__
  if (target._isVue || (ob && ob.vmCount)) {
    process.env.NODE_ENV !== 'production' && warn(
      'Avoid adding reactive properties to a Vue instance or its root $data ' +
      'at runtime - declare it upfront in the data option.'
    )
    return val
  }
  if (!ob) {
    target[key] = val
    return val
  }
  defineReactive(ob.value, key, val)
  ob.dep.notify()
  return val
}

上面源碼的執行邏輯如下:

1、如果是在開發環境,且target未定義(爲null、undefined)或target爲基礎數據類型(string、boolean、number、symbol)時,拋出告警;

2、如果target爲數組且key爲有效的數組key時,將數組的長度設置爲target.length和key中的最大的那一個,然後調用數組的splice方法(vue中重寫的splice方法)添加元素;

3、如果屬性key存在於target對象中且key不是Object.prototype上的屬性時,表明這是在修改target對象屬性key的值(不管target對象是否是響應式的,只要key存在於target對象中,就執行這一步邏輯),此時就直接將value直接賦值給target[key];

4、判斷target,當target爲vue實例或根數據data對象時,在開發環境下拋錯;

5、當一個數據爲響應式時,vue會給該數據添加一個__ob__屬性,因此可以通過判斷target對象是否存在__ob__屬性來判斷target是否是響應式數據,當target是非響應式數據時,我們就按照普通對象添加屬性的方式來處理;當target對象是響應式數據時,我們將target的屬性key也設置爲響應式並手動觸發通知其屬性值的更新;

上面代碼中最重要的就是如下代碼:

defineReactive(ob.value, key, val)
ob.dep.notify()

這兩行是將新增屬性設置爲響應式,並手動觸發通知該屬性值的更新,這就是通過this.$set()設置之後新增的屬性會變成響應式並及時體現在頁面中的原因。

參考文獻:

[1] 從vue源碼看Vue.set()和this.$set()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章