2022年の最新標準!
Vue 3の新しい開発体験に触れよう

112
83
33

2021年のVue.jsは新しいVue 3のコアが安定し、開発環境からライブラリやコードの書き方まで、新しい発表の多い一年でした。ICSではすでに複数のプロジェクトでVue 3やVite等の新しいフレームワーク・ツールを使用していますが、まだまだ様子見という方も多いでしょう。

変化の大きいVue 3の周辺ですが、2021年11月のVueConf Toronto 2021(セッション動画)でようやく次の定番と言える構成がアナウンスされました。この記事では、Vite・cteate-vue・<script setup>・Piniaといった新しい推薦構成を紹介し、Vue 2時代から何が良くなるのかを比較します

新しい構成は何が良くなる? メリットを確認

新しい構成ではプロジェクトを作成する際のコマンドラインツールからVS Codeの機能拡張やコードの書き方まで、さまざまな部分が変わっています。個別のツールや機能を見て行く前に、全体として何が良くなるのか、メリットを確認しましょう。

この記事では比較用にVue 2とVue 3.2で同じ「◯×ゲーム」を実装したサンプルプロジェクトを用意しました。お時間のある方はぜひ手元で表示・実行して違いを確認してください。

※ すべてをひとまとめにすることは正確性を欠く面もありますが、この記事ではわかりやすさを優先し、ツール群や書き方の選択をまとめて「構成」と表現します。なお、この記事で「新構成」や「Vue 3の構成」として解説する要素の多くはVue 2にもバックポートされており、Vue 2系のプロジェクトでも導入可能です

Point1. コードがシンプルに:形式的なコードの削減で書きやすさとわかりやすさが向上

下の図はVue 2とVue 3.2で作成したごく簡単なコンポーネントを比較したものです。Vue 3.2で導入された<script setup>という記法を使うことで、形式的な記述(ボイラープレート)が減り、コードの行数やネスト(インデント)が減っていることがわかります。サンプルアプリでは全体のステップ数を約25%削減できています。

コンポーネント例とステップ数比較

また、冗長になりがちだったストア周りのコードも、軽量な状態管理ライブラリであるPiniaを使うことでよりスッキリと書けるようになっています。<script setup>とPiniaについては記事の後半でもう少し詳しく紹介します。

Point2. TypeScriptの全面サポート:<template>やストア周りの型チェックで開発効率と安全性が向上

TypeScriptの本格的なサポートも大きな進化です。Vue 2時代にはデフォルトでのTypeScriptのサポートは限定的でした。とくにVuexのストアを利用するコードで型チェックやコード補完を効かせるにはさまざまな工夫が必要だったため、苦労した経験をお持ちの方も多いでしょう。

新しい構成ではあらゆる場所でTypeScriptの型チェックやコード補完が効くようになります。とくに<template>の中でもチェックや補完が効くのは大きな進化です。難しい設定も不要なので、TypeScriptを使うハードルも大きく下がりました。

templateで型チェックが効く例

Point3. 開発速度・実行速度もUP:開発環境の起動時間から実行速度やファイルサイズまで、さまざまなパフォーマンスが向上

性能面の向上も見逃せません。開発ツール(バンドラー)のViteを使用することで開発時の待ち時間がほぼゼロに近づきます。

Viteについては以前の記事『jQueryからTypeScript・Reactまで! Viteで始めるモダンで高速な開発環境構築』でも解説していますが、その後Viteを使用したVueプロジェクトを作成してくれるcreate-vueが登場し、さらに簡単にプロジェクトを開始できるようになりました。

ビルド後のバンドルサイズも大きく改善しています。サンプルの◯×ゲームでは、Vue本体が軽量化されたことに加え、Vuexを軽量なPiniaに置き換えたことでサイズが約半分になっています。

ビルド結果とバンドルサイズの比較

また、サンプル程度では実感しにくい部分ですが、Vue本体の性能向上により実行時の速度やメモリ使用量も改善されています。

create-vueとVolarで快適な開発環境を作ろう

全体的なメリットがわかったところで、早速新しいプロジェクトを作成してみましょう。

create-vueでプロジェクトを作成

従来、Vueのプロジェクトを作成するにはVue CLIをグローバルインストールした上でvue createコマンドを使用すること一般的でした。

▼ Vue CLIで新しいVueプロジェクトを作成する例

# まずVue CLIをグローバルインストール
npm install -g @vue/cli

# Vue CLIで新規プロジェクトを作成
vue create my-vue2-project

# 対話式で構成を選択するとプロジェクトをセットアップしてくれる
Vue CLI v4.5.15
? Please pick a preset: (Use arrow keys)
  Default ([Vue 2] babel, eslint) 
  Default (Vue 3) ([Vue 3] babel, eslint) 
❯ Manually select features 

... 中略 ...

# 起動
cd my-vue2-project
npm run serve

新しいツールであるcreate-vueでは事前のグローバルインストールは不要です。単純にnpm init vue@3コマンドですぐにプロジェクトをセットアップできます。(名前から想像できるように、vue@2に変えればVue 2のプロジェクトも作れます)

▼ ✨create-vueで新しいVueプロジェクトを作成する例

# 新しいプロジェクトを作成
npm init vue@3 my-vue3-project

# Vue CLIと同様に対話形式で構成を選択
Vue.js - The Progressive JavaScript Framework

✔ Add TypeScript? … No / Yes
... 中略 ...

# モジュールをインストール&起動
cd my-vue3-project
npm install
npm run dev

選択肢はVue CLIと少々異なりますが、操作自体に迷うことはないでしょう。Vue CLIがwebpackを使用しているのに対し、create-vueで作成したプロジェクトはViteを利用します。Viteを使うことで開発時の起動や差分更新が極めて高速になり、開発体験が向上します。

※ ただし、Viteはビルドプロセスの拡張性でwebpackにやや劣ります。複雑なビルド処理が必要な場合は事前の検証をオススメします。

VS Codeの機能拡張Volarをインストール

VS Codeを使って開発をされている方のマシンであれば、Vue用の機能拡張としてVeturがインストールされていることが多いでしょう。Vue 3の新しい構成ではVeturの代わりにVolarを使うことが推薦されています。<script setup>のような新しい記法への対応や<template>部の型チェックなどさまざまなメリットがあるので、インストールしておきましょう。

ただし、VeturとVolarは機能が重複するため共存できません。完全に乗り換えるのが不安な場合には、ワークスペース(プロジェクト)ごとにVeturかVolarのどちらかだけが有効になるよう設定しておく必要があります。

新しいコンポーネントの書き方をみてみよう

Vue 2ではオブジェクトを作成し、data, computed, methodsといった所定のフィールドを埋めて行く形が一般的でした。この書き方をOptions APIと呼びます。

▼ Vue 2でOptions APIを使ってコンポーネントを定義する例

<script lang="ts">
import Vue from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default Vue.extend({
  components: { HelloWorld },
  data() {
    return {
      userName: 'Tarou'
    }
  },
  methods: {
    sayHello() {
      alert(`Hello ${this.userName}`)
    }
  }
})
</script>

Options APIはわかりやすい反面、コンポーネントが大きく複雑になりやすい問題を抱えていました。Vue 3では、新たにより柔軟な書き方としてsetup()関数を使用するComposition APIが導入されました。

▼ 同じコンポーネントをVue 3のComposition APIで定義した例

<script lang="ts">
import { defineComponent, ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

export default defineComponent({
  components: { HelloWorld },
  setup() {
    const userName = ref('Tarou')
    const sayHello = () => {
      alert(`Hello ${userName.value}`)
    }
    return { userName, sayHello }
  }
})
</script>

Composition APIのメリットや活用法は以前の記事『コンポーネントを小さく・きれいに設計しよう Vue Composition APIを活用したコンポーネント分割術』も参考にしてください。

Vue 3.2で採用された<script setup>はこのComposition APIをさらに簡潔に記述するための書き方です。使用するには<script>タグにsetup属性を追加します。

▼ ✨同じコンポーネントをVue 3.2の<script setup>で定義した例

<script setup lang="ts">
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

const userName = ref('Tarou')
const sayHello = () => {
  alert(`Hello ${userName.value}`)
}
</script>

export default Vue.extend()export default defineComponent()といった「お約束」の冗長な記述がなくなり、インデントも浅くなっています。他のコンポーネントを利用する時も、components: {...}の定義は不要になり、単にimportするだけで良くなります。

また、従来は型定義に難のあったpropsemitの書き方も改善されています。下のコードは◯×ゲームのひとつのマスを表すコンポーネントです。マスに表示する石を指定するstone propsと、マスをクリックした時に発火するplaceイベント(emits)を定義しています。

▼ ✨<script setup>propsemitを使用する例

<script lang="ts" setup>
import { Player } from '@/logics/GameType'

// propsの定義: 型定義やデフォルト値を指定できる
const props = withDefaults(
  defineProps<{
    stone?: Player | undefined
  }>(),
  {
    stone: undefined
  }
)

// emitsの定義: イベント名や引数の型を指定できる
const emit = defineEmits<{
  (e: 'place'): void
}>()

// クリック時にplaceイベントを発火させる
const cellClick = () => {
  if (props.stone) return
  emit('place')
}
</script>

ちょっと見慣れない書き方かもしれませんが、とくにTypeScriptを利用する場合に恩恵の大きい変更です。すでに日本語の公式リファレンスも整備されているので目を通しておくと良いでしょう。

Vuexの後継ライブラリのPiniaを試そう

Piniaの機能

PiniaVuexの後継となる状態管理(state management)ライブラリです。元々はVue 3のComposition APIをベースとした軽量な状態管理ライブラリとして実験的に開発されていたものですが、2021年12月にVueの公式プロジェクトになっています。🍍のロゴが可愛いですね。

Piniaはcreate-vueでプロジェクトを作る際、「Add Pinia for state management?」にYesで答えるだけでインストールと初期設定が行われます。もちろん既存のプロジェクトにnpm install piniaで追加しても構いません。

下の例はサンプルの◯×ゲームでゲームの盤面を管理するシンプルなストアのコードです。defineStore()関数でstateと、必要に応じてgettersactionsを定義するだけです。

▼ ✨Piniaを使って◯×ゲームのストアを定義する例

import { defineStore } from 'pinia'
import { createGame } from '@/logics/createGame'
import { placeStone } from '@/logics/placeStone'

export const useGameStore = defineStore({
  id: 'game',
  // ストアの初期状態を定義
  state: () => createGame(),
  // 状態を取得するgetter(computedに相当)
  getters: {
    isEnded: (state) =>
      state.winner !== undefined ||
      state.board.every((cell) => cell !== undefined)
  },
  // 状態を変更するaction(methodsに相当)
  actions: {
    newGame() {
      this.$reset()
    },
    placeStone(place: number) {
      this.$state = placeStone(this, place)
    }
  }
})

VuexではStateを変更するにはMutationを経由する必要がありましたが、PiniaではMutationを使わず、Actionから直接Stateを更新します。また、Piniaでは複数のストアを自由に定義して利用できるのも大きな違いです。VuexがFluxの考え方を基礎としているのに対し、Piniaはより現実的で負担のない状態管理を目指していると言えます。

ストアを利用するコンポーネントもみてみましょう。下のコンポーネントは◯×ゲームの盤面のコンポーネントです。現在の盤面を表示したり、勝敗を記録するために2つのストアを利用しています。

▼ ✨Piniaのストアを利用する◯×ゲームの盤面のコンポーネント(script部のみ)

import { computed, onMounted } from 'vue'
import { useGameStore } from '@/stores/game'
import { useScoreStore } from '@/stores/score'
import { playerName } from '@/logics/playerName'
import BoardCell from './BoardCell.vue'

// Piniaのストアを読み込む
const game = useGameStore()
const score = useScoreStore()

/** ゲームの状況を説明するメッセージ */
const message = computed(() => {
  if (game.isEnded) {
    return game.winner
      ? `${playerName(game.winner)}の勝ちです!`
      : '引き分けです。'
  }
  return `${playerName(game.currentTurn)}のターンです。`
})

/** マスに石を置いてゲームを進めます */
const placeStone = (cellIndex: number) => {
  // すでにゲームが終わっているならそれ以上置かせない
  if (game.isEnded) return
  game.placeStone(cellIndex)
  // 勝者が決まったら勝敗を記録
  const winner = game.winner
  if (winner) {
    score.addWinner(winner)
  }
}

// マウント時に盤面を初期化
onMounted(() => game.newGame())

Vuexを使い慣れている方からすると、これはストアを使うコードには見えないかもしれません。 PiniaではVuexでお馴染みのdispatchcommitは登場しません。ストアの変更は単に定義したメソッドを呼び出すだけです。同様にストアからの読み出しも単純なプロパティアクセスになります。

Composition APIを触ったことのある方は気づかれると思いますが、実際のところ、多くのケースでPiniaのストアは実質ただのComposition関数(composable)です。Vue標準の機能を利用することでライブラリ独自の書き方をなくし、コンパクトな作りを実現しています。Vuexとくらべて気軽に導入できるライブラリと言えるでしょう

一方、公式のライブラリとなったことで、Vue.js devtools等のツールではVuexと遜色のないサポートが利用できるようになりました。

devtoolsとPinia

Fluxベースの厳格な状態管理を行う場合には引き続きVuexを利用し、それ以外のケースでは積極的にPiniaの利用を検討するのが良いでしょう。

新しい構成に移行すべき?

新しい構成ではツールやライブラリからコンポーネントの書き方までさまざまな要素が変わっているため、古くからVueを使っている方は不安に感じるかもしれません。気になるポイントをQ&Aでまとめました。

Q. 古いやり方はこの先使えない? → A. NO:今までのやり方も引き続きOK

この記事で紹介してきた通り、新しい構成はコードがシンプルになり、開発効率の向上が期待できます。新規のプロジェクトであればできる限り新しい構成を採用するのが良いでしょう。一方で、今までのツールや書き方がすぐに廃れることはないので、既存プロジェクトを無理に移行する必要はありません。

最初に紹介した通り、新構成のツール等はほとんどがVue 2にもバックポートされており、自由に組み合わせて利用できます。たとえば、Vue 2のプロジェクトにPiniaを入れたり、Vue 3のプロジェクトで従来のOptions APIを使うこともなんら問題はありません。プロジェクトごとに必要な部分だけ、無理なく更新して行くのが良いでしょう。

Q. Vue 3への切り替えは必須? → A. NO:ただし必要に応じて移行を見越しておくのが吉

Vue 3のリリース後もVue 2は引き続きメンテナンスされているので、無理に移行する必要はありません。2022年には少なくなると思われますが、IE11対応が必要な場合やプラグインが未対応の場合など、Vue 2で新規プロジェクトを立ち上げることもあるでしょう。

PiniaやComposition APIはVue 2系でも使えるので、できる範囲で導入しておくと将来の移行や次のプロジェクトを立ち上げるときの負担を少なくできるかもしれません。

Q. TypeScriptは必須? → A. 必須ではないがオススメ

Vue 2時代と比べ、Vue 3ではTypeScriptを使う際の問題の多くが改善されています。引き続きJavaScriptを選択することもできますが、新しい構成の恩恵を十分に受けるにはTypeScriptを利用する方が良いでしょう。

Q. NuxtやVuetifyは使える? → A. 出揃いつつあるが、ライブラリ・フレームワークごとに確認が必要

Vue 3への移行が進みづらかった大きな要因のひとつが周辺ツールやライブラリの対応でした。とりわけNuxtやVuetifyはよくネックとして上がっていましたが、Nuxtは2021年10月にVue 3対応のβ版がリリースされ、Vuetifyも2022年3月にVue 3対応版がリリースされる予定です。ただし、小規模なライブラリやツールにはVue 3対応が未定のものもあるため、必須のものについては個別に確認が必要でしょう。

まとめ:2022年はようやく「本気で」Vue 3が使える年に

ICSではVue 3の正式リリース前からComposition API等の新しい技術を積極的に導入してきました。移行期には若干の混乱や不安もありましたが、ようやく周辺ツールも含めてVue 3で安心して効率的に開発できる環境が出揃ったと感じています。

実務ですぐに使えない方も、まずはサンプルプロジェクトをクローンしてお手元で新しい環境を試してみてください。

松本 ゆき

フロントエンドエンジニア。SIer&UXコンサルタントからフロントエンドエンジニアに転身。新しいアイデアを企画段階からプロトタイピングしていくことが得意です。趣味はお絵かきと開発。

この担当の記事一覧