React から Vue への出戻りを検討する 3 つの理由

はじめに

「Vue.js と React.js、結局どっちがいいの?」

2年半ほど前に、流行っている JS フレームワークのどちらかを勉強してみようと思った時、そんなことを延々と考えていました。 「Vue は Easy、React は Simple」「Vue は独自構文が多いけど、普段マークアップをメインに仕事してる人は触りやすいと思う」「React は生 JavaScript を触ってる感じ、JS 得意なら新しく覚えないといけないことは少なさそう」 こんな話を周りから聞き、まず自分の普段の業務内容から考えてとっつきやすそうな Vue から「なんとなく」触り始めたのを覚えています。 最近になってようやく React を触り始めたのですが、両方実際に触ってみることで「なんとなく」ではない、2 つの JS フレームワークそれぞれの具体的な良さが見えつつあります。

「出戻りを検討する理由」としていますが、当然 React のよい点も多々感じています。 自分の普段の JS フレームワークの使用方法としては、Web サイト制作業務から(主に趣味程度の)簡単な Web アプリケーション制作がほとんどであり、使用歴も決して長くありません。ベストな方法を知り尽くしているわけではありませんし、使用機会として Vue.js の方が多いため知識やスタンスがそちらに偏っているということもあるかと思います。

本記事のスタンスとしては 「最近 Vue.js が v3 へのアップデートで React に近づいたらしいし、じゃあもう React やったほうがいいんじゃない?」 という(主に過去の自分の)考えに対し 「でもどっちも触った上で改めて考えてみると、Vue もやっぱりいいよねー」と伝えたい、という気持ちで書いています。 どちらが確実に優位であるという内容を書くつもりはなく、これから少しでも JS フレームワークを触ってみようかな?と選定に迷っている方の参考となれば幸いです。

理由 1. CSS の書き方で悩まない

Vue.js の SFC(Single-file component、いわゆる .vue ファイル)では、CSS の記述を style タグの中に記述することがほとんどかと思います。 style タグの中でどう CSS を記述するかの選択はあれど、基本的にはプレーンな CSS と同様の扱いで記述できます。 当たり前かと思うかもしれませんが、普段 CSS を書いている方であれば、特に迷うことなくスタイリングを行うことができますし、この当たり前さが Vue を Easy たらしめる良さの 1 つであると思っています。

<template>
  <p class="hoge">サンプルテキスト<p>
</template>

<style>
.hoge {
  color: red;
}
</style>

React だとどうでしょうか? React プロジェクトを生成するコマンドである create-react-app のデフォルトでは、CSS ファイルが JS ファイル内で import されているかと思いますが、実際のプロジェクトでそのまま使用されている方は少ないのではないでしょうか。 どの方法で CSS を書くのがより良いか、手法及びライブラリの選定をまず行うことが多いかと思います。 「そのままプレーンな CSS を書くか、CSS Modules で記述するか、CSS in JS にするか、どのプラグインに何を使用するか。。」などを考え出すと結構労力がかかりますし、実際に使ってみてベストプラクティスを検討するとなるとなおさらです。

自分が React を使用したタイミングでは styled-components を採用しましたが、正直なところ今までの CSS 設計で解決できなかった問題が楽になったという印象はありませんでした。いつもの CSS 設計を styled-components にどう反映すべきなのか?を考えている時間の方が長かったかもしれません。

その点、Vue であれば、多少の設計の違いはあれど少なくとも記法の違いで悩むことはありません。チームで開発する際にも、記法に学習コストをかけずに、設計にフォーカスしたレビューに時間を割くことができます。 (React に CSS modules を採用する場合であれば大差なかったかも知れませんが、maintenance only になっているとのことと、CSS を別ファイルで管理したくなかった等の理由で採用を見送りました。。)

コンポーネントの挙動とスタイルが密接に関連づけられている UI が多い場合であれば、もっと CSS in JS が生きるのかもしれませんが、自分の業務内容の場合は CSS 設計で解決できることが多かったため、styled-components の旨味を生かしきれなかった、ということでもあるかも知れません。

理由 2. 設計方針やライブラリと選定での悩みが比較的少ない

Vue は「The Progressive JavaScript Framework」 と銘打たれており、ルーティングの機能が必要であれば vue-router, 状態管理を整理したければ Vuex というように、機能の必要性に応じて拡張されることを前提として周辺のエコシステムが提供されています。 必要とする機能が少し特殊である時などは不十分な場合もあるかもしれませんが、それでも選定基準のひとつとして第一候補に上がる公式のライブラリがあるというのは心強いです。

対して React は「UI 構築のための JavaScript ライブラリ」であり、本体が持っていない機能の実装時には別ライブラリを選定する必要があります。Vue のように公式のエコシステムがリリースされているわけではないため、適切なライブラリを自身で選定ことになるかと思いますが、その際「そもそもどういったライブラリがあり、今回自分が実装するにあたって何が適切か」「メンテナンスされており幅広く使用されているライブラリか、古くなっていないか」など、機能拡張の際に実装や学習以外の部分で一苦労あります。 React は本体の機能としてはシンプルに整っているかもしれませんが、ほとんどの場合 React 本体だけでプロジェクトの用件を満たすことはおそらくできないでしょう。 「React の実装自身はシンプルだが、ライブラリを含めた React プロジェクトがシンプルに保たれるかどうかは別問題」かと思います。

(それぞれのフレームワークに対する慣れや知識量の問題、と言われてしまえば返す言葉もないのですが。。)

「1. CSS の書き方で悩まない」の styled-components の内容と重なるかもしれませんが、React は技術選定や学習コスト、ベストプラクティスを探るのに時間がかかる印象があります。少なくとも追加機能のライブラリ選定時にあまり悩む必要がないのは Vue ではないだろうか、と感じています。

理由 3.Composition API の導入による TypeScript との食い合わせ問題の緩和

Vue.js と React.js の比較の際、 Vue.js と TypeScript との食い合わせの悪さはよく挙げられていました。 TypeScript で容易に型の恩恵を受けることができるというのは React.js の大きなアドバンテージであったかと思いますが、昨年リリースされた Vue 3 では TypeScript との食い合わせ問題が大きく改善されたように思えます

・Vue 2 系からの Options API(型なし)

<template>
  <div>
    <p>{{ computedNumber }}</p>
    <button @click="addCount" type="button">add</button>
  </div>
</template>

<script>
// 型を付けたい場合は defineComponent + as 節での型アサーションとなる
// https://v3.vuejs.org/guide/typescript-support.html#using-with-options-api
export default {
  data() {
    return {
      countNumber: 0
    }
  },
  computed: {
    doubleNumber() {
      return this.countNumber * 2
    }
  },
  methods: {
    addCount() {
      this.countNumber++
    }
  },
}
</script>

・Vue 3 で導入された Composition API(型は推論が効きますが、ドキュメント代わりに書くようにしています)

<template>
  <div>
    <p>{{ doubleNumber }}</p>
    <button @click="addCount" type="button">add</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from 'vue'

export default defineComponent({
  setup () {
    const countNumber = ref<number>(0)

    const doubleNumber = computed((): number => {
      // ref オブジェクトの値は .value に格納されるため countNumber.value を使用する
      // よく忘れそうになるが、TypeScript だと .value 忘れのエラーを吐いてくれるので助かる
      return countNumber.value * 2
    })

    const addCount = (): void => {
      countNumber.value++
    }

    return {
      doubleNumber,
      addCount
    }
  }
})
</script>

Composition API の導入によって TypeScript のサポートだけではなく、機能ごとにロジックの記述をまとめることができたり、カスタムフックのように import / export を使用してロジックを見通しよく記述できるようになりましたが、import / export の数がかなり多くなってしまうことや、ほぼ一緒になるはずの props の type プロパティと setup 関数の引数に渡る props の TS 型定義を別に記述する必要があったり、それなりに面倒くささもありました。 しかし script setup の導入によって、CompositionAPI で個人的に気になっていた記述の面倒くささは、現状かなりシンプルに解決されています。

・Composition API (script setup)

<template>
  <div>
    <p>{{ doubleNumber }}</p>
    <button @click="addCount" type="button">add</button>
  </div>
</template>

<script lang="ts" setup>
import { ref, computed } from 'vue'

const countNumber = ref<number>(0)

const doubleNumber = computed((): number => {
  return countNumber.value * 2
})

const addCount = (): void => {
  countNumber.value++
}
</script>

トップレベルの記述はそのまま template で使用することができるようになり、煩わしい import や return などが整理されました。 また props の型は defineProps を用いて TypeScript の型定義をそのまま使用することができるようになったため、Vue, TypeScript の型宣言も不要となりました。 https://v3.vuejs.org/api/sfc-script-setup.html#typescript-only-features

以前【Vue.js 3.0】Composition API なんかと同じ空間にいられるか!俺は 2.x 系に戻るぞ! を書いた際、は「記述の冗長さも気になる節があるので、コンポーネントの粒度が大きくなってきたら Composition API を導入、くらいで良さそう」程度に考えていましたが、Composition API と TypeScript での開発や、setup script を使用した記述は思いの外良く、Simple かつ Easy にまとまってきていると感じます。

(あとは .vue ファイルに型定義の import が問題なくできるようになってくれると個人的には嬉しい限りです。どうやら今はまだ調整中のようで。。) https://github.com/vuejs/vue-next/issues/4294

まとめ

再度簡単にまとめておくと

  1. そのまま CSS を書くことができる
  2. ライブラリ選定で悩まない
  3. CompositionAPI + script setup による開発体験の改善

という点が、自分が最近改めて感じた Vue の良さになります。 自分も引き続き両方触っていき、よりコアな機能も含めて末長く付き合っていければよいなと思っています。

少しでも興味を持っていただけたようであれば、この機会にお手元で是非一度触っていただけると、多少なりとも師走を楽しく過ごすことができるかもしれません。 それでは皆様、良い年末をお過ごしください!