VueRouterのナビゲーションガードのto, from, nextに型を当てる

こんにちは!kossyです!



さて、今回はVueRouterでナビゲーションガードを実装するときのto, form, nextに型を当てる方法について、
ブログに残してみたいと思います。



環境

@vue/cli 4.5.9
vue 3.0.5
node 14.15.0
npm 6.14.8




状況

以下のようなrouter/index.tsファイルがあるとします。

import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import Home from '@/views/Home.vue'
import Login from '@/views/Login.vue'
import { authorizeToken } from '@/router/guards'

const routes: Array<RouteRecordRaw> = [
  {
    path: '/',
    name: 'Home',
    component: Home,
    meta: { requiresAuth: true }
  },
  {
    path: '/login',
    name: 'Login',
    component: Login,
    meta: { requiresNotAuth: true }
  }
]

const router = createRouter({
  history: createWebHistory(process.env.VUE_APP_BASE_URL),
  routes
})
router.beforeEach(authorizeToken)

export default router

authorizeToken関数の中身は以下のような感じ

// src/router/guards.ts

import { confirmationToken } from '@/api/auth'

export const authorizeToken = (to, from, next) => {
  if (to.matched.some((record) => record.meta.requiresAuth)) {
    confirmationToken()
      .then(() => {
        next()
      })
      .catch(() => {
        next({path: '/login'})
      })
  } else if (to.matched.some((record) => record.meta.requiresNotAuth)) {
      confirmationToken()
        .then(() => {
          next({ path: '/'})
        })
        .catch(() => {
          next()
        })
  }
}

authorizeTokenは、このままだと引数のto, from, nextに型推論が効かず、型安全なコードではありません。

この3つの引数に型を当ててみたいと思います。


vue-routerから型をimportして当てる

結論はタイトル通りなのですが、vue-routerのd.tsファイルを見に行ってみましょう。

// node_modules/vue-router/dist/vue-router.d.ts

/**
 * Navigation guard. See [Navigation
 * Guards](/guide/advanced/navigation-guards.md).
 */
export declare interface NavigationGuard {
    (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext): NavigationGuardReturn | Promise<NavigationGuardReturn>;
}

上記を見ると、toおよびfromにはRouteLocationNormalizedを、nextはNavigationGuardNextを当てればよさそうです。

型の中身を見てみます。

/**
 * Common properties among all kind of {@link RouteRecordRaw}
 * @internal
 */
export declare interface _RouteRecordBase extends PathParserOptions {
    /**
     * Path of the record. Should start with `/` unless the record is the child of
     * another record.
     *
     * @example `/users/:id` matches `/users/1` as well as `/users/posva`.
     */
    path: string;
    /**
     * Where to redirect if the route is directly matched. The redirection happens
     * before any navigation guard and triggers a new navigation with the new
     * target location.
     */
    redirect?: RouteRecordRedirectOption;
    /**
     * Array of nested routes.
     */
    children?: RouteRecordRaw[];
    /**
     * Aliases for the record. Allows defining extra paths that will behave like a
     * copy of the record. Allows having paths shorthands like `/users/:id` and
     * `/u/:id`. All `alias` and `path` values must share the same params.
     */
    alias?: string | string[];
    /**
     * Name for the route record.
     */
    name?: RouteRecordName;
    /**
     * Before Enter guard specific to this record. Note `beforeEnter` has no
     * effect if the record has a `redirect` property.
     */
    beforeEnter?: NavigationGuardWithThis<undefined> | NavigationGuardWithThis<undefined>[];
    /**
     * Arbitrary data attached to the record.
     */
    meta?: RouteMeta;
}

上記がそれぞれの型の共通の型でした。

一通り型の仕様について理解できたので、型定義を追加しましょう。

import { confirmationToken } from '@/api/auth'
import { NavigationGuardNext, RouteLocationNormalized, RouteRecordNormalized } from 'vue-router'

export const authorizeToken = (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => {
  if (to.matched.some((record: RouteLocationNormalized) => record.meta.requiresAuth)) {
    confirmationToken()
      .then(() => {
        next()
      })
      .catch(() => {
        next({path: '/login'})
      })
  } else if (to.matched.some((record: RouteLocationNormalized) => record.meta.requiresNotAuth)) {
      confirmationToken()
        .then(() => {
          next({ path: '/'})
        })
        .catch(() => {
          next()
        })
  }
}

これで型推論が効くようになったと思うので、マウスオーバーしてみます。

f:id:kossy-web-engineer:20210113181233p:plain

無事効いていました。




勉強になりました。



大いに参考にさせていただいたサイト

この場を借りて御礼を申し上げます。
vue-router/router.d.ts at dev · vuejs/vue-router · GitHub
ナビゲーションガード | Vue Router