我在先前的篇幅當中,有提到生命週期與路由的關係。我們這一個篇章,就將路由與生命週期之間的事情,做一個全面性的剖析。如果你之前有稍稍稍稍微留意我的部落格,應該會看過我曾經碎念過 Router 與生命週期之間的事情。
所以我就不跪求心理陰影面積了。
我們除了 Vue 原本就有的方法之外,如果另外再加上 Router 所提供的,那麼大家的順序上就很容易搞混,我們先條列一下到底會有哪些方法:
beforeCreate
created
beforeMount
mounted
beforeUpdate
updated
beforeDestroy
destroyed
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
以上是可以放在元件( Component )當中的方法,另外還有 beforeEnter
,如果你們還有印象的話,他是放在 Routes 的設定當中。所以這些全部算起來,總共有 12 種 Hooks,那麼這麼多方法到底實際上的差異在哪裡?
所以我們先用單一元件,來試著大亂鬥一下:
import Vue from 'vue';
export default {
name: 'App',
beforeCreate () {
console.log('App beforeCreate.');
},
created () {
console.log('App created.');
},
beforeMount () {
console.log('App beforeMount.');
},
mounted () {
console.log('App mounted.');
},
beforeUpdate () {
console.log('App beforeUpdate.');
},
updated () {
console.log('App updated.');
},
beforeDestroy () {
console.log('App beforeDestroy.');
},
destroyed () {
console.log('App destroyed.');
},
beforeRouteEnter (to, from, next) {
console.log('App beforeRouterEnter.');
next();
},
beforeRouteUpdate (to, from, next) {
console.log('App beforeRouterUpdate.');
next();
},
beforeRouteLeave (to, from, next) {
console.log('App beforeRouterLeave.');
next();
}
}
然後我們的 Route 可能先放幾個連結,我們要大亂鬥的順序大概是這樣:
Hello
進入 /hello
。Kitty
進入 /kitty
。HelloKitty
進入 /hello/kitty
。Hello.vue
,他必須要埋入 <router-view>
。Kitty.vue
。HelloKitty.vue
,他是 Hello
的子路由。console.log
來印出結果。我們的路由大概是這樣:
const router = new Router({
mode: 'history',
routes: [
{
path: '/hello',
name: 'Hello',
component: Hello,
beforeEnter: function (to, from, next) {
console.log('Hello beforeEnter.');
next();
},
children: [
{
path: 'kitty',
name: 'HelloKitty',
component: HelloKitty,
beforeEnter: function (to, from, next) {
console.log('HelloKitty beforeEnter.');
next();
}
}
]
},
{
path: '/kitty',
name: 'Kitty',
component: Kitty,
beforeEnter: function (to, from, next) {
console.log('Kitty beforeEnter.');
next();
}
}
]
});
首先我們先看 App.vue
剛進入首頁的時候:
接著我們進入了 Hello 這個路徑:
再來我們前往 Kitty 這個路徑:
最後我們到 HelloKitty 這個子路徑:
如果我們有一個外部連結,我們試著點擊一下外部連結看看:
根據上面的結果,我們來看看到底這些 Hooks 的執行狀況跟順序是如何。首先,我們從 App.vue
這個最根元件的進入點來看,他只做了四件事情:
beforeCreate
created
beforeMount
mounted
雖然你有在 App.vue
埋入 Router 相關的 Hooks,但是其實是不會被執行的。原因在於,App.vue
本身是所有程序的進入點,而且並沒有符合整個根目錄的任何元件或是執行方法。所以,App.vue
自身是沒有辦法使用 Router 的 Hooks 方法的。
接著,我們看看點擊了 Hello 發生了什麼事情:
beforeEnter
beforeRouteEnter
beforeUpdate
beforeCreate
created
beforeMount
mounted
updated
你可以注意到 App.vue
的 beforeUpdate
與 update
是夾在 Hello.vue
的 Router 與元件的 Hooks 中間,這一點請務必留意。當你在 App.vue
有執行更新的時候,請確認你更新的資料是否跟 Hello.vue
有關,否則可能會拿不到相關的資訊。
然後,我們這次再點擊 Kitty 這個路徑,看看他的執行結果:
beforeRouteLeave
beforeEnter
beforeRouteEnter
beforeUpdate
beforeCreate
created
beforeMount
beforeDestroy
destroyed
mounted
updated
這次要留意的地方,在於 Hello.vue
的 beforeDestory
與 destroyed
是會發生於 Kitty.vue
的 created
, beforeMount
之後 ,且在 mounted
之前 ,這意味著什麼呢?
假設,你在 Kitty.vue
的 created
有做了一些 全域事件 綁定,無論你是使用 EventBus 爾或者是綁定在 window
上面,舉例來說:
window.addEventListener('scroll', function () { ... }, false);
然後,假設你的 Hello.vue
的 created
也有類似的作法,而你在 Hello.vue
的 beforeDestroy
也很乖的將這個事件解除,例如:
window.removeEventListener('scroll');
那麼,依照上述的邏輯,可怕的事情就發生了。你在 Kitty.vue
的 created
所綁定的事件,由於 Hello.vue
的 beforeDestroy
比 Kitty.vue
的 created
慢 執行的關係,所以你所綁定的事件,就這樣被清除掉了。
綁一次不行,你可以綁兩次(欸不對)。
所以,你在解除綁定的時候,不建議使用上述的方式,無論是原生的 removeEventListener
爾或是 EventBus 的 $off
都一樣,最好是使用指定監聽函式( 相同的記憶體位址 )的解除方式。注意,同樣的匿名函式不代表有著同樣的記憶體位址。
請認真閱讀 Kuro 的文章 重新認識 JavaScript: Day 14 事件機制的原理
接著我們來看看,再切換到 HelloKitty 會發生什麼事情:
beforeRouteLeave
beforeEnter
beforeEnter
beforeRouteEnter
beforeRouteEnter
beforeUpdate
beforeCreate
created
beforeMount
beforeCreate
created
beforeMount
beforeDestroy
destroyed
mounted
mounted
updated
看起來超複雜的 ,沒關係你現在放棄還來得及(欸)。我們這邊會發現,由於 HelloKitty.vue
是屬於 Hello.vue
的子路由,所以從上一次的 Hello.vue
的順序當中,又會穿插了 HelloKitty.vue
的方法。
我們這邊插花一下,如果我都是點擊 Hello.vue
的子路由,例如說,我們有一個叫做 HelloWorld 的子路由,跟 HelloKitty 同一層,我們的點擊順序是:
Hello
。Hello > Kitty
。Hello > World
。beforeRouteUpdate
beforeEnter
beforeRouteEnter
beforeUpdate
beforeUpdate
beforeCreate
created
beforeMount
mounted
updated
updated
beforeRouteLeave
beforeRouteUpdate
beforeEnter
beforeRouteEnter
beforeUpdate
beforeUpdate
beforeCreate
created
beforeMount
beforeDestroy
destroyed
mounted
updated
updated
在這裡你會發現,比起從其他路徑進來的過程,如果都是在同一個父層級路由當中變換,那麼 Hello.vue
的 mounted
就不會再被觸發,且,Hello.vue
的 beforeRouteUpdate
會在每次每個子路由離開( beforeRouteLeave
)後,銷毀之前執行。
最後,我們按下外部連結,他連去了 Google,然後你會發現,所有的 Hooks 都不會運作。是的,連同什麼 beforeDestroy
還是 destroyed
都不會觸發。關於這一點請大家特別留意。
接著,我們來看一下「上一頁」這個動作會發生什麼事情?我們從剛剛的順序逆向操作:
從這一連串的操作當中,你可以簡單的(?)發現,執行順序大致上沒有太大落差,只是元件的順序有所差異。所以,你在 Router 與生命週期所提供的 Hooks 當中,你必須要理解到這些順序的差異。特別是你有依賴的事件或是元件的時候,必須要特別小心。
另外,Router 所提供的 Hooks,如果不是屬於 Routes 的元件的話,他是不會被觸發的。所以說,如果你在 HelloKitty.vue
當中,引入一個叫做 NotKitty.vue
元件,倘若這個元件 不屬於 你的 Routes 設定元件,那麼你就無法使用 Router 的 Hooks,應該說,你寫了也無效。
import Kitty from '@/components/Kitty.vue';
export default {
name: 'Hello',
components: {
Kitty
},
data () {
return {
foo: '爽 2'
}
},
// 後略...
}
export default {
name: 'Kitty',
data () {
return {
foo: 'Kitty'
}
},
beforeRouteEnter (to, from, next) {
console.log('Kitty beforeRouterEnter.');
next();
},
beforeRouteUpdate (to, from, next) {
console.log('Kitty beforeRouterUpdate.');
next();
},
beforeRouteLeave (to, from, next) {
console.log('Kitty beforeRouterLeave.');
next();
},
// 後略...
}
當然,順序很重要,就是因為這個順序很重要,所以你必須要留意許多可能會爆炸的地方:
beforeCreate
, beforeMount
, created
順序 父元件 > 子元件。mounted
順序 子元件 > 父元件。beforeCreate
created
beforeMount
mounted
beforeEnter
beforeRouteEnter
beforeRouteLeave
。/hello/kitty
列舉如下:
Hello.vue
的 beforeEnter
。HelloKitty.vue
的 beforeEnter
。App.vue
的系列動作:
beforeCreate
。created
。beforeMount
。mounted
。Hello.vue
的 beforeRouteEnter
。HelloKitty.vue
的 beforeRouteEnter
。App.vue
的 beforeUpdate
。Hello.vue
與 HelloKitty.vue
與其他元件系列動作:
beforeCreated
。created
。beforeMount
。mounted
,內部元件理論上會優先。App.vue
的 updated
。「重新整理畫面」的路由執行順序
看到這邊應該想放棄了吧?
放棄雖然可恥,但是有用!
如果說,你覺得這邊已經是很複雜的地方了。那麼恭喜你,可以慢慢放棄沒有關係(欸)。因為在接下來的篇章裡面,我們會來聊聊比較特別的生命週期的狀況,那種你覺得他有 mounted
但是又沒有 mounted
的情況。
薛丁格的生命週期,敬請期待 (可以不要嗎)。