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
属性に記述した名称と同じリアクティブな変数を定義してあげる必要があります。
さらに子コンポーネントのメソッドを実行するには refChildComponent
を return
させないといけません。
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 へのバージョンアップに挑む開発者の参考になれば嬉しいです!