Vue3 へのアップデートでハマったこと3選

Vue3 へのアップデートでハマったこと3選
Vue3 へのアップデートでハマったこと3選

People illustrations by Storyset

こんにちは、開発チームでエンジニアをしている和田です。

最近は担当しているプロダクトのフロントエンドを Vue2 から Vue3 にバージョンアップを行うタスクを任され、なんとか本番リリースができました🙌

自分自身、ライブラリやフレームワーク全体のバージョンアップは初めてでもあり、チームとしてもかつてない規模の変更だったため、大仕事でした😭

そのような背景もあり、今回は Vue3 へのバージョンアップでハマったことを 3 つほど紹介させていただきます。

背景・目的

Vue3 へのバージョンアップ作業でハマったことの備忘録です。
Vue3 へのバージョンアップに取り組む方の参考になれば嬉しいです。

前提条件・コンテキスト

自身が担当していたプロジェクトは Vue2 で作られており、Options API と Composition API のそれぞれで書かれたコンポーネントが混在している状況でした。 Composition API の導入が決まってから少しずつ Options API からの書き換えを行ってきた途中で Vue3 へのバージョンアップを行ったという背景があります。


Vue3 のバージョンアップでハマったこと3選

1. リアクティブな値が取得できない

原因: 構文書き換え時の .value のつけ忘れ

Vue3 へ書き換えが完了した直後、期待しない動作を示すほとんどの原因が .value のつけ忘れでした。 Vue3 から Vue を学習されている方は馴染みが無いかもしれませんが、Vue2 の Options API ではリアクティブな値を次のように定義します。

<script>
  export default {
    name: "TestComponent",
    data() {
      return {
        fruits: [],
      }
    },
    computed: {
      apples() {
        return this.fruits.filter(fruit => fruit === "apple");
      }
    }
  }
</script>

一方で、Vue3 Composition API では次のように記述します。

<script>
  export default defineComponent({
    name: "TestComponent",
    setup() {
      const fruits = ref([]);
      const apples = computed(() => fruits.value.filter(fruit => fruit === "apple"));

      return { fruits, apples };
    }
  })
</script>

Vue3 ではリアクティブな値には .value をつけて値の参照を行うため.value をつけずに処理すると当然、不具合が発生します。 computed を使用している値も .value が必要となるため、記述忘れによる不具合が開発中によく見られました。

Vue3 書き換えの際は TypeScript を導入しておらず、これらの記述ミスがあったとしても一見、問題なく動作するように見えます。 結合テストでしっかりチェックしないと気づかない場合もあるので侮れないですね😬


2. ref 属性から子コンポーネントのメソッドが実行できない

原因: ref 属性に記述したリアクティブな値を return していない

次に子コンポーネントのメソッドがなぜか実行できないという問題に遭遇しました。
結論として、子コンポーネントのメソッド実行のために ref 属性に記述するリアクティブな値を return していないことが原因でした。

Vue2 で Composition API を使用する場合、下記のように記述して子コンポーネントのメソッドを実行していました。

<template>
  <ChildComponent ref="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup(props, context) {
      const doChildMethod = () => {
        context.refs.refChildComponent.childMethod();
      }

      return { doChildMethod };
    }
  })
</script>

一方、Vue3 Composition API では次のように記述します。

<template>
  <ChildComponent ref="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup() {
      const refChildComponent = ref(null);
      const doChildMethod = () => {
        refChildComponent.value.childMethod();
      }

      return { refChildComponent, doChildMethod };
    }
  })
</script>

context.refs を使って refChildComponent にアクセスできなくなったため、子コンポーネントref 属性に記述した名称と同じリアクティブな変数を定義してあげる必要があります。
さらに子コンポーネントのメソッドを実行するには refChildComponentreturn させないといけません。

Vue2 Composition API では return の記述が必要なかったため盲点でした。
新しく Vue3 でコンポーネントを作るような状況であれば、このような見落としは生じづらいかもしれませんが、大量の書き換え作業の中ではやはり抜け漏れがあると疑った方が良いですね。

この問題が発見しづらい要因として、ref 属性に記述した値は文字列であるということも挙げられます。 例えば下記のように子コンポーネントprops に変数を渡す場合、その変数が return に記述されていないとエラーが出ます。

<template>
  <ChildComponent :title="refChildComponent" />
</template>

<script>
  export default defineComponent({
    name: "ParentComponent",
    setup() {
      const title = ref("")

      return { title }; // ここに title を入れないとエラー
    }
  })
</script>

先程の ref 属性の例ではこのようなエラーは表示されないので、一見問題ないように見えてしまうわけですね... 恐ろしい...😱


3. this の残骸によって処理が走らない

原因: Options API の this を消し忘れている

最初の Vue2 Options API の例にも示したように、Options API ではコンポーネントで定義されたメソッドやリアクティブな値を使用すときは頭に this を付ける必要がありました。
Composition API になってからは this の記述は不要になったのですが、Composition API に書き換えるときに Options API の残骸の this が、残っていても警告が出ないパターンが存在します。

<script>
  export default defineComponent({
    name: "TestComponent",
    setup() {
      const doFoo = () => {
        doHogeHoge();
        this.doBar(); // 意図しない this が紛れ込んでもエラーが見えない
        doFugaFuga();
      }
    }
  })
</script>

幸いなことに自分が担当しているプロダクトでは this を使用している箇所は限定的なため見つけるのは簡単でしたが、開発中にエラーとして出てくれないのはやはり怖いです。


この問題に今後どう向き合うか?

今回、紹介したハマった箇所3つはいずれも開発中に見落としがちになるものばかりでした。
結合テストでほとんどの問題を発見するに至りましたが、できればテストを実施する前の段階で見つけたいです。型チェックによって防げるものも多いので TypeScript を導入すれば、より安全に開発を進めることができるかもしれないですね。

とはいえ、Vue2 から Vue3 への大規模なアップデートのタイミングで TypeScript を入れるのもハードルが高いかもしれないので導入のタイミングは検討する必要があります。


まとめ

最後まで読んでくださりありがとうございました 🙇‍♂️
紹介させていただいたハマった点はいずれも初歩的なものです。普段なら間違えないような作業も、大量の書き換え作業の中で埋もれてしまって気づかないということを教訓として得ました。

改めてこの記事が Vue3 へのバージョンアップに挑む開発者の参考になれば嬉しいです!

コミュニティ・リソース・参考文献