はじめに
この記事では、React と Vue の書き方の違いを、コードを読む順番から整理します。
Vue と React の比較では、テンプレートと JSX、v-if と条件分岐、v-model と value + onChange のような個別記法がよく話題になります。
ただ、実務でコードを読むときに効いてくるのは、記法そのものだけではありません。
同じ画面を作っても、Vue と React では次の感覚がかなり違います。
- どこから読み始めるか
- 状態と表示の関係をどこで見るか
- イベント処理をどこで追うか
- 表示用の値をどこで作るか
この記事では、個別記法の対応表ではなく、「同じ画面を書いたときに、どう読み味が変わるか」を整理します。
先に結論
ざっくり言うと、次の違いです。
- Vue はテンプレートから画面の形を読みやすい
- React は JavaScript / TypeScript の処理の流れを追いやすい
Vue は、template を見ると画面の構造が分かりやすいです。
HTML に近い形で、条件分岐、繰り返し、イベント、入力が並びます。
一方で React は、コンポーネント関数の中で状態、派生値、イベント、表示をまとめて扱います。
最初は JSX に慣れが必要ですが、慣れると「この関数の中で何が起きているか」を追いやすいです。
どちらが常に良いという話ではありません。
Vue は画面の形を先に見たいときに読みやすいです。
React は処理の流れを先に見たいときに読みやすいです。
この記事で扱わないこと
既に別の記事で整理している内容とは、なるべく重ならないようにします。
この記事では、次の話は深掘りしません。
-
v-ifやv-forを React でどう書くか -
v-modelとvalue + onChangeの細かい違い - Composition API と React Hooks の違い
-
computedとwatchの使い分け
ここでは、もっと上の層として「コード全体をどう読むか」に絞ります。
読み始める場所が違う
Vue と React では、最初に見る場所が変わります。
Vue では、まず template から読むことが多いです。
画面に何が表示されるかを先に見て、その後で script setup 側の状態や関数を確認します。
一方 React では、コンポーネント関数を上から読むことが多いです。
useState で状態を確認し、派生値を見て、最後に return の JSX を読みます。
つまり Vue は「画面から処理へ」、React は「処理から画面へ」読む感覚に近いです。
この記事の「読む順番」とは、この最初に見る場所の違いを指しています。
同じ画面を Vue で書く
例として、ユーザー一覧を絞り込み、選択したユーザー名を表示する小さな画面を考えます。
Vue では次のように書けます。
<script setup lang="ts">
import { computed, ref } from "vue"
type User = {
id: number
name: string
}
const users = ref<User[]>([
{ id: 1, name: "山田" },
{ id: 2, name: "佐藤" },
{ id: 3, name: "鈴木" },
])
const keyword = ref("")
const selectedId = ref<number | null>(null)
const filteredUsers = computed(() => {
return users.value.filter((user) => user.name.includes(keyword.value))
})
const selectedUser = computed(() => {
return users.value.find((user) => user.id === selectedId.value)
})
const selectUser = (id: number) => {
selectedId.value = id
}
</script>
<template>
<section>
<input v-model="keyword" placeholder="名前で検索">
<ul>
<li v-for="user in filteredUsers" :key="user.id">
<button @click="selectUser(user.id)">
{{ user.name }}
</button>
</li>
</ul>
<p v-if="selectedUser">
選択中: {{ selectedUser.name }}
</p>
</section>
</template>
Vue の場合、まず template を見ると画面の構造が分かります。
- 入力欄がある
- 一覧を表示している
- ボタンを押すと選択する
- 選択中のユーザーを表示している
画面の形を先に掴み、その後で script setup 側を見ると、状態や計算値の意味を確認できます。
この読み方ができるのは Vue の強みです。
UI の見た目とテンプレートの形が近いため、画面構造を追いやすいです。
同じ画面を React で書く
React で書くと、次のようになります。
import { useState } from "react"
type User = {
id: number
name: string
}
const users: User[] = [
{ id: 1, name: "山田" },
{ id: 2, name: "佐藤" },
{ id: 3, name: "鈴木" },
]
export function UserList() {
const [keyword, setKeyword] = useState("")
const [selectedId, setSelectedId] = useState<number | null>(null)
const filteredUsers = users.filter((user) => user.name.includes(keyword))
const selectedUser = users.find((user) => user.id === selectedId)
return (
<section>
<input
value={keyword}
placeholder="名前で検索"
onChange={(event) => setKeyword(event.target.value)}
/>
<ul>
{filteredUsers.map((user) => (
<li key={user.id}>
<button onClick={() => setSelectedId(user.id)}>
{user.name}
</button>
</li>
))}
</ul>
{selectedUser && (
<p>
選択中: {selectedUser.name}
</p>
)}
</section>
)
}
React の場合、コンポーネント関数の中に状態、派生値、イベント、表示がまとまっています。
読むときは、だいたい次の順番になります。
-
useStateで何の状態を持っているかを見る -
filteredUsersやselectedUserで何を計算しているかを見る -
returnの JSX でどう表示しているかを見る - イベントでどの state を更新しているかを見る
Vue と比べると、HTML 的な見た目からは少し離れます。
ただし、処理は JavaScript / TypeScript の流れとして追いやすいです。
filteredUsers は配列の filter です。
selectedUser は配列の find です。
ボタンを押したら setSelectedId を呼びます。
入力が変わったら onChange でイベントを受け取り、setKeyword で state を更新します。
専用記法を別に読むというより、JavaScript の処理として読めます。
派生値の置き方が違う
状態から作る値の置き方にも違いがあります。
Vue では、状態から作る値を computed として明示します。
const filteredUsers = computed(() => {
return users.value.filter((user) => user.name.includes(keyword.value))
})
filteredUsers は、users と keyword から作られる値です。
Vue では、このような値を computed にすることで「これは状態から作る派生値です」と宣言します。
React では、単純な派生値ならコンポーネント関数内の普通の変数として書くことが多いです。
const filteredUsers = users.filter((user) => user.name.includes(keyword))
React では、コンポーネントがレンダリングされるたびに関数が実行されます。
その中で users.filter(...) を実行し、今回の表示に必要な値を作ります。
Vue は派生値であることを明示する感覚が強いです。
React はレンダリング中に計算される値として、JavaScript の変数に自然に置く感覚があります。
もちろん React でも、計算が重い場合や参照を安定させたい場合は useMemo を使います。
ただ、この例のような小さな絞り込みでは、まず普通の変数として読む方が自然です。
イベント処理の追い方も違う
イベント処理も、どこを見ると理解しやすいかが変わります。
Vue では、テンプレート上にイベントを書きます。
<button @click="selectUser(user.id)">
{{ user.name }}
</button>
この行を見ると、ボタンを押したときに selectUser(user.id) が呼ばれることが分かります。
処理の本体を知りたい場合は、script setup 側の関数定義を見に行きます。
const selectUser = (id: number) => {
selectedId.value = id
}
Vue は、テンプレートで「何が起きるか」を見て、必要に応じて処理本体へ移動する読み方になります。
React では、JSX のその場で state 更新まで見えることがあります。
<button onClick={() => setSelectedId(user.id)}>
{user.name}
</button>
この例では、クリックしたら setSelectedId(user.id) を呼ぶことが JSX 上で分かります。
関数を別に切り出していないため、イベントから state 更新までの距離が短いです。
Vue は画面上のイベント名から処理へ進みます。
React は JSX の中で、イベントと更新処理をまとめて読むことが多いです。
Vue は画面の形が先に見える
Vue の読みやすさは、画面の形が先に見えるところにあります。
<input v-model="keyword" placeholder="名前で検索">
この1行を見ると、入力欄と keyword がつながっていることが分かります。
<li v-for="user in filteredUsers" :key="user.id">
この行を見ると、filteredUsers を一覧表示していることが分かります。
Vue のテンプレートは、UI の構造を読むにはかなり素直です。
デザイナー寄りの人や、HTML の見た目から入りたい人には読みやすいです。
一方で、処理の詳細は Vue の記法を理解している前提になります。
-
v-modelが何を省略しているのか -
v-forがどのように配列を描画するのか -
@clickが何のイベントを扱うのか -
computedがいつ再計算されるのか
このあたりを知らないと、短く見えるぶん、逆に裏側を想像しにくいことがあります。
React は処理の流れが先に見える
React の読みやすさは、処理の流れが JavaScript / TypeScript として見えるところにあります。
const filteredUsers = users.filter((user) => user.name.includes(keyword))
これは普通の配列操作です。
onChange={(event) => setKeyword(event.target.value)}
これは入力イベントを受け取り、state を更新しています。
値の入口が event.target.value で、更新先が setKeyword だとその場で分かります。
{filteredUsers.map((user) => (
<li key={user.id}>
{user.name}
</li>
))}
これは配列を JSX に変換しています。
React は書く量が増えやすいです。
ただし、処理が見えているため、どこで値が変わるのかは追いやすいです。
特に TypeScript を普段から書いている人にとっては、React の方が「知らない専用記法」が少なく感じることがあります。
書き方の違いは責務の置き場所の違い
Vue と React の書き方の違いは、単に記法の違いではありません。
責務の置き場所が違います。
Vue は、テンプレートに UI の意図を置きます。
- 表示する
- 繰り返す
- 条件で出す
- イベントを受ける
- 入力と値をつなぐ
React は、JavaScript / TypeScript の式として UI を組み立てます。
- 配列を
mapする - 条件式で JSX を出す
- イベントで state を更新する
- state から表示を作る
Vue は UI 専用の読みやすさを作ります。
React は JavaScript の一貫性を保ちます。
この差が、書き味の違いとして出ます。
どちらが読みやすいかは場面で変わる
画面の構造をすぐ見たいなら、Vue は読みやすいです。
template を見れば、UI の骨格が分かります。
入力欄、一覧、条件表示、イベントがテンプレート上に並ぶため、画面の形を掴みやすいです。
一方で、状態の流れや値の変化を追いたいなら、React は読みやすいです。
コンポーネント関数の中で、state、派生値、イベント、JSX がつながっています。
JavaScript / TypeScript の関数として読めるため、ロジックの流れを追いやすいです。
特に、バックエンド寄りで TypeScript に慣れている人は、React の方が自然に感じることがあります。
Vue と React を比べると、Vue の方が短く見えることが多いです。
例えばフォーム入力では、Vue は v-model で短く書けます。
React は value と onChange を分けて書きます。
ただし、短いことと読みやすいことは同じではありません。
Vue の短さは、Vue がよくある処理を包んでくれているからです。
React の冗長さは、表示と更新を明示しているからです。
onChange から setKeyword までが見えるので、入力値がどこで state に入るかを追いやすくなります。
そのため、比較するときは次の問いで見る方が実務的です。
- 初見で画面の形を掴みやすいのはどちらか
- 値がどこで変わるか追いやすいのはどちらか
- チームのメンバーが慣れているのはどちらか
- TypeScript の知識をそのまま使いやすいのはどちらか
- フレームワーク固有の作法をどこまで許容できるか
この観点で見ると、単純に「短いから Vue」「明示的だから React」とは言い切れません。
画面が大きくなると差が出る
小さいサンプルでは、Vue も React も大きな差はありません。
どちらのコードも短く、全体をすぐに読めます。
ただ、画面が大きくなると読み方の違いが効いてきます。
Vue は template を見れば UI の骨格を掴みやすいです。
入力欄、一覧、条件表示、イベントの位置が、画面に近い形で並びます。
一方で、処理が増えると script setup 側との往復が増えます。
テンプレートで見つけた filteredUsers や selectUser の意味を、上の script まで戻って確認する場面が出てきます。
React は JSX と処理が同じ関数内にあるため、値の流れは追いやすいです。
状態、派生値、イベント、表示が同じスコープにあります。
一方で、JSX に条件分岐や map が増えると、見た目の構造は少し読みにくくなります。
JavaScript の式としては自然でも、HTML の骨格だけを眺めたいときには情報量が多くなります。
この差は、どちらが優れているかではありません。
画面構造を先に見たいのか、値の流れを先に見たいのかの違いです。
まとめ
React と Vue は、同じ画面を作れても、コードの読み方がかなり違います。
ポイントをまとめると次のとおりです。
- Vue はテンプレートから画面の形を読みやすい
- React は JavaScript / TypeScript の処理の流れを追いやすい
- Vue は UI 専用の記法で短く書ける
- React は状態更新や配列操作が明示的に見えやすい
- 比較するときはコード量ではなく、どこから読むと理解しやすいかを見るとよい
Vue は「画面をテンプレートとして整理して読む」感覚に近いです。
React は「関数の中で状態から UI を作る」感覚に近いです。
この違いを押さえると、Vue と React の書き方を単なる好みではなく、読み方の違いとして整理しやすくなります。
Vue と React の違いは、どちらが簡単かではなく、どこに読みやすさを置いているかの違いです。
Vue はテンプレートに読みやすさを置き、React は JavaScript / TypeScript の流れに読みやすさを置いています。
テンプレートと JSX の違いは「VueのテンプレートとReactのJSXは何が違うのか」で整理しています。
個別の記法の対応は「Vueの主要ディレクティブをReactで書くとどうなるか」も関連します。
フォーム入力の違いは「Vueのv-modelは便利だがReactのvalueとonChangeはなぜ分かりやすいのか」でも整理しています。