🎃

Vue2.7にバージョンアップする際はcomposition-apiのroot(特にvue-router)に注意!

2022/08/24に公開

Vue2.6 のプロジェクトを Vue3 へとマイグレーションしている段階で先日 Vue3 の環境にかなり近づけた Vue2 最新のVue2.7がリリースされました。

Vue2.6 までは @vue/composition-api を使うことで Vue2 でも Composition API 記法を使うことができました。
しかし Vue2.7 以降はネィティブで Composition API 記法がサポートされ @vue/composition-api を使う必要がなくなりました。
そして script setup 記法もサポートされました。

結論

Vue2 向け Vue Router の新しいバージョンがリリースされたから、root を経由しなくても useRouteuseRouter を使って VueRouter のメソッドを呼び出せるよ!

背景

Vue2.6 以前で@vue/composition-api を利用している時は setup 関数の第二引数(context)のパラメータとして root をとることができました。
この root にアクセスすることで vue のインスタンスにもアクセスできるため、vue-router[1]等で使われているカスタムインスタンスプロパティ($ 表記のメソッド)にアクセスできました。

例)

export default defineComponent ({
  setup(_, { root }) {
    root.$router.push('/home') // vue-routerのpushメソッド
    root.$store.dispatch('action') // vuexのdispatchメソッド
    root.$axios.get('/api') // axiosのgetメソッド
  }
})

@vue/composition-apiからのVue2.7への移行に伴う注意点

しかし、Vue2.7 以降及び Vue3 以降においては root が引数から削除されており、インスタンスに直接アクセスできなくなりました。
そのため、他の方法でメソッドを呼び出す必要があります。
上記の例であれば、store だと Vuex.Store を store module として Export することで、そのまま各コンポーネント内で import して使うことができます。

VueRouterの場合

Vue3 対応の vue-router@next では各コンポーネントで useRouteuseRouter が提供されており、使う側はインスタンスにアクセスする必要がありません。
しかし root から取れるのでと vue-router@3 ではこの関数が提供されていませんでした。

そこで先日このような Issue が立てられ、Vue2 系の vue-router@3 においても同様に useRouteuseRouter を提供しようという動きがありました。

https://github.com/vuejs/vue-router/issues/3760

そしてつい昨日、この Issue に対して Pull Request がマージされ、Eduardo 氏曰く最後のマイナーバージョンとなるvue-router@3.6がリリースされました。

これにより、@vue/composition-api / Vue2.7 に限らず Vue2 でも root を使わないで VueRouter のメソッドを呼び出すことができるようになりました。

例)

import { useRouter, useRoute } from 'vue-router/composables'

export default defineComponent ({
  setup() {
    const router = useRouter()
    const route = useRoute()
    router.push('/home')
    const currentId = route.params.id
  }
})

ただ依存関係の問題でどうしても vue-router の version を上げることができないという方へ、vue-router が 2.7 をサポートするまで利用していた妥協案を残しておきます。
そういう場合は vue の getCurrentInstance()メソッドを使うことでインスタンスにアクセスでき useRouteuseRouter を仮に自作できます。
current を参照しているだけですので実際に Route オブジェクトの監視はできず、不安定挙動を示す恐れがあるため動作の保証はできませんが一応この方法でも自分の環境では動きました。

import { computed, reactive, getCurrentInstance } from "vue";
import VueRouter, { Route } from "vue-router";

declare module "vue/types/vue" {
  interface Vue {
    $router: VueRouter;
    $route: Route;
  }
}

export const useRouter = () => {
  const instance = getCurrentInstance();
  if (!instance) return {} as VueRouter;
  return instance.proxy.$router;
}

export const useRoute = () => {
  const instance = getCurrentInstance();
  if (!instance) return {} as Route;
  const currentRoute = computed(() => instance.proxy.$route);

  const protoRoute = Object.keys(currentRoute.value).reduce((acc, key) => {
    acc[key] = computed(() => currentRoute.value[key]);
    return acc;
  }, {});

  return reactive(protoRoute) as Route;
}
脚注
  1. Vue Composition API と Vue 3 での実装方法の違いについて - Vue Composition API で URL遷移をする場合 ↩︎

GitHubで編集を提案

Discussion