深入理解vue響應式原理

2020-10-12 18:00:47

Vue最顯著的特性之一便是不太引人注意的響應式系統(reactivity system)。模型層(model)只是普通JS物件,修改它則更新檢視(view)。這會讓狀態管理變得非常簡單且直觀,不過理解它的工作原理以避免一些常見的問題也是很重要的。

本文將詳細介紹Vue響應式系統的底層細節。

追蹤變化

把一個普通JS物件傳給Vue範例的data選項,Vue將遍歷此物件所有的屬性,並使用Object.defineProperty把這些屬性全部轉為getter/setter。

Object.defineProperty是僅ES5支援,且無法shim的特性,這也就是為什麼Vue不支援IE8瀏覽器的原因。

使用者看不到getter/setter,但是在內部它們讓Vue追蹤依賴,在屬性被存取和修改時通知變化

每個元件範例都有相應的watcher範例物件,它會在元件渲染的過程中把屬性記錄為依賴,之後當依賴項的setter被呼叫時,會通知watcher重新計算,從而致使它關聯的元件得以更新。

1.png

變化檢測

受現代JS的限制(以及廢棄 Object.observe),Vue不能檢測到物件屬性的新增或刪除。由於Vue會在初始化範例時對屬性執行 getter/setter轉化過程,所以屬性必須在data物件上存在才能讓Vue轉換它,這樣才能讓它是響應的。

var vm = new Vue({
  data:{
    a:1
  }
})
// `vm.a` 是響應的
vm.b = 2
// `vm.b` 是非響應的

Vue不允許在已經建立的範例上動態新增新的根級響應式屬性(root-level reactive property)。然而它可以使用 Vue.set(object, key, value) 方法將響應屬性新增到巢狀的物件上。

Vue.set(vm.someObject, 'b', 2)

也可以使用 vm.$set 實體方法,這也是全域性 Vue.set 方法的別名。

this.$set(this.someObject,'b',2)

有時想向已有物件上新增一些屬性,例如使用Object.assign()或 _.extend()方法來新增屬性。但是,新增到物件上的新屬性不會觸發更新。在這種情況下可以建立一個新的物件,讓它包含原物件的屬性和新的屬性。

// 代替 `Object.assign(this.someObject, { a: 1, b: 2 })`
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

宣告響應式屬性

由於Vue不允許動態新增根級響應式屬性,所以必須在初始化範例前宣告根級響應式屬性,哪怕只是一個空值。

var vm = new Vue({
  data: {
    // 宣告 message 為一個空值字串
    message: ''
  },
  template: '<div>{{ message }}</div>'
})
// 之後設定 `message` 
vm.message = 'Hello!'

如果在data選項中未宣告 message,Vue將警告渲染函數在試圖存取的屬性不存在。

這樣的限制在背後是有其技術原因的,它消除了在依賴項跟蹤系統中的一類邊界情況,也使Vue範例在型別檢查系統的幫助下執行的更高效。

而且在程式碼可維護性方面也有一點重要的考慮:data 物件就像元件狀態的概要,提前宣告所有的響應式屬性,可以讓元件程式碼在以後重新閱讀或其他開發人員閱讀時更易於被理解。

非同步更新佇列

Vue非同步執行DOM更新。只要觀察到資料變化,Vue將開啟一個佇列,並緩衝在同一事件迴圈中發生的所有資料改變。如果同一個watcher被多次觸發,只會一次推入到佇列中。

這種在緩衝時去除重複資料對於避免不必要的計算和DOM操作上非常重要。然後,在下一個的事件迴圈「tick」中,Vue重新整理佇列並執行實際(已去重的)工作。

Vue在內部嘗試對非同步佇列使用原生的Promise.then和MutationObserver,如果執行環境不支援,會採用setTimeout(fn, 0)代替

例如,當設定vm.someData='new value',該元件不會立即重新渲染。當重新整理佇列時,元件會在事件迴圈佇列清空時的下一個「tick」更新。多數情況不需要關心這個過程,但是如果想在DOM狀態更新後做點什麼,這就可能會有些棘手。

雖然Vue.js通常鼓勵開發人員沿著「資料驅動」的方式思考,避免直接接觸 DOM,但是有時確實要這麼做。為了在資料變化之後等待Vue完成更新DOM ,可以在資料變化之後立即使用Vue.nextTick(callback) 。這樣回撥函數在DOM更新完成後就會呼叫。

<div id="example">{{message}}</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: '123'
  }
})
vm.message = 'new message' // 更改資料
vm.$el.textContent === 'new message' // false
Vue.nextTick(function () {
  vm.$el.textContent === 'new message' // true
})

在元件內使用vm.$nextTick()實體方法特別方便,因為它不需要全域性Vue,並且回撥函數中的this將自動繫結到當前的Vue範例上:

Vue.component('example', {
  template: '<span>{{ message }}</span>',
  data: function () {
    return {
      message: '沒有更新'
    }
  },
  methods: {
    updateMessage: function () {
      this.message = '更新完成'
      console.log(this.$el.textContent) // => '沒有更新'
      this.$nextTick(function () {
        console.log(this.$el.textContent) // => '更新完成'
      })
    }
  }
})

相關推薦:

更多程式設計相關知識,請存取:!!

以上就是深入理解vue響應式原理的詳細內容,更多請關注TW511.COM其它相關文章!