Vue2.7にバージョンアップする際はcomposition-apiのroot(特にvue-router)に注意!
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 を経由しなくても useRoute
・useRouter
を使って 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 では各コンポーネントで useRoute
・useRouter
が提供されており、使う側はインスタンスにアクセスする必要がありません。
しかし root から取れるのでと vue-router@3 ではこの関数が提供されていませんでした。
そこで先日このような Issue が立てられ、Vue2 系の vue-router@3 においても同様に useRoute
・useRouter
を提供しようという動きがありました。
そしてつい昨日、この 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()メソッドを使うことでインスタンスにアクセスでき useRoute
・useRouter
を仮に自作できます。
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;
}
Discussion