Vue Composition APIをチームで導入して得られたメリット

f:id:fkmy:20210324123405p:plain

前書き

はじめまして、ANDPADでフロントエンド開発を担当している小泉です。入社から2年弱ですが、ここ1年の社員数の増えるペースが速すぎてすっかり古株になりつつあります。

自分は入社当初からVue.js・Nuxt.jsを使って開発を行っているのですが、2020年7月頃よりスタートしたプロジェクトで、Vueの新しい記法であるVue Composition APIを全面的に採用しています。

この記事では、Composition APIを実際に製品開発に導入するまでの流れから、9ヶ月ほど使っていく中で気づいたComposition APIのメリットとデメリットなどをまとめています。

昨年秋にVue 3.0が正式リリースされ、今年はNuxt 3.0も控えており、これからVue 3.0やComposition APIを本格導入するチームや会社も多いと思いますので、その際の参考になれば幸いです。

技術選定

現在、ANDPADのフロントエンドでは、プロダクトに適した技術選定をチームごとに行っています。

自分がこれまで担当していたプロジェクトでは、Nuxt.js のデフォルトの構成をほぼそのまま踏襲していました。Nuxt TypeScriptこそ導入していたものの、Vue 2.0、特にOptions APIやVuexとの相性の問題もあって、TypeScriptによる静的型付けなどの恩恵を最大限には受けられていませんでした。(私自身が当時はTypeScriptをそこまで深く理解していなかったのも理由にあります)

開発初期はそこまで苦労していなかったのですが、製品やチームの規模が大きくなるにつれて徐々にこの問題が顕在化していき、既存のコードへの影響を掴み切れないことに起因するバグ・デグレなどが発生するようになっていました。

そんな中で、2020年7月に、新規製品の開発チームに移動することになったため、この経験も踏まえ、どういった技術構成でスタートするか、ということを他のフロントエンドエンジニアや弊社の技術顧問の方とも相談した結果、ちょうどVue Composition APIの仕様が本格的に固まってきており、少しずつ国内外での採用事例や技術記事も増えてきたタイミングであったことから、Composition API ベースで開発を行うと良いのではないかという話になりました。

最初はReact.jsなど他技術での開発も検討したものの、リリースまでのスケジュールが少し厳しいプロジェクトだったため、(自分以外のメンバーも含めて)全く経験のない技術での開発はリスクが高く、自分がそれまでに担当していたプロジェクトからコンポーネントレベルで流用できる画面が多いという点からも、互換性のあるVueがより適していると判断しました。

Vue 3.0ではなく Vue 2.0 ベースでの開発となったのは、昨年夏の時点では Vue 3.0が正式リリースされていなかったためで、

プラグインに @nuxtjs/composition-api ではなく @vue/composition-apiを利用したのも同様に、当時はNuxt Composition APIのドキュメントに It is not recommended to use this package in production という一文が載っていたためです。

(なお、現在はこの文章は削除されているため、Nuxtで利用する場合はこのプラグインを利用する方が良いと思います。)

Composition APIでよかったこと

自然に型安全なコードが書ける

Options API ではなく Composition API を使って書く一番のメリットがTypeScriptとの相性の良さだと思います。

とにかく意識しなくても自然にType Safeなコードを一貫して書くことができます。

この「意識しなくても」という部分が重要で、Options APIでも頑張ればそれなりに型を付けることはできましたが、this を拡張していく性質上どこかで歪みが生じやすく、

例えば、明らかに返却される値の型が決まっている computed でも明示的に型を書かないとany扱いになってしまうなど、少し気を抜くと意図せずに型安全でないコードを生み出してしまうことがあります。

その点、Composition APIで書けば、computedもrefもreactiveも、明示的に型を書いていなくても、マウスオーバーすれば自動的に推論された型が表示され、元々の定義に合わない値を入れようとすると確実にエラーになります。

TypeScriptで開発中すぐに警告を出してくれる恩恵はとても大きく、「頑張れば使える」から「自然に適用される」になったことで、事前にエラーや潜在的なバグに気づける場面が大幅に増えました。

また、意識せずに型安全になる、ということは、実装者によって暗黙的なanyが増えてしまうことも防げます。Composition APIの導入によって、細かいルールを設けることなくチーム間でのTypeScriptの実装度が全体的に統一され、PRレビューコストの軽減にも繋がっています。

Vue 2(Options API)からの移行しやすさ

Composition API について最初に情報を見た際に、個人的には「Vue.js のシンプルな書きやすさがなくなってしまうのでは」という不安を覚えていました。

しかし、実際に使ってみると、Vue.js の学習コストの低さ・理解しやすさを可能な限り継承しながら、型安全性や処理の分割を達成している、ということがわかってきました。

computedやwatchやonMountedなど、命名や対応がわかりやすいので「Options APIで何を指していたのか」が掴みやすく、dataについても、今までであればdataで宣言していたものをとりあえずreactiveでまとめる、というようにすれば、大きく考え方を変えずにコードを書いていくことができます。

data() {
  return {
    userId: null as number | null,
    message: '',
    isError: false,
  }
}
const data = reactive({
  userId: null as number | null,
  message: '',
  isError: false,
})

そして、このような最低限の使い方でも、TypeScriptの恩恵は十分に受けられ、書いていくうちに「reactiveで一括りにしてしまっているけれど、意味のまとまりとして別だからそれぞれ異なるreactiveで定義しよう」というように、自然に少しずつ可読性の高い書き方に変えていくことができます。

これまでVue × TSの選択肢として人気だったClass Componentと比べても、Options APIからの移行がかなりスムーズに行える、という印象があり、

特に業務としての開発だと、新しい技術を採用するうえで、移行期間にチームの生産性をなるべく落とさないという点に大きなメリットがあると感じました。

Composition API で苦労したこと

ベストプラクティスが固まっていない

現状、Composition APIを特に業務で使うにあたっての最大のハードルになるのがこの部分だと思っています。

Options APIと比べて自由度が高くなったことで、人によって異なる書き方ができるようになったのですが、それをある程度統一するための規約や慣習がまだ固まっていません。

他の人のコードを参考にしようにも採用例が少なく、Composition APIの仕様自体が策定中だった期間に書かれた記事も多く残っており、検索して上位に出てくる記事だからといってベストな書き方になっているとは限らなかったりもします。

そのため、人によって書き方が変わってしまったり、もっと良い書き方・整理の仕方があることに後で気づく、ということが起きがちです。

とはいえ、TypeScriptで補完が強力に効くこと、コンポーネントに影響範囲が閉じていることから、多少スマートでない書き方になっていたとしても大きな負債にはなりづらいのも利点の1つです。

当然ながら後から直すこともできるので、ある程度割り切って進めていくと良いのではないかと今は考えています。

(プロジェクト初期、設計やちょっとした命名に悩みすぎたなという個人的な反省も少し含んでいます)

ちなみに、早めに知っておきたかった地味な知識として、「Composition APIの提供する機能は全般的に型宣言の方法が2通りある」というものがありました。

const numRef1: Ref<number> = ref(0)
const numRef2 = ref<number>(0)

const computedNum1: ComputedRef<number> = computed(() => numRef1.value)
const computedNum2 = computed<number>(() => numRef2.value)

const injectionKey1: InjectionKey<ProvideModule> = inject(key)
const injectionKey2 = inject<ProvideModule>(key)

最初は1の方法でしか書けないと思い込んでいて、RefComputedRefを毎回importするのが地味に面倒に感じていたので、この書き方を発見した時は目からウロコでした。(TypeScript力が高い方にとっては当たり前かもしれませんが)

Vuex の型の問題が未解決

Vue自体の機能についてはComposition APIへの移行やTypeScriptとの組み合わせで困ることはほとんどありませんが、Vuex についてはサポートがなかなか進んでいないのが現状です。

ctx.root.$storeで呼び出すことはできますが、型のサポートは全くされておらず、TSサポートの強化が予定されているVuex 4もVue 3.0にしか対応していません。

今回のプロジェクトでは基本的には nuxt-typed-vuexを使うことでTypeScript対応させています。

Vuexの代わりにnuxt-typed-vuexを用いて型の恩恵を受ける🏦 - Qiita

ctx.root.$accessor で呼び出せ、$accessor. まで入力すると候補を補完してくれるので非常に便利です。

(ただしVue 3.0で ctx.root が廃止された後にどう呼び出せば良いかについてはまだわかっておらず、バージョンを上げる際の懸念はあります)

とはいえ、型サポートが完全になったとしても、グローバルに利用できてしまうというVuexの根本的なリスクは(もちろんメリットも大きいですが)残っているため、

特定のコンポーネント範囲でしか使わない値の共有では、 provide/inject を使った簡易的なストアパターンへの置き換えを徐々に試みています。

こちらも完全なベストプラクティスは確立されていないものの、参考になる記事が既にたくさん書かれており、ある程度テンプレートがあれば簡単に扱うことができます。

Vue3 Composition APIにおいて、Providerパターン(provide/inject)の使い方と、なぜ重要なのか、理解する。 - Qiita

Vue Composition API を使ったストアパターンと TypeScript の組み合わせはどのくらいスケールするか? - Qiita

Vue Composition API でprovide , inject を用いたデータの状態を管理する方法

(ちなみに上に挙げた1つ目の記事を書いている@karamageさんもANDPADのメンバーです!)

コンポーネントと同じComposition API記法で統一的に書けるため後から切り出しやすく、Vuexと違って明示的なインポートで呼び出せるので扱いやすい印象があります。

ただしこの方法にも、親コンポーネントでprovideされているかどうかが実行時までわからない、コンポーネントの再利用がしにくくなる、providerとして切り出したモジュール同士で値の受け渡しを行うことが難しい、Vuexと併用すると初期化タイミングなどで混乱を招く……といったデメリットもあり、一長一短です。

このあたりは今後の課題として適切な運用方法を模索しつつ、Vue/Nuxtのバージョンアップでよりスマートな方法が提供されることを期待しています。

まとめ

Vue Composition API は、Beta版のリリースから半年以上が経過し、かなり安定して使えるようになってきました。

また、Vue 3.0はこのComposition API以外にも、パフォーマンスの大幅な改善や、Teleport・Suspenseといった新しいUXを生み出す機能追加も行われています。

2021年は、Nuxt 3.0のリリース、Vue 3.0 のIE11への対応が予定されており、国内でもますます普及・移行が進むことが予想されます。個人的にも、Nuxt 3.0の正式リリースはとても楽しみです。

この記事が、Composition APIおよびVue 3.0を使ってみたい・チームに導入したいと考えている方のお役に少しでも立てば幸いです。

最後に

ANDPADでは一緒に働く仲間を募集中です。

engineer.andpad.co.jp

ANDPADのプロダクト開発では、各チームごとに最適な技術を採用することで、開発体験とユーザー体験の両方を向上させることを目指しています。

この記事で取り上げたVue Composition APIはあくまでその一例にすぎず、他にも各チーム・各メンバーの適性や希望、プロジェクトの特性に応じて、柔軟な技術選定を行っています。また、それによって各チームで得られた知見もチームを超えて積極的に共有されています。

特定のフレームワークの枠に囚われず、新しい技術を使ったチャレンジングな開発に興味のある方はぜひご応募ください!