Vue2 Composition API でのVue Router3系の使い方

Route系の動きを制御する際に、Vue3のComposition APIでは以下のようなやり方をとっています。
しかし、これが使えるのはVue Routerの4.x系以降になります。プロジェクトによっては事情があって3系を使い続けているという場合もあるかと思いますので対処法をまとめました。
また、今後4.x系に移行するぜ！という方にもおすすめできるように`import`の`from`を以降を書き換えるだけで移行が完了するようにしております。

```ts
import { useRouter, useRoute } from 'vue-router'

export default {
  setup() {
    const router = useRouter()
    const route = useRoute()

    function pushWithQuery(query) {
      router.push({
        name: 'search',
        query: {
          ...route.query,
        },
      })
    }
  },
}
```



## 対処したいこと
対処したい事柄は基本的に以下二つになるのかなと思います。
両方ともvue-router@3系のままではストレートにsetup()な書き方はできません。

1. `useRouter()`, `useRoute()`が使いたい
1. ナビゲーションガードはどうすんねん

一つ一つ解消していきます。

## 1. `useRouter()`,`useRoute()`が使いたい

`useRouter`と`useRoute`をexportするプラグインを作成しましょう。
`plugins`とか`composable`の中にいれて利用しましょう。

```router.ts
import {
  ComponentInternalInstance,
  getCurrentInstance,
} from "@vue/composition-api";
import VueRouter, { NavigationGuard, Route } from "vue-router";

/**
 * 現在のインスタンスを取得する
 * setup関数内で呼ばれているかの確認のためにこのメソッドをかませる
 * @returns インスタンス
 */
function getInstance(): ComponentInternalInstance {
  const instance = getCurrentInstance();

  if (!instance) {
    throw new Error(`Should be used in setup()`);
  }

  return instance;
}

/**
 * 4.x系で使えるuseRouterの代替メソッド
 * setup()関数内でのみ使用可能
 * @returns Routerオブジェクト
 */
export function useRouter(): VueRouter {
  // インスタンスにアクセス
  const instance = getInstance();

  // proxyが従来の`this`にあたるコンポーネントインスタンス
  return instance.proxy.$router;
}

/**
 * 4.x系で使えるuseRouteの代替メソッド
 * setup()関数内のみで使用可能
 * @returns Routeオブジェクト
 */
export function useRoute(): Route {
  // インスタンスにアクセス
  const instance = getInstance();

  // proxyが従来の`this`にあたるコンポーネントインスタンス
  return instance.proxy.$route;
}
```

### 使うとき

置き場所から`import`したらvue-router@4系での利用方法と全く一緒です。
後々移行しようかな〜と考えているのならばfrom以降を変えるだけなのでラクチン

```App.vue
<script lang="ts">
import { defineComponent } from "@vue/composition-api";
import { useRouter, useRoute } from "@/plugins/router.ts"

export default defineComponent({
  name: "xxx",
  setup() {
    const router = useRouter()
    const route = useRoute()

    // この下で好きなように利用
  },
});
</script>
```

## ナビゲーションガードはどうすんねん

`onBefoureUpdate`などのナビゲーションガードをsetup()内で利用するのはどうするのか。
同じように、てかさっきと同じファイルに書いてあげます

`getCurrentInstance()`部分はさっき作成した`getInstance()`に置き換えるのがいいと思います。

```router.ts
import { getCurrentInstance } from "@vue/composition-api";
import { NavigationGuard } from "vue-router";
import Vue from "vue";
import { ComponentOptions } from "vue/types/umd";

/**
 * ナビゲーションガード系メソッドを生やす為のメソッド
 * @param name ナビゲーションガード名
 * @param callback コールバック
 */
function onHook(
  name: keyof ComponentOptions<Vue>,
  callback: NavigationGuard<Vue>
): void {
  const vm = getCurrentInstance();
  const merge = Vue.config.optionMergeStrategies[name];
  if (vm && merge) {
    const prototype = Object.getPrototypeOf(vm.proxy.$options);
    prototype[name] = merge(vm.proxy.$options[name], callback);
  }
}

/**
 * 4.x系で使えるonBeforeRouteUpdateの代替メソッド
 * @param callback ナビゲーションガードコールバック関数
 */
export function onBeforeRouteUpdate(callback: NavigationGuard<Vue>): void {
  return onHook("beforeRouteUpdate", callback);
}

/**
 * 4.x系で使えるonBeforeRouteLeaveの代替メソッド
 * @param callback ナビゲーションガードコールバック関数
 */
export function onBeforeRouteLeave(callback: NavigationGuard<Vue>): void {
  return onHook("beforeRouteLeave", callback);
}
```

### 使い方

使い方は4.x系の公式と同じなのでそちらをみてください

https://next.router.vuejs.org/guide/advanced/composition-api.html#navigation-guards

## 4.x系に移行するには

vue-routerの4系をいれて、

```bash
> npm i vue-router@4
```

`from`以降を置き換えるだけ
ナビゲーションガードも同じ

```App.vue
<script lang="ts">
import { defineComponent } from "@vue/composition-api"; 
// import { useRouter, useRoute } from "@/plugins/router.ts"
// ↓に置き換え
import { useRouter, useRoute } from "vue-router"

export default defineComponent({
  name: "xxx",
  setup() {
    const router = useRouter()
    const route = useRoute()

    // この下で好きなように利用
  },
});
</script>
```