vue-routerのafterEachグローバルフックがhookされるタイミングとonReadyがhookされるタイミング

vue-routerにはonReadyというフックが用意されている

ドキュメントを読めばわかるように、これは when the router has completed the initial navigation, which means it has resolved all async enter hooks and async components that are associated with the initial route. 、つまり最初に表示するルートに紐付けられたbeforeEnter ナビゲーションガードやbeforeEachglobalナビゲーションガードが解決され、「どのrouteが実行されるべきか」が解決されたあとに呼ばれる。

これが便利になるのはSSRのときで、preloadしたいデータがあるような場合はbeforeEnter内で非同期読み込みし、その読み込みが終わったらnextするようにしておき、onReadyのタイミングでSSRすればよい、というような使いかたができるわけだ。

ところで、ナビゲーションガードが解決されたあとに呼ばれるhookはもうひとつある。それがafterEachグローバルhookだ。

このafterEachというhookとonReadyhookはどちらが先に呼び出されるのだろう。ドキュメントを読んでもよくわからなかったので、ソースを追った。

まず、onReadyで登録したhookを実際に呼び出しているのはここである。

  transitionTo (location: RawLocation, onComplete?: Function, onAbort?: Function) {
    const route = this.router.match(location, this.current)
    this.confirmTransition(route, () => {
      this.updateRoute(route)
      onComplete && onComplete(route)
      this.ensureURL()

      // fire ready cbs once
      if (!this.ready) {
        this.ready = true
        this.readyCbs.forEach(cb => { cb(route) })  // ココね!!!ここ!!!
      }
    }, err => {
      if (onAbort) {
        onAbort(err)
      }
      if (err && !this.ready) {
        this.ready = true
        this.readyErrorCbs.forEach(cb => { cb(err) })
      }
    })
  }

で、その前にコールされてるthis.updateRoute(route)の中身見ると

  updateRoute (route: Route) {
    const prev = this.current
    this.current = route
    this.cb && this.cb(route)
    this.router.afterHooks.forEach(hook => {
      hook && hook(route, prev)
    })
  }

ここでafterHooksを呼んでいる。というわけで、onReadyよりも先にafterEatchのhookが呼ばれるようになっていることが確認できた。

注意すべき点として、(あたりまえだが)afterEachは同期的に呼び出されているので、その中で非同期な操作を行った場合、その非同期操作の完了を待たずにonReadyhookが呼び出されるので、onReadyにhookしてSSRしても、afterEach内で行った非同期操作の結果はSSRされない。