Vue | 32 內部 - 深入響應式原理

主要內容:

  1. 改變是如何被追蹤到的
  2. 改變檢測的一些說明及聲明屬性的方式
  3. 聲明響應式屬性的形式及原因
  4. 異步更新隊列的方式及如何在nextTick下手動更新數據

現在是時候深入研究一下了!Vue最獨特的功能之一就是非侵入式的響應系統。模型僅僅是純JavaScript對象。當你修改他們,視圖更新。它使狀態管理更加簡單直觀,然而理解他們是如何工作的以避免一些常見的陷阱。在這章,我們將深入研究Vue的響應系統的一些細節。

如何追蹤變化

當你把普通的JavaScript對象傳遞給一個Vue實例作爲data選項,Vue將遍歷所有的屬性,並使用Object.defineProperty將這些屬性轉爲getter/setters。這是ES5中不可模擬的特性,這就是爲什麼Vue不支持IE8及以下的版本。

getter/setter是對用戶不可見的,當屬性被訪問或修改的時候,鉤子會被啓用,使得Vue執行依賴追蹤和改變通知。需要注意的是,在打印轉換數據對象時,瀏覽器控制檯打印的getters/setters格式並不相同。所以爲了更友好的界面檢查你可能需要安裝vue-devtools

每一個組件實例有一個一致的watcher實例,它會把組件渲染的過程中把任何屬性記錄爲依賴。而後當一個依賴的setter被觸發的時候,它會通知watcher,反過來導致組件被重新渲染。
reactivity in depth

變化檢測說明

由於現代Javascript的限制(而且Object.observe已經被終止),Vue不能檢測到屬性的添加或刪除。由於Vue在實例初始化期間執行了getter/setter轉換過程,爲了Vue轉換換它和響應它,屬性必須在data對象中存在。例如:

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

vm.b = 2
// `vm.b` 不是響應式的

Vue不允許動態的添加一個新的屬性到一個已經被創建好的實例中。然而,使用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 = Objet.assign({}, this.someObject, {a: 1, b: 2})

這裏有一些和屬性相關的注意事項,在之前的列表渲染章節已經講過。

聲明響應式屬性

由於Vue不允許動態添加根級響應屬性,你必須通過聲明所有根級響應式屬性初始化Vue實例,即使值爲空:

	var vm = new Vue({
		data: {
			// 用一個空值聲明message
			message: ''
		},
		template: '<div>{{ message }}/div>'
	})
	// 稍後設置'message'
	vm.message = 'Hello!'

如果你沒有聲明message在data操作項,渲染函數試着訪問一個不存在的屬性,Vue將警告你。

這樣的限制在背後是有技術上的原因的-它消除了依賴追蹤中的邊緣情況,也使得Vue實例在類型檢查系統的幫助下發揮更好的作用。而且在代碼可維護性方法也有重要的考慮:data對象就像組件狀態的概要。當一會再看或者另一個開發者讀代碼的時候,預先聲明所有響應式屬性使得組件代碼更容易理解。

異步更新隊列

可能你沒有注意到,Vue執行DOM更新是異步的。無論何時當一個數據改變被觀察到,它會開一個隊列,緩存發生在同一個消息循環內的所有data改變。如果相同的watcher被觸發多次,它僅僅被pushed到隊列一次。這種緩存重複數據刪除對於避免不必要的計算和DOM維護是重要的。然後,在下一次消息循環"tick"中,Vue刷新隊列並執行實際的(已經去重的)工作。內部的Vue嘗試對異步隊列使用原生的Promise.thenMessageChannel,如果環境不支持,會採用setTimeout(fn, 0)代替。

例如,當你設置vm.someData = 'new value',組件將不會立即重新渲染。它將更新在下一個’tick’ , 當一個隊列被刷新後。多數情況下你不需要關心這個,但是當你想在DOM狀態更新之後做點什麼。這就可能有些棘手。雖然Vue一般鼓勵開發者沿着數據驅動的方式思考,避免直接接觸DOM,有時可能確實需要這麼做。爲了等待數據更新之後Vue.js已經完成了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: 'not updated'
		}
	},
	methods: {
		updateMessage: function () {
			this.message = 'updated'
			console.log(this.$el.textContent)  // => 'not updated'
			this.$nextTick(function () {
				console.log(this.$el.textContent)  // => 'updated'
			})
		}
	}
})

由於$nextTick()返回一個promise對象,所以你可以使用新的ES2016 async/wait語法完成相同的事情:

methods: {
	async updateMessage: function () {
		this.message = 'updated'
		console.log(this.$el.textContent) // => 'not updated'
		await this.$nextTick()
		console.log(this.$el.textContent)  // => 'updated'
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章