らぼるてっく。

てっくてっく歩いてっく。

ラボルで実施した、Vue2→Vue3移行手順と注意点を公開します!

はじめに

こんにちは🧑‍💻 ラボルのUIデザイナー/フロントエンドエンジニアの寺岡です。

今回はVue開発者向けの記事になります。
Vue2のバグ修正・セキュリティアップデートが、2023年12月31日に終了することをご存知でしょうか?

ja.vuejs.org

これ受けて、ラボルのフロントエンドチームは、Vue2→Vue3にアップデートする移行作業をこの半年間実施していました。

今回の記事は、その移行作業の目的や手順、2→3の変更点など、これから作業する方に向けたものになります。是非ご一読ください。

目次

Vue3移行の目的

最も重要な目的は、上述している「バグ修正・セキュリティアップデートが、2023年12月31日に終了すること」がメインの目的ですが、ラボルでは他に以下のサブ目的も定義していました。

  • 機能の再利用を可能に
  • コードの可読性の向上

これらはVue3から使用可能なComposition APIの記述をすることによって得られる恩恵です。
Composition APIのメリットについて詳しく知りたい方は、以下の記事もオススメです!

blog.labol.co.jp

Vue2時点ではComposition APIがメインで提供されていなかったですが、Vue3ではOptions API・Composition API両方の選択肢があることは嬉しいですね。

Vue3移行の手順

ラボルでのVue3移行は、ざっくり以下のような手順で行いました。

  1. Vue3(新)プロジェクトを新規作成
  2. Vue2(旧)プロジェクトのコードを、Vue3プロジェクトにコピペ
  3. Vue3プロジェクトの方で、コピペしたコードをComposition APIの形にリファクタリング
  4. 既存仕様に変更がないことをテストしリリース

この手順を、ページ単位でのリリースを繰り返して行っていました。

1と2に関して、以下のようなイメージのディレクトリ構成になっています。
vue3-project/が新しく作成した環境になります。

.
├── vue2-project/
│   ├── assets
│   ├── components
│   ├── pages
│   └── lib
└── vue3-project/
    ├── assets
    ├── components
    ├── pages
    └── lib

3の部分を具体的にコード例で説明していきます。

Vue2(旧)コード

以下のようなページがあるとします。
※CSSは省略しています。

<template>
  <h1>{{ memberName }}さんのマイページ</h1>
  <p>申請可能額: ¥{{ member.availableCredit }}</p>
  <button @click="moveFormPage">申請ページへ</button>
</template>

<script>
export default {
  data: {
    // memberの情報
    member: {
      lastName: "",
      firstName: "",
      availableCredit: 0,
    },
  },
  computed: {
    memberName() {
      return member.lastName + member.firstName
    },
  },
  async created() {
    await this.getMemberInfo()
  },
  methods: {
    // member情報の取得
    async getMemberInfo() {
      const res = await axios.get("endpoint")
      this.member.lastName = res.data.lastName
      this.member.firstName = res.data.firstName
      this.member.availableCredit = res.data.availableCredit
    },
    // ページ遷移
    moveFormPage() {
      this.$router.push("/mypage/form")
    },
  },
}
</script>

Vue3(新)コードでリファクタリング

上記のコードをコピペし、Composition APIの形に書き換えます。
※Options APIのままでも、問題はないです。

<template>
  <h1>{{ memberName }}さんのマイページ</h1>
  <p>申請可能額: ¥{{ member.availableCredit }}</p>
  <button @click="moveFormPage">申請ページへ</button>
</template>

<script setup>
import { reactive, computed } from "vue"

// memberの情報
const member = reactive({
  lastName: "",
  firstName: "",
  availableCredit: 0,
})
const memberName = computed(() => {
  return member.lastName + member.firstName
})

// member情報を取得
const getMemberInfo = async() => {
  const res = await axios.get("endpoint")
  member.lastName = res.data.lastName
  member.firstName = res.data.firstName
  member.availableCredit = res.data.availableCredit
}
await getMemberInfo()

// ページ遷移
const router = useRouter()
const moveFormPage = () => {
  router.push("/mypage/form")
}
</script>

こうなると、TypeScriptの導入もしやすいですね。
tsの記述については、公式の以下のページがわかりやすいです。

ja.vuejs.org

ja.vuejs.org

<template>
  // 省略
</template>

<script setup lang="ts"> // ←tsの使用を宣言
// memberの型
interface MemberType {
  lastName: string
  firstName: string
  availableCredit: number
}

const member: MemberType = reactive({
  lastName: "",
  firstName: "",
  availableCredit: 0,
})
// 戻り値stringのcomputed
const memberName = computed<string>(() => {
  return member.lastName + member.firstName
})

// void型の関数
const getMemberInfo = async(): void => {
  const res = await axios.get("endpoint")
  member.lastName = res.data.lastName
  member.firstName = res.data.firstName
  member.availableCredit = res.data.availableCredit
}
await getMemberInfo()
</script>

Vue3移行の注意点

破壊的変更など、ラボルの移行作業の中で引っかかった項目を、注意喚起の意味も込めて紹介します。 また、破壊的変更(breaking-changes)は公式にてリストアップされているので、要チェックです。

v3-migration.vuejs.org

ページ遷移

移行作業中は、どうしてもVue2上で動くページとVue3上で動くページが混在してしまいます。
ラボルはSPAなので、ページ遷移方法に気をつけないと、遷移先が404になってしまします。

遷移方法は以下4パターンがあります。

  1. Vue3→Vue3のページに遷移する時
    • <router-link to=""> (template内)
    • router.push (script内)
  2. Vue3→Vue2のページに遷移する時
    • <a href="">(template内)
    • window.location.href (script内)
  3. Vue2→Vue3のページに遷移する時
    • <a href="">(template内)
    • window.location.href (script内)
  4. Vue2→Vue2のページに遷移する時
    • <router-link to=""> (template内)
    • router.push (script内)

まとめると以下のような感じです。

遷移元→遷移先 →Vue3 →Vue2
Vue3→ 1. router遷移 2. aタグ遷移
Vue2→ 3. aタグ遷移 4. router遷移

注意すべきは2と3で、要するに違うプロジェクトに遷移するケースを洗い出し、それらのテストが必要でした。
移行のリリース毎に、遷移テストを必須にし、遷移先が404になってしまうリスクを排除しました。

オブジェクトや配列のデータ更新

昔からVue2で開発していたフロントエンドの方はご存知だと思いますが、オブジェクトや配列のデータ更新をした時に、リアクティブになるよう以下の記述方法が必要でした。
リアクティブの探求 (Vue2時点のドキュメント)

// Vue2

// objectの更新
this.$set(obj, "newKey", "newItem")
// -> obj: {newKey: "newItem"}

// arrayの更新
this.$set(ary, 0, "newItem")
// -> ary: ["newItem"]

このグローバル関数の$setが、Vue3では削除されています。
Vue2での書き方に慣れている方こそ、引っかかるポイントだと思います。

グローバル関数の set と delete、インスタンスメソッドの $set と $delete は削除されました。プロキシベースの変更検出では不要になりました。

v3-migration.vuejs.org

なので、以下の更新方法で問題ないです(リアクティブになります)

// Vue3

// objectの更新
obj.newKey = "newItem"

// arrayの更新
ary[0] = "newItem"

<template v-for>でのkeyの使い方

key 属性

<template>v-forを使う場合、Vue2ではkeyを子要素に設定していました。

<!-- Vue2 -->
<template v-for="item in list">
  <div :key="item.id">{{ item.name }}</div>
</template>

しかし、Vue3ではkey<template>に設定しなければいけません。

<!-- Vue3 -->
<template v-for="item in list" :key="item.id">
  <div>{{ item.name }}</div>
</template>

ややこしいですね。

まとめ

Vue2で開発中のみなさん、年末の大掃除だと思って、2023年12月31日までにVue3にアップデートしましょう。
上述したラボルのVue3以降はリファクタリングも行っていましたが、最低限Options APIの記述のままでも問題ないです。

ただ、以下に移行時の注意点まとめたので、こちらだけ気をつけてください。

  • Vue3移行ガイドを見ながら移行すべし
  • 破壊的変更に該当するコードを全文検索などで洗い出すべし
  • SPAの場合、移行後/移行前のページ間の遷移方法をテストすべし

最後に

ラボルでは、エンジニアを積極採用中です。1、2年目のエンジニアから経験豊富なテックリードやエンジニリングマネージャーまで、興味がある方はぜひご応募ください!!

labol.co.jp