0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue 経験者のための React 最速ガイド 🏃‍♂️

Last updated at Posted at 2025-07-06

1. はじめに

  • 筆者: Vue (v2/v3) で業務経験あり、転職先が React + TypeScript

  • 目的: 「そもそも React って何者?」を Vue の視点 で整理

  • 対象:

    • Vue で開発してきたけど React にジョブチェンジしたい
    • Hooks が多すぎて「何から手を付ける?」となっている人

TL;DR Vue→React へ乗り換えるなら まず 3 つの HookuseState / useEffect / useReducer を押さえれば、業務コードの 8 割は読めるようになる。


2. ざっくり比較 – データの流れ編

観点 Vue React
基本構造 MVVM / SFC “UI ライブラリ” (JSX)
データ → UI 自動リアクティブ (data) 片方向: 親→子 (props)
算出 / キャッシュ computed useMemo
副作用 watch, mounted useEffect
複雑な状態管理 Vuex / Pinia useReducer, Context, Redux など

要点
React は「親 → 子に一方通行」。子→親は「コールバックで通知→親が state 更新」のみ。双方向バインディングは存在しないので、フォームは Controlled Component として書く。


3. useState — "変わる値" をコンポーネント内で持つ

何者? コンポーネント内に "状態" を作り、値が変わった瞬間に 再レンダリング を発生させる Hook。

いつ使う?

  • カウンターやフォーム入力のように ユーザー操作で変わる
  • タブ開閉、モーダル表示など UI の ON/OFF
  • 外部 API 取得結果を一時的に保持するとき(グローバル共有が不要な場合)

使わない例

  • コンポーネント外 (URL, ローカルストレージなど) にすでに ソースオブトゥルース が存在する値 ← useState は不要で props で受け取れば OK

※ソースオブトゥルース (Single Source of Truth):** アプリ全体で「この値はここだけが正なのだ」と決めた唯一の保管場所**。React では state を増やす前に「その値の元データはどこか?」を必ず確認する。

3‑1. シグネチャ

const [state, setState] = useState<>(初期値);

3‑2. 更新方法 2 パターン

書き方 いつ使う? コード例
直接値 setState(newValue) 前回の値を参照しない setName("Ken")
関数型更新 setState(prev => prev + 1) 前回値が必要(連打・非同期) setCount(prev => prev + 1)
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(prev => prev + 1)}>
      {count} clicks
    </button>
  );
}

3‑3. オブジェクト State とスプレッド構文(...

React は 置き換え更新―すなわち “新しいオブジェクトで丸ごと差し替える” ことで変化を検知します。

スプレッド構文とは?

const user = { name: "A", age: 20 };
const updated = { ...user, age: 21 };
// => { name: "A", age: 21 }

...foo でオブジェクト(または配列)の中身を“展開”しコピーできます。

Vue と React の違い(リアクティブ更新 vs 置き換え更新)

フレームワーク 更新例 画面に反映?
Vue this.user.age = 21 自動リアクティブ
React (NG) user.age = 21; setUser(user); × 参照が不変で検知できない
React (OK) setUser(prev => ({ ...prev, age: 21 })); 新オブジェクトなので再レンダリング

既存オブジェクトを 直接 変更すると参照が変わらず React は「未変更」と判断します。

実戦パターン

const [user, setUser] = useState({
  name: "田中",
  age: 25,
  email: "tanaka@example.com"
});

const updateAge = () => {
  setUser(prev => ({ ...prev, age: prev.age + 1 }));
};
  • ...prev既存キーをコピー
  • 更新したいキーだけあとから上書き

4. useEffect — 副作用 (fetch / 監視) を 1 か所に集約

何者? DOM へのアクセス / API 通信 / イベント購読など、レンダリング以外の処理(=副作用) を安全に書くための Hook。

いつ使う?

  • データ取得 : fetch, axios, GraphQL などの非同期リクエスト
  • サブスクリプション : addEventListener, WebSocket, setInterval など
  • 外部ライブラリとの連携 : Chart.js や Map SDK の初期化
  • DOM 操作 : document.title 変更、スクロール位置調整

使わない例

  • 純粋な計算やフィルタリング → useMemo / 通常関数で十分

4‑1. シグネチャ

useEffect(effectFn, deps?);
引数 意味
effectFn 実行したい副作用。戻り値に “クリーンアップ関数” を返せる
deps 依存配列。依存が変わるたび effect を再実行

クリーンアップ関数とは?
effectFnreturn した無名関数。コンポーネントが 消えるとき または 次の useEffect が走る直前 に React が自動実行します。

書き方の型

useEffect(() => {
  /* 副作用の開始 */
  return () => {
    /* クリーンアップ処理 (後片付け) */
  };
}, deps);

何を片付ける?

  • addEventListenerremoveEventListener
  • setInterval / setTimeoutclearInterval / clearTimeout
  • WebSocket / SSE → socket.close()
  • 進行中の fetch → AbortController.abort()

忘れると起こること

  • イベントハンドラが重複してパフォーマンス低下
  • タイマーが増殖してメモリリーク
  • WebSocket が残りっぱなしで接続数上限に達する

借りたものは返す・使ったものは片付ける — React の副作用も同じ発想です。

useEffect(() => {
  const id = setInterval(log, 1000); // ① 副作用を開始
  return () => clearInterval(id);    // ② クリーンアップ
}, []);

上の例では ❶ マウント時に setInterval が走り、❷ アンマウント時に clearInterval で後始末。依存配列 [foo] がある場合は「foo が変わる→旧タイマー停止→新タイマー開始」の順に実行される。依存が変わるたび effect を再実行 |

4‑2. 実行タイミングとクリーンアップ

  1. 初回マウント後(DOM が描画された直後)

  2. deps のいずれかが変わるたび

  3. Strict Mode(開発時のみ)で 意図的に 2 回 実行

    • なぜ? 予期せぬ副作用(破壊的 API 呼び出し・二重購読など)を検出するため、React 18 以降は 開発モードuseEffectmount → unmount → mount という擬似サイクルでテストする。
    • 本番ビルドでは 1 回 だけ。パフォーマンスへの影響はなし。
  4. 次回実行前 or アンマウント時に クリーンアップ関数 が呼ばれる

よくあるパターン

① データフェッチ
useEffect(() => {
  let canceled = false;
  (async () => {
    const res = await fetch("/api/user");
    if (!canceled) setUser(await res.json());
  })();
  return () => { canceled = true; };
}, []); // マウント時 1 回
② イベント登録 & クリーンアップ
useEffect(() => {
  const handler = () => setX(window.innerWidth);
  window.addEventListener("resize", handler);
  return () => window.removeEventListener("resize", handler);
}, []);

4‑3. ESLint exhaustive-deps で事故防止

  • 依存配列に入れ忘れた変数を自動検知
  • 無視する場合はコメントより ロジックの分割 を検討

5. useReducer — 小さな Redux / フォームや複雑ロジックに最適

何者? 複数の useState が絡み合って読みにくくなったとき、更新ロジックを 1 箇所(=reducer) に集約できる Hook。

いつ使う?

  • フォーム入力のステップごとに多くのフィールドを更新
  • Undo / Redo のように「変更履歴」を追いたい
  • setState 連打で "○○ が先に走ったせいで△△が上書き" を防ぎたい

ポイント: Action を Union 型 で列挙 → コンパイル時にカバー漏れを検出できる。

5‑1. シグネチャ

const [state, dispatch] = useReducer(reducerFn, initialArg, initFn?);
引数 意味
reducerFn (state, action) => newState 純粋関数
initialArg 初期 state
initFn (省略可) 遅延初期化したいとき (initialArg)=>initialState

reducerFn は「純粋関数 (Pure Function)」

複数のコンポーネントから呼ばれても 同じ入力 (state, action) に対して常に同じ newState を返し、副作用を起こさない 関数です。

ワード 意味
state いま画面が持っている値 { count: 3 }
action “こう変えてほしい” 命令オブジェクト { type: "inc" }
newState action 適用後の次の状態 { count: 4 }
// 純粋関数のイメージ
const reducer = (state, action) => {
  switch (action.type) {
    case "inc":  return { count: state.count + 1 };
    case "dec":  return { count: state.count - 1 };
    default:      return state; // 未知の action は現状維持
  }
};
  • 副作用ゼロ: fetch/console.log は書かない。
  • 入力=出力が決定的 だから タイムトラベルユニットテスト が楽。

5‑2. サンプル: 多段フォーム

type State = {
  page: 1 | 2 | 3;
  form: { name: string; age: number };
};

type Action =
  | { type: "next" }
  | { type: "prev" }
  | { type: "update"; payload: Partial<State["form"]> };

const reducer = (s: State, a: Action): State => {
  switch (a.type) {
    case "next":   return { ...s, page: (s.page + 1) as State["page"] };
    case "prev":   return { ...s, page: (s.page - 1) as State["page"] };
    case "update": return { ...s, form: { ...s.form, ...a.payload } };
  }
};

5‑3. DevTools 連携

  • Redux DevToolsuseReducer でも利用可能: import reduxDevTools from "redux-devtools-extension"
  • Action 履歴をタイムトラベルで確認=バグ調査が容易

5‑4. Undo / Redo を実装できるワケ

useReducer「状態をどう変換するか」を Action + 純粋関数で記録 するため、
過去・現在・未来のスナップショットを 1 か所 で管理できます。

// ⬇︎ 3 つのスロットに分割
// past  : 巻き戻し用スタック
// present: 画面に表示する現在
// future: 先読み(redo)キュー

const initial = { past: [], present: 0, future: [] };

type A = { type: "inc" } | { type: "dec" } | { type: "undo" } | { type: "redo" };

const reducer = (s: typeof initial, a: A) => {
  switch (a.type) {
    case "inc":
    case "dec": {
      const next = a.type === "inc" ? s.present + 1 : s.present - 1;
      return { past: [...s.past, s.present], present: next, future: [] };
    }
    case "undo": {
      if (!s.past.length) return s;
      const previous = s.past[s.past.length - 1];
      return { past: s.past.slice(0, -1), present: previous, future: [s.present, ...s.future] };
    }
    case "redo": {
      if (!s.future.length) return s;
      const next = s.future[0];
      return { past: [...s.past, s.present], present: next, future: s.future.slice(1) };
    }
  }
};
  • 状態遷移を reducer 内だけで完結 → UI 側は dispatch({ type: "undo" }) するだけ
  • 過去・未来の履歴 をオブジェクトに保持できるのは「state を直接 mutate しない=不変更新」だからこそ
  • DevTools と組み合わせれば タイムトラベルデバッグ が実質 “Undo / Redo” と同義

6. 依存配列チートシート(Vue 対比版)

React – useEffect Vue 等価処理 実行タイミング
useEffect(() => {}, []) mounted() マウント時に 1 回だけ
useEffect(() => {}, [foo]) watch: { foo() { … } } foo が変わるたび
useEffect(() => {}) updated() 毎レンダリング(依存配列を省略すると全再実行)

メモ

  • 依存配列を省略したパターンはパフォーマンスとバグの温床。ほぼ使わない。
  • Vue の watch{ immediate: true } を付けた挙動にしたい場合は、useEffect で初回も走るので追加設定不要。

7. デバッグ&安全運用 Tips デバッグ&安全運用 Tips

  • React DevTools: state/props をリアルタイム確認
  • Strict Mode: 副作用の二重実行で非同期バグを早期発見
  • ESLint exhaustive-deps: 依存配列漏れを自動警告
  • カスタム Hook: 重複ロジックを再利用・テスト容易に

8. 2025 年版スターター – Vite 一択

npm create vite@latest my-app -- --template react-ts
cd my-app
npm i
npm run dev

Create React App はメンテモード。Vite or Next.js を推奨。


9. まとめ

  1. Vue → React の最大ギャップは 双方向バインディングが無いこと。
  2. まず useState / useEffect / useReducer を理解すれば業務コードの大半は読める。
  3. Strict Mode + ESLint で副作用の落とし穴を防ぐ。
  4. 物足りなくなったら Context → Redux / Zustand へスケール。

React は関数ファースト。 最初は冗長でも、可読性とテストしやすさで後々効いてきます。


コメント・質問歓迎!

「ここが React で困った」「こう解決した」など気軽にコメントください!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?