其實,這是我之前寫過的 一篇文章(我絕對不會承認是拿來墊檔的)。雖然我覺得這件事情很奇妙,但是比起 EventBus 的都市傳說,這個應該算還行。只要你看過原始碼應該就能理解。
是說,有誰會沒事去挖人家的原始碼來看( 就你啊 )。
再看一次 $watch
我們平常在操作 $watch
的時候,會有兩種寫法:
export default {
name: 'Component',
data () {
return {
myAge: 18
}
},
watch: {
myAge (newAge, oldAge) {
// newAge 表示改變後的新值
// oldAge 表示改變前的舊值
}
}
}
或是你寫在其他的生命週期勾子裡,
export default {
name: 'Component',
data () {
return {
myAge: 18
}
},
created () {
this.$watch(() => {
return this.myAge
}, (newAge, oldAge) => {
// newAge 表示改變後的新值
// oldAge 表示改變前的舊值
})
}
}
兩種寫法的差異在於呼叫的時機點不同,一個是在特定的生命週期勾子裡才去 監看 你所想要的變數,另一個是在元件初始化的同時 順便 幫你把想要監看的變數初始化。而第二種寫法,彈性比較大的地方在於,你可以將第一個變數傳入函式回傳值,這中間你想要回傳什麼就都沒差了,甚至你想要做非同步處理也是可以。
當然,你可以監看一個變數,那我們要怎麼解除這個監看呢?根據官方的說法,解除監看( unwatch )的方式,就是呼叫 $watch
所回傳的函式即可。那麼,你就會發現,只有第二種方法可以解除監看這件事情,亦即:
export default {
name: 'Component',
data () {
return {
myAge: 18,
myWatcher: void 0
}
},
created () {
this.myWatcher = this.$watch(() => {
return this.myAge
}, (newAge, oldAge) => {
// newAge 表示改變後的新值
// oldAge 表示改變前的舊值
})
},
beforeDestroy () {
// 呼叫剛剛 $watch 回傳的函式,即可解除監看動作
this.myWatcher()
}
}
接著,我們來提一下官方文件所沒有提到的部分。上述的段落是比較常見的作法,其實 $watch
還是有比較奇妙的寫法。首先,第三個參數在官方文件也有提及,
export default {
name: 'Component',
data () {
return {
myAge: 18,
myWatcher: void 0
}
},
created () {
this.myWatcher = this.$watch(() => {
return this.myAge
}, (newAge, oldAge) => {
}, {
// 這邊可以放第三個參數
immediate: true
})
},
beforeDestroy () {
this.myWatcher()
}
}
第三個參數可以接受的設定有:
immediate
即刻救援即刻執行。deep
深度觀察。user
呼叫執行的保護動作。lazy
初始化執行動作,以及是否使用延遲監看(預設是true
)。sync
同步處理,與lazy
互斥,但lazy
優先。
以上除了 immediate
與 deep
官方文件有提到以外,剩下三件事情官方沒有多加著墨,如果想要稍微理解的話,可以看我之前 嘴 $watch
的文章。
以我們目前鐵人賽所使用的 Vue 2.6.10 的版本來說,這個 $watch
的原始碼可能已經跟我原本的文章有一點出入,不過基本上操作邏輯還是相同。
那麼,除了上述在勾子裡使用 $watch
,如果元件屬性 watch
要使用的話,官方也是沒有告訴你該怎麼使用,我們來看看這個例子:
export default {
name: 'Component',
data () {
return {
myAge: 18
}
},
watch: {
myAge: {
handler (newAge, oldAge) {
// 這邊就是原本的回呼函式
},
// 以下就是第三個參數
immediate: true,
deep: true
}
}
}
以上就是目前 $watch
可以使用的所有方法。只是,如果你想要能夠取消,那你就只能使用在勾子裡面操作的那種方式,寫在元件裡面的這種 watch
在元件被銷毀之前,是沒辦法取消的。
再看生命週期
扣除掉你在生命週期的勾子當中所建立的 $watch
之外,元件本身的 watch
到底是存在於生命週期的那個部分?我們簡易的寫一個範例來運作,看看結果為何:
我們這邊簡單的利用四種生命週期,來追查 watch
到底會出現在哪裡:
beforeCreate
created
beforeMount
mounted
我在 created
這個生命週期的勾子當中,執行了 this.age = 99
這個動作。所以我們看上面執行結果的例子,你會發現 age watcher
出現在 mounted
之後。由於我們要在 created
之後才會有 this
這個實例,所以當我們執行了 this.age = 99
之後,觸發了 watch
的監看,最終在 mounted
才會有反應。
無論你多早呼叫,最後會在
mounted
才會有反應。
但是,我們來看看第三個參數 immediate
的例子:
你會發現在上面的執行結果當中,出現了兩次 age watcher
的訊息,原因在於,我們在 watch
的第三個參數 immediate: true
,所以,我們執行結果就會有兩次 age watcher
訊息。
那麼,第一次的 age watcher
訊息,發生在 beforeCreate
與 created
中間,這個地方就是元件的 watch
被初始化的地方。而在 mounted
之後,就是當我在 created
呼叫的時候,他被 觸發 的反應結果會是在 mounted
之後才發生。
當你使用了
immediate: true
,那麼元件建立完成之前就會先被呼叫一次。
根據這樣的結果,我們可以知道,其實 watch
被初始化的地方,跟元件本身是一樣的。所以,如果你加上了 immediate: true
的參數,那麼你就必須注意他在初始化就會被呼叫。
另外,在 watch
還有一件事情,無論你的第三參數為何,倘若你所 監看 的變數,並不會影響到渲染結果,那麼,在生命週期當中,關於元件的更新方法 beforeUpdate
與 updated
就不會被呼叫,亦即,只有 watch
本身的 handler
會被執行而已。
隱藏函式 before
在目前版本 Vue 2.6.10 當中,我們的 Watcher 第三個參數中,還隱藏了一個 before
的屬性可以使用,他本身是接受一個函式的呼叫,觸發的時機點是在你的 watch
被觸發之前:
export default {
name: 'Component',
data () {
return {
myAge: 18
}
},
watch: {
myAge: {
handler (newAge, oldAge) {
// 這邊就是原本的回呼函式
},
// before 函式
before () {
console.log('age watcher before function')
}
}
}
}
在上述的執行結果當中,你會看到 age watcher before function
的結果被印出,這個函式到底是做什麼用的呢?其實,這個 before
最原始的用意,是 Vue 自身對於 Component 的某些生命週期的勾子,例如 beforeUpdate
,他也是使用 Watcher 去監看的。所以,他其實並非對外開放的方法。
具體來說,這個 before
會在 handler
被呼叫 之前 被執行,所以,基本上你可以在這邊做一些相對應的邏輯操作,或者,去干涉你原本的 handler
( 但是請不要做這種蠢事 )。這個函式所回傳的 this
並不會是你的元件,而是這個元件內部的 Watcher 物件:
這個 Watcher 物件就是元件本身所包含的監看動作,每一種不同的 watch
對象,都會有一組對應的 Watcher 物件。如果你不確定你自己在做什麼,或者是酒過三巡之後,請不要任意對這個物件做操作。由於這個物件並 不是 不可修改( Immutable ),所以你若是硬要從這邊干涉你的對象元件,或是 handler
的操作,是真的會運作的。
由上述的例子你會發現,你原本的 handler
被你自己改掉了。再次聲明,他 不是對外開放的方法,除非你自己知道在做什麼事情,不然請不要任意覆寫 Watcher 這個物件。
小結
希望這個章節能夠讓你比較瞭解 watch
的作用方式,當你下次使用 watch
的時候,應該就能理解觸發時機、觸發結果,以及為什麼沒有被觸發了。
最後再嘴一下 $watch 藏在原始碼裡的邊緣人 ,下次遇到
watch
不會動,可以參考這一篇。