🎃

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

2022/08/24に公開約3,100字

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(deprecated)をとることができました。
この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することで、axiosであればそのまま各コンポーネント内で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

ログインするとコメントできます