はじめに
この記事では、Vue でよく使うディレクティブを React ではどう書くかを整理します。
対象は、Vue のテンプレート記法には慣れているが、React の JSX に入ったときに「これ Vue だと v-bind で書いていたやつはどう書くのか」が気になる人です。
また、Vue と React の両方を学んだうえで、React の明示的な書き方が追いやすいと感じている人にも合う内容にしています。
Vue と React は見た目が似ていても、考え方は少し違います。
Vue は v-bind v-on v-model のように専用のディレクティブが並びますが、React は JSX の属性と JavaScript の式で表現することが多いです。
この差のため、Vue は HTML に機能を足していくテンプレートエンジンのように見えやすく、React は JavaScript の中に UI を書いている感覚になりやすいです。
自分も、学習段階では両方触りましたが、業務で長く使ったのは React です。
その影響もあって、いまは React のほうが追いやすいと感じています。
理由は、専用記法が少なく、「結局これは JavaScript で何をしているのか」が見えやすいからです。
そのため、1つずつ暗記するより、次の対応で見ると理解しやすくなります。
-
v-bindは JSX の属性に値を渡す -
v-onはイベントハンドラを属性で渡す -
v-modelは state とイベント更新を自分で組み合わせる -
v-ifv-forは JavaScript の条件分岐やmapで書く
この記事では、日常的によく使う次のディレクティブに絞って比較します。
v-bindv-onv-modelv-ifv-showv-for
先に対応表を見る
先に全体像を置きます。
| Vue | React で近い書き方 | 考え方 |
|---|---|---|
v-bind:value="name" |
value={name} readOnly |
属性に JS の値を渡す |
v-on:click="handleClick" |
onClick={handleClick} |
イベントハンドラを渡す |
v-model="name" |
value={name} + onChange={...}
|
双方向バインディングを自分で組み立てる |
v-if="show" |
{show && <p>...</p>} |
JSX 内で条件分岐する |
v-show="show" |
style={{ display: show ? undefined : "none" }} / <Activity mode={...}>
|
非表示の考え方を選ぶ |
v-for="item in items" |
items.map(item => ...) |
配列を JSX に変換する |
Vue はテンプレート専用の構文が厚めです。
React は JSX の中で JavaScript の式を使って表現する場面が多いです。
この差を掴むと、個別の記法も追いやすくなります。
なぜ React のほうが分かりやすく感じやすいのか
ここは先に書いておきます。
Vue のほうが短く書ける場面は多いです。
特に v-model はかなり便利です。
ただ、React の書き方が追いやすいと感じる理由もあります。
- 専用ディレクティブをあまり覚えなくてよい
- 条件分岐やループが JavaScript のまま読める
- 「表示」と「更新」が分かれて見える
- フォーム制御や props 渡しが JSX の書き方に揃う
Vue はテンプレート記法として整理されているぶん、覚えると速いです。
一方で React は、便利な省略記法が少ない代わりに、何が起きているかを追いやすいです。
Vue はテンプレートエンジンのように専用記法を積み上げて読む感覚があり、React は JavaScript の式としてそのまま追う感覚があります。
そのため、実務で保守するコードとして見ると、React の明示的な書き方のほうが理解しやすいと感じる人もいます。
v-bind は JSX の属性になる
Vue では v-bind で属性に値を渡します。
<script setup lang="ts">
import { ref } from "vue";
const inputNameBind = ref("しんちゃん");
</script>
<template>
<input type="text" v-bind:value="inputNameBind">
</template>
React では次のようになります。
export function Sample() {
const inputNameBind = "しんちゃん";
return <input type="text" value={inputNameBind} readOnly />;
}
ここでやっていることはシンプルです。
- Vue は
v-bind:value="..."と書く - React は
value={...}と書く
つまり React では、v-bind 相当の専用キーワードを使わず、属性にそのまま JavaScript の式を入れます。
React で input に value を渡すと、その入力欄は制御コンポーネントとして扱われます。
この例のように入力値を固定して見せるなら、readOnly を付けた書き方が自然です。
初期値だけを表示したいなら、defaultValue を使う方法もあります。
コンポーネントへの値渡しも同じです
Vue:
<script setup lang="ts">
import { ref } from "vue";
import OneInfo from "./components/OneInfo.vue";
const propsTitle = ref("発生した乱数");
const propsContent = ref(Math.round(Math.random() * 100));
</script>
<template>
<OneInfo
v-bind:title="propsTitle"
v-bind:content="propsContent"/>
</template>
React:
type OneInfoProps = {
title: string;
content: number;
};
function OneInfo({ title, content }: OneInfoProps) {
return (
<section>
<h2>{title}</h2>
<p>{content}</p>
</section>
);
}
export function Sample() {
const propsTitle = "発生した乱数";
const propsContent = Math.round(Math.random() * 100);
return <OneInfo title={propsTitle} content={propsContent} />;
}
Vue では v-bind が「値を渡している」という印になります。
React では title={...} content={...} と直接書くので、props 渡しと通常属性が同じ見た目になります。
v-on は onClick のようなイベント属性になる
Vue ではイベント処理を v-on で書きます。
<script setup lang="ts">
const handleClick = (): void => {
console.log("clicked");
};
</script>
<template>
<button v-on:click="handleClick">
クリック
</button>
</template>
React ではイベントハンドラ属性に関数を渡します。
export function Sample() {
const handleClick = () => {
console.log("clicked");
};
return (
<button onClick={handleClick}>
クリック
</button>
);
}
- Vue は
v-on:click="handleClick"と書く - React は
onClick={handleClick}と書く
入力フォームでは React の onChange がよく出てきますが、これは v-on 単体というより v-model に近い話です。
value と onChange を組み合わせて state を更新する形になるため、その整理は次の v-model で見た方が分かりやすいです。
v-model は React では分解して書く
Vue では v-model がかなり強力です。
<script setup lang="ts">
import { ref } from "vue";
const name = ref("名無し");
</script>
<template>
<input type="text" v-model="name">
<p>{{name}}さんですね!</p>
</template>
React では、これを次の2つに分けて書きます。
- 表示する値
- 変更時に state を更新する処理
import { useState } from "react";
export function Sample() {
const [name, setName] = useState("名無し");
return (
<section>
<p>{name}さんですね!</p>
<input
type="text"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</section>
);
}
Vue だと v-model ひとつで済むところを、React では value と onChange に分けます。
このため React は少し冗長に見えます。
ただ、そのぶん「どの値を表示して、どのイベントで、どう更新するか」が明示的です。
React の書き方が追いやすいと感じるなら、この明示性が理由であることが多いです。
Vue の v-model を React に写すときは、まず次に分解すると迷いにくいです。
- どの state を表示しているか
- どのイベントで更新するか
- 更新時に値変換が必要か
v-if は JavaScript の条件分岐になる
Vue:
<script setup lang="ts">
import { ref } from "vue";
const number = ref(80);
</script>
<template>
<p v-if="number >= 50">
条件に合致したので表示
</p>
</template>
React:
export function Sample() {
const number = 80;
return (
<>
{number >= 50 && <p>条件に合致したので表示</p>}
</>
);
}
React では v-if のような専用ディレクティブはなく、&& や三項演算子を使って JSX を出し分けます。
{isPremium ? <PremiumPanel /> : <BasicPanel />}
ここは React の重要な違いです。
テンプレート記法で条件分岐するのではなく、JavaScript の式として条件分岐します。
&& は便利ですが、左辺は boolean になる条件に寄せた方が安全です。
例えば {count && <p>表示</p>} のように書くと、count が 0 のときに 0 がそのまま出ることがあります。
そのため、{count > 0 && <p>表示</p>} のように条件を明示した方が混乱しにくくなります。
v-show は style や className、Activity で考える
Vue の v-if と v-show は似ていますが、意味は同じではありません。
-
v-ifは要素自体を出したり消したりする -
v-showは要素を残したまま表示だけ切り替える
Vue:
<script setup lang="ts">
import { ref } from "vue";
const showOrNot = ref(true);
</script>
<template>
<p v-show="showOrNot">
条件に合致したので表示
</p>
</template>
React では単純に見た目だけ切り替えるなら、style や className で表現できます。
export function Sample() {
const showOrNot = true;
return (
<p style={{ display: showOrNot ? undefined : "none" }}>
条件に合致したので表示
</p>
);
}
display: "block" と固定すると、要素本来の表示形式を変えてしまうことがあります。
そのため、非表示のときだけ display: "none" を当てる方が汎用的です。
実務では className で切り替えることも多いです。
React 19 では、標準の <Activity> も選べます。
import { Activity } from "react";
export function Sample() {
const showOrNot = true;
return (
<Activity mode={showOrNot ? "visible" : "hidden"}>
<Sidebar />
</Activity>
);
}
<Activity> は、非表示中に子要素を視覚的に隠しつつ、内部 state を保持したまま再表示できます。
さらに、非表示中の effect を破棄し、再表示時に復元する動きもあります。
v-if と v-show の違いを React 側で考えると、次の整理になります。
- 非表示時に DOM から消したいなら条件分岐で描画しない
- 単純に見た目だけ切り替えたいなら
styleやclassNameを使う - 非表示中も内部 state を保ったまま戻したいなら
<Activity>を検討する
v-for は map で書く
Vue:
<script setup lang="ts">
import { ref } from "vue";
interface Cocktail {
id: number;
name: string;
price: number;
}
const cocktailDataList = ref<Cocktail[]>([
{ id: 2345, name: "ホワイトレディ", price: 1200 },
{ id: 4412, name: "ブルーハワイ", price: 1500 },
{ id: 6792, name: "ニューヨーク", price: 1100 },
{ id: 8429, name: "マティーニ", price: 1500 },
]);
</script>
<template>
<ul>
<li
v-for="cocktailItem in cocktailDataList"
v-bind:key="cocktailItem.id">
{{cocktailItem.name}}の値段は{{cocktailItem.price}}円
</li>
</ul>
</template>
React:
type Cocktail = {
id: number;
name: string;
price: number;
};
export function Sample() {
const cocktailDataList: Cocktail[] = [
{ id: 2345, name: "ホワイトレディ", price: 1200 },
{ id: 4412, name: "ブルーハワイ", price: 1500 },
{ id: 6792, name: "ニューヨーク", price: 1100 },
{ id: 8429, name: "マティーニ", price: 1500 },
];
return (
<ul>
{cocktailDataList.map((cocktailItem) => (
<li key={cocktailItem.id}>
{cocktailItem.name}の値段は{cocktailItem.price}円
</li>
))}
</ul>
);
}
考え方はかなり近いですが、書き方は違います。
- Vue は
v-for - React は
array.map(...)
また、key が重要なのは両方同じです。
Vue では v-bind:key として見えますが、React では <li key={...}> の形で直接書きます。
Vue から React に移るときに混乱しやすい点
ここまでの違いをまとめると、混乱の元は次の3つです。
- Vue は「何をしたいか」がディレクティブ名で見える
- React は JSX の属性と JavaScript の式に分散して見える
- React には
v-modelのような便利な短縮記法が少ない
ただ、React 側の見方に慣れると、ほぼ次の一文で整理できます。
「テンプレート専用の構文で書いていたものを、JSX と JavaScript に展開して書く」
Vue がテンプレートエンジンっぽく見えて、React が JavaScript 直書きっぽく見えるのは、まさにこの差から来ています。
この視点で見ると、対応関係は追いやすくなります。
そのまま1対1対応しないもの
最後に、Vue の主要ディレクティブの中でも、そのまま 1 対 1 で置き換えないものを挙げます。
v-html
React では dangerouslySetInnerHTML を使います。
ただし名前のとおり扱いに注意が必要です。
<div dangerouslySetInnerHTML={{ __html: html }} />
v-once
React に v-once そのものはありません。
再レンダリングを減らしたいなら、コンポーネント分割や memo を検討します。
v-pre
React ではテンプレートコンパイルを止めるための専用ディレクティブは通常意識しません。
v-cloak
React では初期描画の見え方は、マウント前の HTML やローディング設計の話として考えることが多いです。
まとめ
Vue のディレクティブを React に写すときは、個別記法を丸暗記するより、まず次の対応で考えると整理しやすいです。
-
v-bindは JSX 属性 -
v-onはイベントハンドラ属性 -
v-modelはvalue + onChange -
v-ifは条件分岐 -
v-showはstyleやclassName、場合によっては<Activity> -
v-forはmap
Vue はテンプレート記法が厚く、React は JavaScript に寄せて書きます。
そのため、Vue ではディレクティブ単位で覚えていたものを、React では「JSX に値を渡す」「条件分岐する」「配列を描画する」という JavaScript の操作として読み替えるのが近道です。
もし Vue と React の両方を触っていて、React の書き方が追いやすいと感じているなら、その感覚は自然です。
React は便利な短縮記法が少ないぶん、何をしているコードなのかを追いやすいからです。