大家對於 watch 應該不陌生,專案中都用過下面這種寫法:
watch: {
someProp () {
// do something
}
}
// 或者
watch: {
someProp: {
deep: true,
handler () {
// do something
}
}
}
複製程式碼
上面的寫法告訴 vue,我需要監聽 someProp 屬性的變化,於是 vue 在內部就會為我們建立一個 watcher 物件。(限於篇幅,我們不聊 watcher 的具體實現,感興趣的可以直接看原始碼 watcher)
然而在 vue 中,watcher 的功能並沒有這麼單一,先上段程式碼:
<template>
<div>
<p>a: {{ a }}</p>
<p>b: {{ b }}</p>
<button @click="increment">+</button>
</div>
</template>
<script>
export default {
data () {
return {
a: 1
}
},
computed: {
b () {
return this.a * 2
}
},
watch: {
a () {
console.log('a is changed')
}
},
methods: {
increment () {
this.a += 1
}
},
created () {
console.log(this._watchers)
}
}
</script>
複製程式碼
上面程式碼非常簡單,我們現在主要關注 created
鉤子中列印的 this._watchers
,如下:
分別展開三個 watcher,觀察每一個 expression,從上到下分別為:
- b() {↵ return this.a * 2;↵ }
- "a"
- function () {↵ vm._update(vm._render(), hydrating);↵ }
上面三個 watcher 代表了三種不同功能的 watcher,我們將其按功能分為三類:
- 在 watch 中定義的,用於監聽屬性變化的 watcher (第二個)
- 用於 computed 屬性的 watcher (第一個)
- 用於頁面更新的 watcher (第三個)
normal-watcher
我們在 watch 中定義的,都屬於這種型別,即只要監聽的屬性改變了,都會觸發定義好的回撥函式
computed-watcher
每一個 computed 屬性,最後都會生成一個對應的 watcher 物件,但是這類 watcher 有個特點,我們拿上面的 b
舉例:
屬性 b
依賴 a
,當 a
改變的時候,b
並不會立即重新計算,只有之後其他地方需要讀取 b
的時候,它才會真正計算,即具備 lazy(懶計算)特性
render-watcher
每一個元件都會有一個 render-watcher, function () {↵ vm._update(vm._render(), hydrating);↵ }
, 當 data/computed
中的屬性改變的時候,會呼叫該 render-watcher 來更新元件的檢視
三種 watcher 的執行順序
除了功能上的區別,這三種 watcher 也有固定的執行順序,分別是:
computed-render -> normal-watcher -> render-watcher
這樣安排是有原因的,這樣就能儘可能的保證,在更新元件檢視的時候,computed 屬性已經是最新值了,如果 render-watcher 排在 computed-render 前面,就會導致頁面更新的時候 computed 值為舊資料
小結
本文並不是原始碼解析類文章,只是從一個角度來聊聊,那些看似不相關的東西(computed/watch/頁面更新),內部卻有著緊密的聯絡,希望能拋磚引玉,讓大家更深入的去探索 vue