こんにちは!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() }) } }
これで型推論が効くようになったと思うので、マウスオーバーしてみます。
無事効いていました。
勉強になりました。
大いに参考にさせていただいたサイト
この場を借りて御礼を申し上げます。
vue-router/router.d.ts at dev · vuejs/vue-router · GitHub
ナビゲーションガード | Vue Router