Link and Motivation Developers' Blog

リンクアンドモチベーションの開発者ブログです

Vue 2の開発を出来るだけVue 3に近づけるための基本テクニック

こんにちは、リンクアンドモチベーションのプラットフォームチームの菊池です。

普段はアプリケーション開発のプロジェクトマネジメントをしたり、横断でフロントエンドを見ていたりします。

さて先日、長らくnextバージョンであったVue 3が2022年2月7日からデフォルトバージョンになることが発表されました。

https://blog.vuejs.org/posts/vue-3-as-the-new-default.html

公式が推奨するビルドツールや状態管理ライブラリもViteやPiniaに変わって新時代って感じですね。

一方でまだVue 2を現役で使っているプロダクトも多いと思います。

弊社のフロントエンドも全てVue 2で書かれており、ブラウザ互換を保つため、最近開発をしたアプリケーションでもVue 2(Nuxt) + TypeScriptの構成を取っています。

が、すぐにVue 3にアップデートできない状況であっても工夫次第でかなりVue 3に近づけることは可能です。

今回は、Vue 2の構成でも出来るだけVue 3の体験を得るために実際に使っている基本テクニックを紹介したいと思います。

@vue/composition-apiを入れる

https://github.com/vuejs/composition-api

まずはこれを入れないと始まりません。

既にVue 2プロジェクトで採用している方も多いと思います。

私の開発しているVue 2アプリケーションでも@vue/composition-apiを入れて以下のようなComposition APIスタイルでコンポーネントを定義しています。

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

const getSomeApi = () => Promise.resolve('some api...');

// APIを叩いて結果orエラーをセットするComposition
// 例示のためファイル内に直接書いてます
const useSomeApi = () => {
  const result = ref<string | undefined>();
  const error = ref<string | undefined>();
  getSomeApi()
    .then(v => { result.value = v })
    .catch(e => { error.value = e.message });
  
  return { result, error };
}

// コンポーネント
export default defineComponent({
  setup() {
    const { result, error } = useSomeApi(); 
    
    return { result, error };
  }
});
</script>

<template>
  <div>
    <p v-if="error">{{ error }}</p>
    <p v-else>{{ result }}</p>
  </div>  
</template>

Vue 3のcomposition APIと全く同じではなく、多少制限はありますが、半年以上開発していて大きな問題を感じたことはありません。

getCurrentInstance()でthisにアクセスする

@vue/composition-apisetup()な書き方が出来るようになりますが、setup()内では今までのようにthisにはアクセスできません。

// 従来のOptions APIの書き方
// `this`はVueインスタンスなので、インスタンスに登録した`$router`などにアクセスできる
export default Vue.extend({
  mounted() {
    this.$router.push('/');
  }
})

// Composition APIでの書き方
// `this`コンテクストがないので、今まで通りの方法では記述できない
export default defineComponent({
  setup() {
    onMounted(() => {
      this.$router.push('/');
    })
  }
});

ここで使えるのが、@vue/composition-apiが提供している getCurrentInstance()です。このAPIを使うことでコンポーネントインスタンスにアクセスすることが出来ます。

試しに、上記サンプルのようにvue-routerを取得するCompositionを作成してみます。

import { getCurrentInstance } from '@vue/composition-api';

export function useRouter() {
  // インスタンスにアクセス
  const instance = getCurrentInstance();
  
  // `setup()`外で使用するとインスタンスは取得できません
  // https://v3.vuejs.org/api/composition-api.html#getcurrentinstance
  if (!instance) {
    throw new Error(`Should be used in setup().`);
  }
  
  // proxyが従来の`this`にあたるコンポーネントインスタンス
  return instance.proxy.$router;
}

あとはインポートして使うだけです。

import { defineComponent, onMounted } from '@vue/composition-api';
import { useRouter } from '~/composables/use-router';

export default defineComponent({
  setup() {
    onMounted(() => {
      const router = useRouter();
      router.push('/');
    });
  }
});

なお、サンプルで行っているvue-routerへのアクセスですが、Nuxtでは@nuxtjs/composition-apiが同じ機能useRouterを提供しているので、Nuxt環境ではこちらを使うと良いでしょう。

https://composition-api.nuxtjs.org/packages/routes/#userouter

Vue 2用のComposition APIライブラリを使う

先程のgetCurrentInstance()を使えばほとんどのthisプロパティは使えます。

が、これを全て用意するのも大変ですよね。

というわけで、ライブラリがVue 2用のAPIを提供しているか探してみると良いです。

たとえばVue.jsのi18nライブラリであるvue-i18nは、Vue 2用Composition APIライブラリを公開しています。

https://github.com/intlify/vue-i18n-composable

※ 慣習としてcomposableと付いていることが多いです

VueUse

https://vueuse.org/

また、Vue 2/3両方に対応しているComosition APIユーティリティセットがあります。

メディアクエリの判別やオフライン状態のチェックなど痒いところに手が届く機能が揃っているので、プロダクト環境ではもちろん、ちょっとしたプロトタイプを作るときにも便利です。

unplugin

正確にはVue 2/3に閉じた話ではありませんが、unpluginというビルドツールユニバーサルなプラグインを開発できるライブラリがあります。

これはViteやWebpack、Rollupといった様々なフロントエンドビルドツール用のプラグインを同じコードから作成してくれます。

そして、このunpluginをベースに公開されているライブラリはVue 2 or 3 + Vite or Vue CLI(Webpack)いずれの構成でも使用できます。

このような普遍的なライブラリを使うと、将来的にフレームワークやビルドツールの移行がしやすいだけでなく、プロダクトによって異なる構成を取っていても同じライブラリを使えるので、選択肢として面白いですね。

なお、ここに挙げたunplugin-iconsを使って社内用アイコンライブラリの開発をしたので、それはまた別の記事で紹介出来ればと思います!

まとめ

Vue 2環境をVue 3に近づける基本テクニックを紹介しました。

Vue 3にはTeleportSuspenseなど面白そうな機能があるので、Vue 3へのアップデートを並行して視野に入れながら、手元の開発体験も向上させていきたいですね。