Reactを使うのかVueを使うのかについて個人的なモチベーションを整理したかった

2022/03/27に公開約10,000字8件のコメント

Reactはいいぞ、Vueはいいぞと様々な情報が世の中には溢れているものの、「こういう場合には」という前提条件にあまり言及されていない情報が多いような気がしたので自分なりの視点で考えてみたいと思いました。

また、SvelteやAngular等他のフレームワークもありますが、そちらは個人的にはよくわからないので、あくまでReactとVueについてだけ言及していきます。

私のフロントエンド経験と気持ちの変化

  • 2018年くらいにReactを勉強し始める。
    • → Reactって難しい…。
  • 2019年くらいにVueを学び始める。
    • → Vueって簡単!Reactよりわかりやすくてええやん!
  • 2020年くらいにNuxtの案件に参画する。
    • → Webフロントエンドへの理解が深まる。
  • 2021年〜 Vueの案件に参画(プライベートで何か作る時はReact)
    • → Vue結構辛いな…。やっぱReactがいい…。

上記のような経歴なのでVueの方が実際の現場で触っている時間が長いです。
特に最近は移行の検討を始めていたりするので、その辺りには特にフォーカスするかもしれません。

記事を全体を通して中規模程度のフロントエンドの開発・運用をVueでやっているReact好きおじさんの意見・感想であることをご理解いただければと思います🙇‍♂️ (重要)

書き方の比較

React vs Vueの比較もしたいのですが、Hooks・Composition APIの登場前後で同ライブラリ内でも書き方に結構違いがあるのでそこも含めて並べてみます。

React

  • Hooks以前のReact、Classベースで記述する。
import React from 'react'
 
class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  increment = () => {
    this.setState({ count: this.state.count + 1 })
  }
  
  render() {
    return (
      <div>
        <p>count: {this.state.count} times</p>
        <button onClick={this.increment}>
           Click
        </button>
      </div>
    );
  }
 }
  • 関数コンポーネントとHooksで記述、最近ほぼこちら
import React, { useState } from 'react';
 
export const App = () => {
  // countはnumber型
  const [count, setCount] = useState(0); 
  const increment = () => {
    // 再代入せず必ず関数を通して状態を更新する
    setCount(count + 1); 
  }
  return (
    <div>
        <p>count: {count} times</p>
        <button onClick={increment}>
          Click
        </button>
    </div>
  );
}

Vue

  • Vue2 Option API
<template>
  <div>
    <p>count: {{ count }} times</p>
    <button @click=increment>
       Click
    </button>
  </div>
</template>
 
<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    increment() {
      this.count++
    }
  }
}
</script>
  • Vue3 script setup構文で記述
<script setup>
import { ref } from 'vue'

// countはRef<number>型
const count = ref(0) 
const increment = () => {
  // ラップされた値を更新する。再代入っぽい状態更新
  count.value++ 
}
</script>
 
<template>
  <div>
    <p>count: {{ count }} times</p>
    <button @click=increment>
       Click
    </button>
  </div>
</template>

Vueについては上記2つの書き方の間にも様々な記法があります。

  • Vue.extend
  • ClassComponent
    • Vue2でTypeScriptサポートを高めるために考案された記法だが、Vue3ではネイティブでTypeScriptサポートされるようになったので現在は化石
    • リリースも2020年でストップしており、積極的なメンテはされないように見受けられる(リポジトリ
  • setup構文…etc

ReactのClassComponentやVueのOption API等を採用する場合、フックパターン的に状態を管理できないので、単一のコンポーネントが持つ情報がどんどん肥大化し、スケールが難しくなっていく印象があります。

また、React Hooksはリリースされてからある程度時間が経過しており、ネット上でもノウハウが散見されるようになってきましたが、VueのComposition APIについてはReactよりも後発であったり、色んな事情でOption APIも結構現役な気がしています(主観を含む)。
script setup構文は結構スマートで良い感じになったと思います。

可能な限りVueを採用する場合で将来的なスケールも見据えたいという場合は、Reactでサンプルコードが書かれている記事等も参考にしつつVueに落とし込むということをしておいた方が幸せになるのではないかと思っています。
(そこまで頑張るんならReactやろうよ…って言われるとそれはそうとしか言えないけど)

↓とか

https://zenn.dev/morinokami/books/learning-patterns-1

個人的にVueを採用しても良いと思う場面

開発規模の話

個人開発〜小規模程度の開発であればアリかと思います。
作り方によっては中規模程度までもいけるかもしれません。

Vueの良い部分として直感的なAPIで導入が簡単というメリットがあると思うので、あまりフロントエンド開発になれていない人がそこに全力で乗っかり初速を高めた開発を進められるのは大きなメリットではあると思います。
(もちろん最初からReactができる人にとってはこの限りではないかもしれません)
また、状態管理ライブラリやCSSについては選択肢がReactほど多くないので、その辺りの選定コストが比較的低いところも良い部分と言えるかと思います。

一方で、ある程度のチーム人数や規模になった際にTypeScriptの型によるサポートやtscによるチェックによる恩恵を受けたいというような場合や、単方向データバインディングしかできないことによるシンプルな設計のしやすさはReactに軍配が上がるように思います。

特にVueの場合は双方向データバインディングが使えてしまうので、これを濫用してしまうとデータの流れがカオスになりスケールが難しく、末端で負債を残しやすくなる気がしています。

運用面での話

いつでも捨てられる・長期の運用をあまり考えなくて良い場合はアリかと思います。

というのも、長期での運用を考えた場合、現時点ではVue2 -> Vue3へのBreaking Changeや、それによって引き起こされているであろう周辺ライブラリの対応の遅れによる開発スピードの低下・移行コストの高さが無視できるレベルではないように感じているからです。

特に(超メジャーOSSの)Nuxt.jsが現在Public Betaではあるものの、プロダクションで気軽に導入できない状態であったり、VuetifyもまだBeta版なのはなかなか厳しいです。(2022年3月時点)
Vue3がリリースされたのが2020年9月なので対応に1年以上かかっています。

また、移行については一応公式が移行ビルドを公開しています。
公式移行ビルド

  1. vuex を v4 にアップグレード します。
  2. vuex-router を v4 にアップグレードします。

プラグイン周りの仕様の変化により、Vue3でVue2までのライブラリが使えないものがあるようなので、コアのアップグレードと関連するライブラリのアップデートを一気に実施する必要があります。

フロントエンドについてはメジャーバージョンアップでBreaking Changeが発生するライブラリはよく目にしますが、コア部分のアップグレードが他に多大な影響を与える状態はなかなか厳しいように感じます。

(まぁ頑張ればできるんだろうけどね…)

上記についてはVue2 -> Vue3移行についての話なので、Vue3で新規開発する場合はアリかもしれないのですが、Vue3 -> Vue4で同じようなことが起きないとは必ずしも言えない(起きるとも言えないですが)のと、使用できるライブラリが制限されることがあるというのは念頭に入れておいた方が良いでしょう。

VuetifyやNuxtを使いたいから新規でもVue2を採用したいという案件もたまに聞きますが、移行コストを含めて考えると極力避けることをお勧めします。
もちろん冒頭の通りPoCやプロトタイプ用途で長期運用しない前提であればこの限りではありません。

チームメンバーの話

チームメンバーで以下のような人がいれば採用を検討しても良いかもしれません。
各々の好みや経験の問題も考慮すべきでしょう。

  • もともとマークアップを担当していた人・JSよりもHTML/CSSが好きな人等がフロントエンド開発へ参画する場合
  • どうしても初期学習コストをかけたくない
  • どうしてもReact(JSX)に抵抗を感じて仕方がない
  • Vueが好きでたまらない…etc

一言でまとめると

もろもろの不安定さよりもぱっと見の簡単さを重視したい。

といった感じでしょうか。

Vueで辛いと思うところ

周辺ライブラリ状況も考慮しながらの移行

上記にも挙げたのですがエコシステムの分断もあり、周辺ライブラリも含めた移行はなかなか気合が必要かと思います。

また、多種多様な構文についてはある程度後方互換性があるのですが、Breaking Changeによって.syncなど廃止されたものもあるので注意が必要です。

https://v3.ja.vuejs.org/guide/migration/v-model.html#概要

ClassComponentに関しては今後積極的にメンテされなさそうなので、仮にVue2を採用する場合、ClassComponentの採用は控えた方がよさそうです。

純粋なJavaScript(TypeScript)の世界からの乖離

Vueは.vueという独自のフォーマットで記述します。
この独自のフォーマットがそれまでHTMLのマークアップに慣れた人にとっては直感的であり、これがVueのよさでもあると思う一方で、厳密にはこの構文はHTMLでもCSSでもJavaScriptでもありません。

これによりエディタの補完等のために構文を解析しようと思うと独自の拡張機能を入れる必要があります。

これまでであればVue2時代でメジャーであったVeturやVue3以降で推奨されているVolarといった拡張機能が存在します。

これらはとても便利でありがたいと思いますし、解析の精度も高まってきているものの、新しいバージョンや構文が出てくるたびに拡張を変えていくのは個人的には少し手間を感じてしまいます。

また、この独自のフォーマットがあるからか、末端部分でのTypeScriptのサポートに個人的には物足りなさを感じてしまいます。

例えばTypeScriptの構文を以下のようにtemplate部分で使用するとSyntax Errorが発生します。
template部分はTypeScriptとして解釈されないみたいです。

  • 2022/04/01 追記) scriptの方にsetupが抜けておりました。Vue3から以下のような構文で補完ができそうです。
<template>
  <!-- script setup以外だとSyntax Error -->
  <button @click="handleClick(message!)"></button>
</template>

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

const message = ref<string|undefined>(undefined);
const handleClick = (message?: string) => {
  console.log(message);
}
</script>

!を使うこと自体があまりよろしくないというツッコミは一旦無しで)

また、私はVSCodeを使って開発をしており、ピーク > 呼び出し階層のプレビューを使いたいと思うことが多いのですが、このとき.vueファイルを対象に検索することができないのがそれなりの規模の開発をしていると不便に感じてしまいます。
以下はuseCounterという関数がコールされている箇所を検索しようとしているところです。


Reactの場合、.tsx, .tsファイルが対象になっている

Vueの場合、.vueは検索の対象にならない

この辺りも拡張機能があったりするかもしれないですが、個人的にはそこを頑張りたいわけじゃないんだけどな〜という気持ちになってしまいます。

VueとTypeScriptとの相性が良くないことは昔から言われていますし、Vue3でかなり改善はしているものの、やはりTypeScriptの恩恵を存分に受けたいという場合はReactを採用しておくのが無難かと思います。

逆にこういった部分的に親和性に欠けるということを理解した上でVue (+ TypeScript)を採用するのはアリかと思います。
上記の例の場合はundefinedのチェックはscript側でするなど工夫することで回避できることはあります。

私個人としてはVueを使うための工夫よりも根本的な部分でのサポートが欲しいと思うことが多いのでReactを採用したいと思うことが多いです。

双方向データバインディング

Reactは単方向データバインディングのみのサポートなのに対し、Vueは双方向データバインディングもサポートしています。

双方向データバインディング自体は小規模なフロントエンド(開発メンバーがすぐに仕様を把握できるレベル)をサクッと作る分には便利な一方で、規模が大きくなるにつれて重荷になっていくように思います。

特にReactライクな単方向でのデータ更新に加えて、v-modelemitを使った実装等、同じことを実現するのにも複数の方法があることはコードの統一感を失くしやすいです。

実際Vueは2 -> 3で.syncによる双方向データバインディングを廃止していたりしますし、
双方向データバインディング自体がある種の諸刃の剣のようなツールだと個人的には考えています。

  • 2022/04/01 追記) 廃止ではなく書き方が統一されたというのが正しいとフィードバックを受けたので取り消し線を入れました。

個人的にReactの採用を検討してほしいと思う場面

上で述べたことの逆になりますのでサラッといきます。

開発規模の話

  • 中規模以上のチーム開発
    • TypeScriptによる恩恵
  • 単方向データバインディング

運用面での話

  • 長期の保守、安全な運用が前提
    • 後方互換性の高さ
    • TypeScriptとの親和性の良さ
      • tscによるチェック
      • JSX、TSXの表現力の高さ

チームメンバーの話

  • フロントエンドへ関心が高い
    • 新機能については概ねVueがReactに追従している状態なので、Reactをやっておけば現状大が小を兼ねてくれる。
  • Vueへのこだわりがそこまでない

一言でまとめると

それなりの規模で安定的な開発・運用がしたい。

といったときでしょうか。

Reactで辛いと思うところ

逆にReactで辛いと思われる部分もあります。

関数型ベースな設計

Reactには関数型的な思想が見受けられます。
例えばJSXは実質JavaScriptの関数です。
これがプレーンなHTML/CSSからの構文的な乖離を生んでいるように見え、違和感を感じる方もいると思います。

また、関数コンポーネントで記述していると、ライフサイクル等の実装をしようと思った時に全てuseEffectという一見すると直感的ではない関数を使用することになります。
これも初見では何が起きているのか分かりにくく感じる人は多そうです。

function App() {
  const [count, setCount] = useState(0);

  // Vueでいうmounted
  useEffect(() => {
    console.log("App mounted!!")
  }, [])

  // ライフサイクルじゃないけどVueでいうwatch
  useEffect(() => {
    console.log("count updated!!")
  }, [count])

  return (
    <div>
      <span>{count}</span>
      <button onClick={() => setCount(count + 1)}>count</button>
    </div>
  );
}

あと基本的な構文もv-ifv-forのような直感的なものではありません。

function App() {
  const [visible, setVisible] = useState<boolean>(true)
  const [numbers, setNumbers] = useState<number[]>([1, 2, 3])
  
  return (
    <div>
      {
        // v-if的なやつ
        visible && <div>Hello</div>
      }
      { // v-for的なやつ
        numbers.map(number => (<div key={number}>{number}</div>))
      }
    </div>
  );
}

関数型風の考え方に馴染みがない人にとっては一定の慣れが必要な箇所はどうしても出てしまう気がします。

ライブラリの選択肢が多い

これは特にStyleや状態管理ライブラリが顕著ですが、Vueに比べると選択肢が多く、慣れていないとどれを選ぶのが良いか悩まされるかもしれません。

0ベースであれば個人的には標準ベースで公式ドキュメントを読みつつ順次に触れていくのが良いように思っています。

https://reactjs.org/docs/context.html
https://reactjs.org/docs/faq-styling.html

最後に

Vueの方で色々語ってしまったのでとてもバランスが悪くなってしまいました🙇‍♂️

VueはEasy、ReactはSimpleとしばしば言われます。

VueにはPHPを彷彿とさせるようなとっつきやすさがありますし、Reactには関数型に影響を受けた思想(?)を感じます。
この辺は最終的には好みも影響すると思うのですが、開発時だけでなく長期の運用や開発メンバーなど色々な視点から考えてメリットデメリットを考えられるようになりたいと思い執筆しました。

私個人の視点としてはどうしても長期運用のしやすさという観点が強くなってしまうので、結構なバイアスがかかってしまったようにも感じますが、あまりこういった部分に言及されている記事は多くないと思っているのでこれはこれでありだと自分に言い聞かせています。
一意見として捉えていただければ幸いです🙇‍♂️

それぞれ使い所を考えつつ良い感じにフロントエンド開発ができればいいですね。

GitHubで編集を提案

Discussion

Vue.jsにおいて、.vueの利用が必須ではないことがぬけています。

Nuxt等は、利便性を優先して、.vueを採用していますが、Vue.jsでは、HTMLに
テンプレートを直接埋め込むことができます。

Vue.js がテンプレートにおいて、TypeScript をサポートしないのは、
個人的には正しい判断、と考えています。

言わずもがな、ですが、TypeScript(TS) は 開発用言語であり、これをネイティブにサポートする
プラットホーム(実行環境)は存在しません。tsc etcのコンパイラによりJavaScript(JS)に変換され、
JS実行環境で実行されます。

つまり、TypeScriptは、あくまで、JavaScriptのシンタックスシュガー(糖衣構文)の拡張バージョンです。

中規模〜大規模の開発において、型のあるTSを採用することは、デメリットがなくメリットが大きい、
とは思います、が、フロントエンド界隈で、TS原理主義者みたいなのが湧いているのを目にすると
ゲンナリします。

Vue.jsのテンプレートは、内蔵するテンプレート・コンパイラにて、vDOMの構文木?(JavaScript)に
コンパイル(変換)されてから実行されるので、テンプレートでTypeScriptをサポートするのなら、
テンプレート・コンパイラでTypescriptの記述を含んだテンプレートを解析する必要が出てくるハズですし、
JSにあってTSにない構文が含まれていると、HTML(仕様)との親和性も崩れます。

Vue.jsのテンプレートのコンパイルには、事前コンパイルと実行時コンパイルがあります。
.vueを利用する場合は事前コンパイル、.vueを利用しない場合には実行時コンパイル、に
なります(そのハズ)。

Vue.jsのテンプレートにおいて、TypeScriptをサポートしていない、のは、実行時コンパイル、
が、あるからだ、と思います。

Vue.jsのテンプレートにおいて、TypeScriptをサポートするなら、実行時コンパイルにおいて、
tsc等のTSコンパイラをブラウザetcのプラットホーム上で走らせる必要があり、余計な
オーバーヘッドが生じます。テンプレートの事前トランスパイルを行い、あらかじめ、
テンプレート内のTSをJSに変換する形で、実行時のオーバーヘッドを回避できますが、2度手間
です。

Vue.jsのテンプレートにおいて、デファクトになるつつはあるものの非標準の TS を採用するのは、
掛かるコストや想定されるオーバーヘッドetcを考えると、不要だと、自分は考えています。
Vue.js の原作者である Evan You も、たぶん、そう考えているのでしょう。

.vue が CSSでもHTMLでもJSでもない、と書いていますが、反面、.jsx/.tsx が、CSSでもHTML
でも(厳密には)JS/TSでもない、には触れていませんね。

みんな大好き LINE の LINE株式会社、中国のアマゾンと言われ、Tencentと並ぶ中国最大のIT企業Alibaba
etc が、Vue.jsを採用し、中規模〜大規模な開発プロジェクトで利用しているハズですが、
それでも、中規模〜大規模な開発プロジェクトに向かないのでしょうか?
(側からは、筆者が向かないと思っている、としか、読み取れません。。。具体的な事例も書かれていない
(根拠がないor弱い)ので、向かない、に説得力が。。。)

コメントありがとうございます。
色々と言葉足らずな部分もある中補足いただきありがとうございます🙇‍♂️

取り急ぎ2点だけ

みんな大好き LINE の LINE株式会社、中国のアマゾンと言われ、Tencentと並ぶ中国最大のIT企業Alibaba
etc が、Vue.jsを採用し、中規模〜大規模な開発プロジェクトで利用しているハズですが、
それでも、中規模〜大規模な開発プロジェクトに向かないのでしょうか?

こちらについては内部の事情がわからないのでなんとも・・・というのが結論なのですが、逆にどうやってVueで大規模なプロジェクトを回しているのかは個人的には興味があるところです。(向いていないと思った理由は記事の通りです)

(側からは、筆者が向かないと思っている、としか、読み取れません。。。具体的な事例も書かれていない
(根拠がないor弱い)ので、向かない、に説得力が。。。)

こちらについては冒頭でもありますが、

記事を全体を通して中規模程度のフロントエンドの開発・運用をVueでやっているReact好きおじさんの意見・感想であることをご理解いただければと思います🙇‍♂️ (重要)

と書かせていただいてるので改めてご理解いただけると幸いです。
ご指摘の通りで筆者が向かないと思っているという解釈で問題ありません。
(根拠について弱いと思われたのであれば至らない点があり申し訳ありません。)

例えばTypeScriptの構文を以下のようにtemplate部分で使用するとSyntax Errorが発生します。
template部分はTypeScriptとして解釈されないみたいです。

いつ頃のアップデートだったかは忘れてしまったのですが、Vue3ならtemplate内でもTypeScriptを解釈できるようになっています!

https://vuejs.org/guide/typescript/overview.html#typescript-in-templates

追記

去年の9月頃のようです!

実際に試しましたが、TypeScript以前の問題で、 setup を書き忘れただけなんじゃないかと思います。

ありがとうございます!
setup抜けてましたので追記しておきます。
お二方とも調査いただきありがとうございました🙇‍♂️

なるほど、Vue.js 本体でのサポートではなく、vue-loader (Webpack用のloader)、
plugin-vue (Viteのプラグイン)を経由することで、Vue.js 3 のテンプレートに
TypeScriptを書くことを可能にしている、のですね。

特にReactライクな単方向でのデータ更新に加えて、v-modelやemitを使った実装等、同じことを実現するのにも複数の方法があることはコードの統一感を失くしやすいです。

v-modelはシンタックスシュガーです。
シンタックスシュガーが存在することを理由にVueをネガティブに語るのは無理があると思います。

https://v3.ja.vuejs.org/guide/forms.html#基本的な使い方

実際Vueは2 -> 3で.syncによる双方向データバインディングを廃止していたりしますし、双方向データバインディング自体がある種の諸刃の剣のようなツールだと個人的には考えています。

vue3では v-model を使います。

https://v3.ja.vuejs.org/guide/migration/v-model.html#_3-x-での構文
ログインするとコメントできます