【TechGrowUp】 https://techgrowup.net エンジニアを強くする Sat, 31 May 2025 06:35:58 +0000 ja hourly 1 https://wordpress.org/?v=6.8.1 https://techgrowup.net/wp-content/uploads/2021/05/hp-icon-150x150.png 【TechGrowUp】 https://techgrowup.net 32 32 実際に行ってきました!大阪万博2025の日記 https://techgrowup.net/expo-2025-osaka-0526-review/ https://techgrowup.net/expo-2025-osaka-0526-review/?noamp=mobile#respond Sat, 31 May 2025 06:32:49 +0000 https://techgrowup.net/?p=2852 日記

5月25日に観光を兼ねて大阪に前泊しました。翌日の万博に備え、梅田エリアのホテルに宿泊しました。
5月26日は万博の入場を11時に予約していたので、朝10時頃に梅田駅からOsaka Metro中央線のコスモスクエア駅経由で夢洲駅へ向かいました。

10時40分ごろに夢洲駅に到着しましたが、東側ゲートはすでに大混雑。早めに入れるだろうと期待していましたが、実際は1時間近く待たされ、入場は11時30分頃となりました。

入場後、万博の会場構成を把握していなかったため、まずは中央に位置する「大屋根リング」近くまで歩きました。途中、東ゲート付近にはNTT、パナソニック、三菱などの民間企業のパビリオンが並び、大屋根リングの内側には各国パビリオンやイベントエリアが配置されていました。

万博公式アプリの「会場マップ」がとても便利で、自分の現在地や各パビリオンの位置がリアルタイムで確認できました。以下のサイト上でも利用できるのでこちらでも可能です。

EXPO 2025 Visitors
EXPO 2025 Visitorsは大阪・関西万博の来場者向け公式ポータルサイトです。会場マップ、パビリオンやイベントの最新情報などを提供しております。

人気の国別パビリオンはどこも長蛇の列だったため、まずは比較的空いていた「コモンズA」に入りました。ここではサモアやイエメンなど、普段馴染みのない国々の展示を楽しめました。

次に大屋根リングに登りましたが、当初は1周するつもりが、あまりの人混みと想像以上の大きさに途中で断念しました。しかし上から眺める会場全景は非常に美しく、ぜひおすすめしたいスポットです。

リングを降りた場所にバングラデシュ館があったので訪れました。日本と似た国旗に親近感が湧きました。またドラゴンボールのイラストなどが展示されており、日本のアニメ文化の浸透を感じました。隣接していたセネガル館も訪れましたが、さらに隣のエジプト館は1時間待ちだったため諦めました。この時点で13時ごろでした。

予約していた三菱館の時間が迫ったため、会場の反対側の東ゲート付近まで急ぎました。徒歩で15分ほどかかり、万博の敷地が想像以上に広いことを実感しました。
三菱館では写真撮影禁止でしたが、深海から宇宙までを旅する物語仕立ての展示がありました。特にプラネタリウム形式の映像展示が印象的で、ISSへの物資輸送や衛星打ち上げの情報に加えて、火星探査機の展示が興味深かったです。

その後、カタール館とアラブ首長国連邦館を訪問しました。日本にはない砂漠文化に触れることができ、非常に新鮮な体験でした。ここで15時を過ぎていたため、ポルトガル料理の「ビファナ弁当」を購入しました。サンドバーガーのような食感で、日本ではなかなか味わえない美味しさでした。

最後に、予約していた「空飛ぶ車パビリオン」へ行きました。実際の空飛ぶ車の座席に座って記念撮影ができ、映像シミュレーションで空中飛行体験もできました。個人的にはヘリコプターとの違いを実感できませんでしたが、未来の可能性を感じることができました。

帰りの飛行機の時間が迫っていたため、近くのトルコ館とモザンビーク館を訪問してから会場を後にしました。

全体として非常に楽しく、再訪したいと思える万博体験でした。特に夜の景色を見逃したことが心残りで、次回は新幹線で夜までゆっくり過ごす予定です。予約システムの混雑もありましたが、10月のリベンジで再挑戦したいと思います。

]]>
https://techgrowup.net/expo-2025-osaka-0526-review/feed/ 0
Custom Hooks完全ガイド──Reactでロジックを再利用しコンポーネントを超DRY https://techgrowup.net/react-custom-hooks/ https://techgrowup.net/react-custom-hooks/?noamp=mobile#respond Mon, 05 May 2025 11:00:00 +0000 https://techgrowup.net/?p=2846 はじめに

React 公式チュートリアルでも強調される「コンポーネントは UI を、Hooks はロジックを記述する」という原則。しかし現場のコードベースでは、似たような副作用や状態初期化ロジックを複数コンポーネント間でコピペしてしまうことが珍しくありません。Custom Hook を導入すると、共通処理を 1 箇所でテスト・保守でき、UI コンポーネントは宣言的に保たれます。本記事では公式ドキュメント https://react.dev/learn/reusing-logic-with-custom-hooks をベースに、基礎から高度なパターン、テスト、パフォーマンス、型安全まで徹底解説します。

Custom Hooks の基本構文

import { useState, useEffect } from 'react';

export function useClock() {
  const [time, setTime] = useState(() => new Date());

  useEffect(() => {
    const id = setInterval(() => setTime(new Date()), 1000);
    return () => clearInterval(id);
  }, []);

  return time;
}
  • 関数名use から始める
  • ビルトイン Hook は通常どおり使用可能
  • 依存配列は呼び出し元コンポーネントではなく、Hook 内部で完結

よくあるアンチパターン: カスタムフックの中で条件付きフック呼び出し

function useWrong(flag) {
  if (flag) {
    // これはルール違反: フックはトップレベルで呼ぶ必要がある
    useEffect(() => console.log('Bad'), []);
  }
}

React のルールオブフックスに違反し、レンダー順序が崩れる可能性があります。必ずトップレベルで呼び、条件分岐は内部で処理を早期リターンするなどで対応しましょう.

事例1: API フェッチロジックの共通化

ネットワーク通信は多くの画面で発生するため、抽象化すると保守負荷が激減します。

import { useState, useEffect } from 'react';

export function useFetch(url, options) {
  const [status, setStatus] = useState('idle');  // idle | loading | success | error
  const [data, setData]   = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    let ignore = false;
    const controller = new AbortController();

    async function start() {
      setStatus('loading');
      try {
        const res = await fetch(url, { ...options, signal: controller.signal });
        if (!res.ok) throw new Error(res.statusText);
        const json = await res.json();
        if (!ignore) {
          setData(json);
          setStatus('success');
        }
      } catch (e) {
        if (!ignore) {
          setError(e);
          setStatus('error');
        }
      }
    }
    start();

    return () => {
      ignore = true;
      controller.abort();
    };
  }, [url, JSON.stringify(options)]);

  return { status, data, error };
}

ポイント解説

  • AbortController でコンポーネントのアンマウント時にリクエストを中断
  • 依存配列に options がオブジェクトの場合は JSON.stringify で比較簡略化
  • status を文字列列挙にすることで UI が状態ごとに切り替えやすくなる

事例2: フォーム入力とバリデーション

React Hook Form や Formik を使わずに軽量に済ませたい場合、以下のような useForm フックを自作できます。

export function useForm(initial) {
  const [values, setValues] = useState(initial);
  const [errors, setErrors] = useState({});

  const onChange = (e) => {
    const { name, value } = e.target;
    setValues(v => ({ ...v, [name]: value }));
  };

  const validate = (rules) => {
    const newErrors = {};
    for (const key in rules) {
      const error = rules[key](values[key]);
      if (error) newErrors[key] = error;
    }
    setErrors(newErrors);
    return Object.keys(newErrors).length === 0;
  };

  const reset = () => setValues(initial);

  return { values, errors, onChange, validate, reset };
}

使用例

function Signup() {
  const { values, errors, onChange, validate } = useForm({ email: '', pw: '' });

  const handleSubmit = () => {
    if (validate({
      email: v => /@/.test(v) ? '' : 'メールが不正',
      pw: v => v.length >= 8 ? '' : '8文字以上必要'
    })) {
      // 送信
    }
  };
  return (/* JSX */);
}

パフォーマンスとメモ化のベストプラクティス

Custom Hook 内で useMemouseCallback を使う場面:

  1. 計算コストが高いロジックをキャッシュ
  2. 子に渡すコールバックを安定参照に
  3. オブジェクトや配列を返す場合—参照が変わると下位ツリーが再レンダー
function usePagination(total, perPage) {
  const pageCount = useMemo(() => Math.ceil(total / perPage), [total, perPage]);
  return pageCount;
}

TypeScriptとの組み合わせ

Custom Hook の戻り値がオブジェクトの場合、ジェネリクスで型の再利用性を高められます。

function useToggle<S extends string>(on: S, off: S) {
  const [state, setState] = useState<S>(off);
  const toggle = () => setState(s => s === on ? off : on);
  return { state, toggle };
}

const { state } = useToggle<'on' | 'off'>('on', 'off');

テスト戦略

@testing-library/react-hooks を用いると、Hook 単体をテストできます。

import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

test('カウンタが増える', () => {
  const { result } = renderHook(() => useCounter());
  act(() => result.current.inc());
  expect(result.current.count).toBe(1);
});

高度: イベントリスナーを扱う useEventListener フック

DOM イベントを登録・解除するロジックはほぼすべての SPA で必要です。以下の汎用フックをプロジェクトに追加すると、スクロール監視やキー入力検知を簡単に組み込めます。

import { useEffect, useRef } from 'react';

export function useEventListener(type, handler, target = window) {
  const saved = useRef();

  useEffect(() => {
    saved.current = handler;
  }, [handler]);

  useEffect(() => {
    const el = target?.current || target;
    if (!el?.addEventListener) return;
    const listener = (e) => saved.current(e);
    el.addEventListener(type, listener);
    return () => el.removeEventListener(type, listener);
  }, [type, target]);
}

利用例: Escape キーでモーダルを閉じる

function Modal({ onClose }) {
  useEventListener('keydown', e => {
    if (e.key === 'Escape') onClose();
  });
  /* ... */
}

React 18 Concurrent Rendering と Custom Hooks

中断と再実行

並列レンダーではコンポーネントが「途中で止まって再開」される可能性があります。Custom Hook 内で 副作用をレンダーフェーズに書くとバグの温床 になります。

  • NG: useMemo(() => performSideEffect(), [])
  • OK : useEffect 内で副作用を実行し、クリーンアップを返す

スケルトン UI の設計

useSuspenseQuery などデータフェッチ Hook を利用するときは、サスペンス発火タイミングを想定しローディングプレースホルダを計画的に配置しましょう。

パフォーマンス計測: React Profiler で Hook のコストを可視化

  1. React DevTools の Profiler タブを開き「Record」
  2. 画面操作を実行
  3. Flamegraph または Ranked view で再レンダー時間を検査
  4. Custom Hook 導入前後で差分を比較

重い計算が Commit フェーズに長く表示される場合は useMemo、頻繁なレンダーが Render フェーズに点在する場合は useCallbackReact.memo との併用を検討します。

他ライブラリの Custom Hook パターン

  • TanStack Query: useQuery, useMutation
  • Redux Toolkit: useSelector, useDispatch
  • Zustand: useStore(selector)

カスタム Hook のドキュメントとディスカバビリティ

大規模チームでは「どんな Hook があるか分からない」問題が発生します。

  • 命名規約:機能+動詞 (useFetchUser, useDebouncedValue)
  • Storybook Docs: Hook 用ストーリを作り動作を可視化
  • JSDoc/TSDoc と例コードを README に添付

落とし穴と対策一覧

症状原因対処
無限ループ依存配列にオブジェクトリテラルuseMemo で固定 or JSON.stringify
stale data依存配列抜け漏れESLint exhaustive-deps
メモリリークuseEffect 内でクリーンアップ忘れ必ず return で解除
フックのネスト深すぎ抽象化不足/責務過多Hook を小さく分割

いまさら聞けない React ルールオブフックス

  1. トップレベルでのみ呼ぶ
  2. 関数コンポーネント or Custom Hook 内で呼ぶ

エラーハンドリング: useSafeAsync フック

function useSafeAsync(fn, deps = []) {
  const mounted = useRef(true);
  useEffect(() => {
    mounted.current = true;
    fn().catch(console.error);
    return () => (mounted.current = false);
  }, deps);
  const safeSet = updater => {
    if (mounted.current) updater();
  };
  return safeSet;
}

フックファクトリパターン

function createUseApi(base) {
  return function useApi(path) {
    return useFetch(base + path);
  };
}
const useGitHub = createUseApi('https://api.github.com');

まとめ

この記事では Custom Hook の意義から実装、型安全、テスト、パフォーマンス、外部 API 連携、アクセシビリティまで幅広く網羅しました。これらのノウハウを活かし、あなたの React プロジェクトを 再利用性が高く、バグが少なく、パフォーマンスに優れた コードベースへ進化させてください。

]]>
https://techgrowup.net/react-custom-hooks/feed/ 0
useContext超大全──React Context APIでグローバル状態をスマートに共有する https://techgrowup.net/react-usecontext/ https://techgrowup.net/react-usecontext/?noamp=mobile#respond Thu, 01 May 2025 23:00:00 +0000 https://techgrowup.net/?p=2841 はじめに

React アプリが成長するにつれて、コンポーネント間で共通の値を渡す「props ドリリング」が増え、保守性が下がる課題に直面します。Context API と useContext フックは、この問題を標準機能だけで解決できる強力な仕組みです。本連載記事では公式ドキュメント https://react.dev/reference/react/useContext をベースに、基礎・設計・最適化・型安全・周辺ライブラリ まで 2 万文字超で徹底的に解説します。第一部では基本概念と最低限動くコード、スコープ設計、props ドリリングの解消フローをじっくり掘り下げます。

Context API の全体像

  • React.createContext で「箱」を作成
  • Provider が値を注入
  • useContext で最寄りの Provider から値を取得
  • Provider をネストすれば “局所グローバル” を作れる

図式化すると次のようになります。

<Context.Provider value={A}>
  └─ <ChildA />  ← useContext ⇒ A
      └─ <Context.Provider value={B}>
            └─ <ChildB />  ← useContext ⇒ B

外側と内側で値が上書きされるため、テーマやロケールなど「場面に応じて変わる設定」を自然に切り替えられます。

props ドリリング問題とContextの必要性

propsドリリングとは?

親→子→孫→ひ孫…と同じPropsをリレーして渡す状態。深いツリーでは可読性が悪化し、途中の中間コンポーネントが無関係のPropsを抱える。

Contextで解決

Providerをルート付近に置き、任意の深さで直接useContext。不要な中継Propsが消え、各コンポーネントが必要な値だけを購読できます。

具体例

function App() {
  const user = { id: 1, name: 'Taro' };
  return <Page user={user} />;
}
function Page({ user }) {             /* ← 中継だけ */
  return <Sidebar user={user} />;
}
function Sidebar({ user }) {          /* ← 中継だけ */
  return <UserProfile user={user} />;
}
function UserProfile({ user }) {
  return <p>{user.name}</p>;          /* ← 本当に必要なのはここだけ */
}

Context を導入すると、中継 Props をすべて削除し、下記のようにシンプル化できます。

const UserContext = createContext(null);
function App() {
  const user = { id: 1, name: 'Taro' };
  return (
    <UserContext.Provider value={user}>
      <Page />
    </UserContext.Provider>
  );
}
function UserProfile() {
  const user = useContext(UserContext);
  return <p>{user.name}</p>;
}

Context の作成と Provider 実装

createContext の引数

const ThemeContext = React.createContext('light'); // デフォルト値

1 引数めは フォールバック値。Provider がツリーになかった場合に限り返されるため、開発中の警告用や Storybook のダミー値として活用できます。

Provider に渡す value

<ThemeContext.Provider value="dark">
  <Toolbar />
</ThemeContext.Provider>

ポイント

  • Provider が再レンダーされると value の参照比較で下位ツリー全部が再レンダー
  • value がオブジェクトなら useMemo で安定化必須
const memoValue = useMemo(() => ({ theme, toggle }), [theme]);

useContext の基本挙動

const value = useContext(ThemeContext);
  • 最も近い Provider の value を返す
  • Provider が見つからない場合は createContext 時のデフォルト値
  • 複数回呼んでも React がキャッシュするためコストは低い

注意: useContext は必ず React のレンダーフェーズ内で呼ぶ。コールバックや条件分岐で早期 return する位置に入れるとルール違反。

スコープ設計:いつ Provider を分割すべきか

ケース1 Provider複数 Provider
テーマ、ロケールなど不変に近い設定
頻繁に変わる値(カーソル座標等)
複数ドメインデータ(Auth と Cart)

複数の値が同時に更新されるわけではない場合、それぞれ独立した Provider に分けることで再レンダー範囲を局所化できます。

サンプル:Todo アプリでの Context 分離

1. アプリ共通設定

const SettingsContext = createContext({
  showCompleted: true,
  toggleShow: () => {}
});

2. ログインユーザー

const AuthContext = createContext({
  user: null,
  login: () => {},
  logout: () => {}
});

3. Todo リスト

todo はサイズが大きく頻繁に変わるため、Context より useReducer + Context の複合パターンが適切です(第二部で詳細解説)。

パフォーマンス最適化と再レンダー制御

Context API は便利ですが、Provider の value が変わるたびに配下すべての useContext 呼び出しが再レンダー されます。大規模ツリーでは深刻なパフォーマンス低下につながるため、次の 4 つの対策を組み合わせましょう。

1. useMemo で value オブジェクトを安定化

const SettingsProvider = ({ children }) => {
  const [showCompleted, setShowCompleted] = useState(true);
  const ctx = useMemo(
    () => ({ showCompleted, toggle: () => setShowCompleted(v => !v) }),
    [showCompleted]    // プリミティブのみ依存
  );
  return <SettingsContext.Provider value={ctx}>{children}</SettingsContext.Provider>;
};

オブジェクト内の参照が変わらなければ再レンダーは下位に波及しません。

2. Context を分割する

更新頻度 が異なる値を 1 つの Provider に詰め込むと、滅多に変わらない設定が毎回レンダーのトリガーになります。

  • AuthContextThemeContext を個別 Provider にする
  • パフォーマンスクリティカルな配列や Map は専用 Context へ切り出す

3. Context Selector パターン

ライブラリ use-context-selector を使うと、特定プロパティだけを購読 でき、不要な再レンダーを劇的に減らせます。

import { createContext, useContextSelector } from 'use-context-selector';

const CountContext = createContext({ count: 0, inc: () => {} });

function CounterLabel() {
  const count = useContextSelector(CountContext, v => v.count);
  return <span>{count}</span>;
}

Provider の値がオブジェクト再生成されても count が変わらない限り CounterLabel は再レンダーされません。

4. memo + useContext は要注意

React.memo でラップしても、Context 更新は強制的に渡ってくるため 再レンダー抑止はできません。必要に応じ「コンテナ–プレゼンテーション分割」を行い、Context を読む部分をコンテナ、表示だけ行う部分を memo 化すると効果的です。

useReducer と Context を組み合わせたグローバルストア

小規模アプリなら Redux などを導入せずに、useReducer + Context で十分なグローバルストアを構築できます。

const TodoContext = createContext(null);

function todoReducer(state, action) {
  switch (action.type) {
    case 'add':
      return [...state, action.payload];
    case 'toggle':
      return state.map(t =>
        t.id === action.id ? { ...t, done: !t.done } : t
      );
    default:
      return state;
  }
}

export function TodoProvider({ children }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const ctx = useMemo(() => ({ todos, dispatch }), [todos]);
  return <TodoContext.Provider value={ctx}>{children}</TodoContext.Provider>;
}

呼び出し側は dispatch({ type: 'add', payload }) で状態を更新し、todos を購読できます。Redux に比べてボイラープレートが少ないのが利点ですが、ミドルウェア機構開発ツール が欲しくなった時点で Redux Toolkit へ移行する判断基準となります。

型安全カスタムフックの作成(TypeScript)

export const SettingsContext =
  createContext<SettingsCtx | undefined>(undefined);

export function useSettings(): SettingsCtx {
  const ctx = useContext(SettingsContext);
  if (!ctx) {
    throw new Error('useSettings must be inside <SettingsProvider>');
  }
  return ctx;
}
  • undefined をコンテキスト型に含め、Provider 外使用をコンパイル時に検出
  • 呼び出し側は const { theme } = useSettings(); とシンプル

React 18 コンカレント特有の落とし穴

並列レンダーでは 「旧ツリー」と「新ツリー」が同時に存在 します。Context の値もレンダー単位でスナップショットされるため、更新が重なった場合は “数フレームだけ古いテーマで描画” のような一貫性揺らぎが起こり得ます。
対策:

  1. Transition 内で Provider を更新し、旧ツリーが素早く置き換わるようにする
  2. 非同期値(サーバフェッチ結果など)useSyncExternalStore で同期を取る

Suspense for Data Fetching と Context

React 18 の サスペンス対応データフェッチライブラリ(TanStack Query, SWR v2 等)は、Context でキャッシュを共有する設計が主流です。

<QueryClientProvider client={client}>  // Provider
  <App />
</QueryClientProvider>

内部的に Provider の値が変わらないよう慎重に実装されているため、大量のデータを扱っても再レンダーが抑えられます。

テストと Storybook パターン

単体テスト

  • Provider をラップしたテストユーティリティを作成
  • renderHook でカスタムフックのみをテスト
const wrapper = ({ children }) => <AuthProvider>{children}</AuthProvider>;
const { result } = renderHook(() => useAuth(), { wrapper });

Storybook

  • decorators で Provider を追加し、Context に基づく UI を再現
  • 複数バリエーションを Template で切り替え、locale や theme を擬似表示

よくあるエラーと解決策

エラー原因対処
Cannot read property 'xyz' of nullProvider がツリーに含まれていないカスタムフックでundefinedチェック
遅延評価したい値が毎回変わるProvider の value を毎レンダー生成useMemo で固定 or 値を分割Providerへ
子が不必要に再レンダーvalue がオブジェクトリテラルuseMemo + useCallback で参照安定化

まとめ

Context の基礎とスコープ設計、パフォーマンス最適化と useReducer 連携・型安全化・並列レンダーの留意点まで解説しました。ここまでを理解すれば、中小規模の React アプリなら Redux 等を導入せずとも十分スケーラブルな状態共有が可能です。

]]>
https://techgrowup.net/react-usecontext/feed/ 0
useMemo徹底ガイド──Reactアプリの再計算コストを劇的に減らす最適化テクニック https://techgrowup.net/react-usememo/ https://techgrowup.net/react-usememo/?noamp=mobile#respond Wed, 30 Apr 2025 23:00:00 +0000 https://techgrowup.net/?p=2838 useMemoとは何か

useMemo は指定した計算式をメモ化(キャッシュ)し、依存配列が変わらない限り再実行をスキップするReactフックです。たとえば数千件のリストを高コストなフィルタリングやソートで描画する場合、入力が変わらなければ同じ結果を再計算するのはムダです。useMemo を使えば計算を一度だけ行い、次回レンダーではキャッシュ済みの値を返すため描画が高速化します。

import { useMemo } from 'react';

function ExpensiveList({ items, query }) {
  const filtered = useMemo(() => {
    return items.filter(item => item.name.includes(query));
  }, [items, query]); // itemsかqueryが変わったときだけ再計算

  return filtered.map(item => <li key={item.id}>{item.name}</li>);
}

基本シグネチャ

const memoizedValue = useMemo(() => compute(expensive), [deps]);
  • 第1引数:メモ化したい関数(副作用を含まない純粋関数が望ましい)。
  • 第2引数:依存配列。ここに列挙した値が変わった場合のみ計算し直す。

依存配列を空配列 [] にするとマウント時に一回だけ実行。undefined を渡すと毎レンダー実行=メモ化無効なので注意。

useMemoが効く典型シナリオ

1. データ変換・重い計算

  • 数学的シミュレーションの結果
  • JSONデータのディープコピーや正規化
  • Markdown → HTML 変換など CPUコストが高い処理

2. フィルタ・ソート・検索

リストの要素数が数百〜数千を超える場合、Array.prototype.filtersort を毎レンダー走らせるとフレーム落ちの原因になります。

3. JSX生成コスト削減

巨大リストを map で JSX に展開するとき、その戻り値をメモ化すると描画コストを下げられます(React 18の並列レンダーでも効果的)。

useCallbackとの違い

観点useMemouseCallback
目的をメモ化関数参照をメモ化
戻り値計算結果そのものメモ化済みの関数
主要用途高コストの計算結果を保持子コンポーネントへ渡すハンドラの参照固定

内部実装では useCallback(fn, deps)useMemo(() => fn, deps) と同等です。しかし意味づけが異なるため、値なら useMemo、関数なら useCallback を使うと読みやすくなります。

実践シナリオ別コード例

例1:検索フィルタリング

function SearchableTable({ rows }) {
  const [keyword, setKeyword] = useState('');
  const visibleRows = useMemo(() => {
    const lower = keyword.toLowerCase();
    return rows.filter(r => r.name.toLowerCase().includes(lower));
  }, [rows, keyword]);
  return (
    <>
      <input value={keyword} onChange={e => setKeyword(e.target.value)} />
      <ul>{visibleRows.map(r => <li key={r.id}>{r.name}</li>)}</ul>
    </>
  );
}
  • rows が巨大でも keyword が変わらなければ再計算なし
  • 入力ごとにフィルタされても実用レベルのパフォーマンス

例2:高コスト画像処理

function Preview({ file }) {
  const thumbnail = useMemo(() => {
    return file ? createThumbnail(file) : null; // Canvas処理など重い関数
  }, [file]);
  return thumbnail ? <img src={thumbnail} /> : null;
}

例3:ソート済みキャッシュ

const sorted = useMemo(
  () => [...items].sort((a, b) => a.date - b.date),
  [items]
);

イミュータブルデータならシャローコピーしてソートし、items が同一参照の間は再計算されません。

依存配列設計のコツ

  1. リストやオブジェクトは参照が変わる条件を把握
    • Redux/Context でステートが再生成されると参照が毎回変わる ⇒ 先にuseSelector(shallowEqual)などで固定化
  2. 不要な依存を追加しない
    • 計算結果に影響しない値を含めると再実行が増える
  3. 入れ忘れはバグの温床
    • ESLintのreact-hooks/exhaustive-depsルールを有効化し、自動検出する
// NG: setState関数は安定参照のため依存不要
useMemo(() => compute(state, setState), [state, setState]); 
// OK
useMemo(() => compute(state), [state]);

パフォーマンス測定と検証

Profiler API

  1. React DevTools を開き「Profiler」タブを選択
  2. 録画して操作 ⇒ 再計算が走る場所を特定
  3. useMemo導入後にレンダー時間が短縮されているか確認

console.time計測

const result = useMemo(() => {
  console.time('expensive');
  const v = heavy();
  console.timeEnd('expensive');
  return v;
}, [deps]);

メモ化の前後で時間を比較し、効果を数字で把握します。

よくある落とし穴

症状原因対策
メモ化しているのに速くならない計算自体が軽い/依存が毎回変わるメモ化を外す or 依存を安定化
stale data(古い値を参照)依存配列に必要な変数がないESLintルールで検出+修正
メモリリーク大きな配列・Mapをキャッシュして解放しない必要に応じて useMemo を解除

useMemoを使わない方がいい場合

  • 計算コスト < メモ化管理コスト
  • UIがほぼ静的:再レンダー回数が少ない
  • 依存が頻繁に変わる:キャッシュがほぼ活きない

最適化は「必要になったら測定して導入」が鉄則です。

useMemo と Suspense / Concurrent Features

React 18 の並列レンダーでは、useMemoのキャッシュが中断と再開をまたいで保持されるため、長い計算でもUIスレッドをブロックしにくくなっています。ただしメモ化対象関数は同期的に実行されるため、CPU負荷の高い処理はWeb WorkerやuseTransitionと組み合わせるとさらに快適です。

まとめ

useMemo

  1. 高コスト計算の結果をキャッシュ
  2. 依存配列が変わるまで再利用
  3. 不要な再レンダーを抑制

というシンプルながら強力な最適化手段です。ただし「とりあえず入れる」は逆効果。まずはプロファイリングでボトルネックを特定し、効果が見込める箇所にのみ導入するのがベストプラクティスです。正しい依存配列と計算粒度を意識し、快適なReact UIを届けましょう。

]]>
https://techgrowup.net/react-usememo/feed/ 0
useReducer完全入門──複雑な状態管理をスマートに解決するReactフックの使い方 https://techgrowup.net/react-usereducer/ https://techgrowup.net/react-usereducer/?noamp=mobile#respond Wed, 30 Apr 2025 22:00:00 +0000 https://techgrowup.net/?p=2835 なぜuseReducerが必要か

useStateは直感的で便利ですが、

  • 複数の状態が相互に影響する
  • 更新ロジックが長い if/else で肥大化する
  • 次の状態が前の状態に依存する更新が多い

といった場面では読みづらくなります。こうしたケースでuseReducerを使うと、状態更新ロジックを1カ所に集約でき、コンポーネントがスッキリします。

基本シグネチャ

const [state, dispatch] = useReducer(reducer, initialState, init?)
パラメータ意味
reducer(state, action) ⇒ newState更新ロジックを持つ純粋関数
initialState任意初期値
init(optional) (arg) ⇒ initializedState遅延初期化用

戻り値

  • state:現在の状態
  • dispatch:アクションを送る関数

まずはカウンターで理解

import { useReducer } from 'react';

function reducer(state, action) {
  switch (action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: throw new Error();
  }
}

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    <>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}></button>
      <button onClick={() => dispatch({ type: 'decrement' })}></button>
    </>
  );
}

ポイント解説

  • reducer純粋関数。副作用を入れない
  • dispatch へは オブジェクトを渡す(type必須)
  • UI 側は state を読むだけ=ロジックが分離され可読性↑

複数フィールドのフォーム例

useStateでそれぞれ管理すると更新がバラけますが、useReducerで一元化可能。

function formReducer(state, action) {
  switch (action.type) {
    case 'change':
      return { ...state, [action.field]: action.value };
    case 'reset':
      return action.initial;
    default:
      return state;
  }
}

const initialForm = { name: '', email: '', password: '' };

function SignupForm() {
  const [form, dispatch] = useReducer(formReducer, initialForm);

  const handleChange = e =>
    dispatch({ type: 'change', field: e.target.name, value: e.target.value });

  return (
    <form>
      {Object.entries(form).map(([key, val]) => (
        <input key={key} name={key} value={val} onChange={handleChange} />
      ))}
      <button onClick={() => dispatch({ type: 'reset', initial: initialForm })}>
        クリア
      </button>
    </form>
  );
}

深掘り

  • ...state で既存をコピー=不変性を守る
  • resetアクションでワンアクション全項目初期化
  • 大量フィールドでも reducer が変わらないので拡張に強い

遅延初期化でパフォーマンス改善

重い計算で初期値を作る場合、第三引数 init を使うとレンダー時に毎回呼ばれません。

function init(countFromLocal) {
  return { count: countFromLocal };
}
const [state, dispatch] = useReducer(reducer, initialArg, init);

useState vs useReducer 使い分け指針

シチュエーション推奨フック
1〜2個の単純な値useState
更新ロジックが直感的useState
多数のフィールド・複雑な分岐useReducer
次の状態が前の状態に強く依存useReducer
Redux 風のアーキテクチャを局所的に実現useReducer

コンポーネント分割との相性

Reducer を別ファイルに切り出し、複数コンポーネントから共通利用するとReduxライクな構成が得られます。

// todoReducer.js
export function todoReducer(state, action) { /* 省略 */ }

// TodoList.jsx
import { todoReducer } from './todoReducer';
const [todos, dispatch] = useReducer(todoReducer, []);

まとめ

useReducer状態更新ロジックを一元管理し、可読性と拡張性を両立させる強力なフックです。

  1. 純粋関数 reducer を定義
  2. dispatch で明示的にアクションを送る
  3. ステートが複雑化したら useReducer へ移行

ロジックが整理されることでバグ発生率が減り、大規模アプリでもスケールしやすいコードベースを実現できます。

]]>
https://techgrowup.net/react-usereducer/feed/ 0
useRef徹底ガイド──DOMアクセスからミューテーション管理まで https://techgrowup.net/react-useref/ https://techgrowup.net/react-useref/?noamp=mobile#respond Wed, 30 Apr 2025 20:00:00 +0000 https://techgrowup.net/?p=2832 useRefとは?

ReactのuseRefフックは、レンダー間で変化しても再レンダーを引き起こさない「箱」を提供します。返されるオブジェクトは{ current: 値 }という形で、コンポーネントのライフサイクル全体を通じて同じ参照を保持します。主な用途は次の二つです。

  1. DOM要素への参照:フォーム入力フォーカスやスクロール位置制御
  2. ミューテーション値の保持:レンダー外で更新されるカウンタや外部ライブラリのインスタンス
import { useRef, useEffect } from 'react';

function TextInputFocus() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} placeholder="自動でフォーカス" />;
}

仕組みと特徴

  • useRef(initialValue) はレンダーのたびに 同じオブジェクト を返す
  • ref.current の変更は React の再レンダーをトリガーしない
  • オブジェクト参照自体は固定だが current プロパティは自由に書き換え可能

この振る舞いにより、状態のように保持しつつUIに影響を与えないデータ を格納できます。

DOMリファレンスの実践例

フォームのフォーカス管理

複数ステップのフォームで次の入力要素へフォーカスを移すとき、useRef が活躍します。

function MultiStepForm() {
  const step1Ref = useRef(null);
  const step2Ref = useRef(null);
  const [step, setStep] = useState(1);

  useEffect(() => {
    if (step === 1) step1Ref.current.focus();
    if (step === 2) step2Ref.current.focus();
  }, [step]);

  return (
    <>
      {step === 1 && <input ref={step1Ref} />}
      {step === 2 && <input ref={step2Ref} />}
      <button onClick={() => setStep(s => s + 1)}>次へ</button>
    </>
  );
}

スクロール制御

チャットアプリで新しいメッセージ追加時に最下部までスクロールする例。

function ChatWindow({ messages }) {
  const bottomRef = useRef(null);

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ behavior: 'smooth' });
  }, [messages]);

  return (
    <div className="chat-area">
      {messages.map(m => <p key={m.id}>{m.text}</p>)}
      <div ref={bottomRef} />
    </div>
  );
}

ミューテーション値の保持

レンダー回数を数える

状態にすると無限ループするような値は useRef で保持。

function RenderCount() {
  const countRef = useRef(0);
  useEffect(() => { countRef.current += 1 });
  return <p>レンダー回数: {countRef.current}</p>;
}

setIntervalのIDを保持

function Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);

  const start = () => {
    if (intervalRef.current === null) {
      intervalRef.current = setInterval(() => setCount(c => c + 1), 1000);
    }
  };

  const stop = () => {
    clearInterval(intervalRef.current);
    intervalRef.current = null;
  };

  useEffect(() => stop, []); // アンマウント時に停止

  return <>
    <p>{count} 秒経過</p>
    <button onClick={start}>開始</button>
    <button onClick={stop}>停止</button>
  </>;
}

forwardRefと組み合わせる

親コンポーネントから子のDOMへ直接アクセスする場合は forwardRef が必要です。

const FancyInput = React.forwardRef((props, ref) => {
  return <input className="fancy" ref={ref} {...props} />;
});

function Parent() {
  const inputRef = useRef(null);
  return <>
    <FancyInput ref={inputRef} />
    <button onClick={() => inputRef.current.focus()}>フォーカス</button>
  </>;
}

useImperativeHandleで公開APIを制御

子コンポーネント側で ref 経由の公開メソッドをカスタマイズできます。

const Canvas = React.forwardRef((props, ref) => {
  const canvasRef = useRef();
  useImperativeHandle(ref, () => ({
    clear: () => canvasRef.current.getContext('2d').clearRect(0,0,300,150)
  }));
  return <canvas ref={canvasRef} width={300} height={150} />;
});

function DrawingApp() {
  const canvasRef = useRef();
  return <>
    <Canvas ref={canvasRef} />
    <button onClick={() => canvasRef.current.clear()}>クリア</button>
  </>;
}

コールバックrefとの比較

種類設定方法特徴
string refref="myRef"レガシー、廃止予定
callback refref={node => { ... }}動的に複数refや条件分岐を管理
useRef + ref属性const r=useRef(); … ref={r}一般的。オブジェクトに一度だけセット

パフォーマンスに与える影響

useRef はレンダーをトリガーしないため、頻繁に変化する値を保持してもコストは低い。ただし current の変更にUIを反映したい場合は、別途 useState と組み合わせる必要がある。

よくある落とし穴

  1. refがnullのままアクセス
    • 初回レンダー直後はまだDOMがセットされておらずnull。useEffect の中で読む。
  2. currentを書き換えても画面が更新されない
    • useRefは状態管理ではない。UIに反映が必要なら useState を使う。
  3. 依存配列にref.currentを入れてしまう
    • ref.current の値が変わっても依存配列は検知しない。監視には別状態が必要。

総括

useRefDOMアクセスミューテーション値の永続化 を実現する万能ポケットです。再レンダーコストを抑えつつ、状態とは異なる形で情報を保持できるため、フォーム・アニメーション・外部ライブラリ統合など多様な場面で活用できます。ポイントは「UI描画に関与しない値」を見極め、状態との役割分担を明確にすることです。

]]>
https://techgrowup.net/react-useref/feed/ 0
ReactのuseCallback徹底ガイド──依存配列・再レンダリング制御・パフォーマンス最適化の基本と応用 https://techgrowup.net/react-usecallback/ https://techgrowup.net/react-usecallback/?noamp=mobile#respond Sun, 27 Apr 2025 23:00:00 +0000 https://techgrowup.net/?p=2827 はじめに

Reactの関数コンポーネントで副作用や再レンダリング制御を扱う際、useCallback フックはしばしば「面倒くさい」「よくわからない」と敬遠されがちです。しかし、大規模アプリのパフォーマンスチューニングや、子コンポーネントへのコールバック関数渡しを正しく行うためには不可欠な存在です。本記事では基礎から依存配列の書き方、内部動作、注意点、具体的な活用シーンまでを解説します。

useCallbackとは?

useCallback は「関数をメモ化(キャッシュ)する」ための React フックです。コンポーネントが再レンダーされるたびに新しい関数インスタンスを生成すると、子コンポーネントに渡したコールバックが”変化”とみなされ、不要な再レンダーにつながります。これを防ぐのが useCallback です。

const memoizedHandler = useCallback(() => {
  console.log('クリックされました');
}, [依存値1, 依存値2]);
  • 第1引数:メモ化したい関数
  • 第2引数:依存配列。ここに入れた値が変わると新しく関数を作り直す

基本シグネチャと戻り値

function useCallback<T extends (...args: any[]) => any>(
  callback: T,
  deps: DependencyList
): T;
  • T:任意の関数型
  • DependencyListany[] 型の配列
  • 戻り値:渡した callback と同じ型の“メモ化された関数”

内部では React が Hook ごとに前回の callback 参照を保持し、deps が完全一致する限り同じ関数を返します。

依存配列の正しい書き方

依存配列を適切に指定しないと、次のような問題が起きます。

誤り症状
依存配列を空にする ([])最新の外部変数を参照できない (stale closure)
依存を省略 (undefined)毎回新しい関数が作られ、効果なし
必要な依存を含めない古い値を使い続ける

例:カウントを依存に含めないと古い値を参照する

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

  const handler = useCallback(() => {
    console.log(count); // 依存にcountを含めないと常に0が表示
  }, []); 

  return <button onClick={handler}>ログ出力</button>;
}

上記では count が変わっても handler 内の count は初回のまま。依存配列には常に最新のcountを正しく指定しましょう。

useCallbackとuseMemoの違い

  • useCallback:関数をメモ化する
  • useMemo:関数の戻り値(任意の値)をメモ化する
const memoizedValue = useMemo(() => computeExpensive(a, b), [a, b]);
const memoizedHandler = useCallback(() => doSomething(a, b), [a, b]);

useCallback(fn, deps)useMemo(() => fn, deps) のエイリアスと考えることもできますが、可読性の観点から関数キャッシュには useCallback を使うのが一般的です。

  1. コンポーネント初回レンダー
  2. useCallback が新しい関数を生成し返却
  3. 次回レンダー
    • deps が同じ → 前回の関数を再利用
    • deps が変わった → 新しい関数を生成
  4. 子コンポーネントへ渡す際、関数参照の変化を検出してレンダー制御できる

この仕組みを利用し、React.memo と組み合わせることで子コンポーネントの再レンダーを抑制できます。

子コンポーネントの最適化例

const Child = React.memo(({ onAction }) => {
  console.log('Child render');
  return <button onClick={onAction}>アクション</button>;
});

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

  const handleAction = useCallback(() => {
    console.log('Action!');
  }, []); // 依存なし → 再レンダー時も同じ参照

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>増やす</button>
      <Child onAction={handleAction} />
    </>
  );
}
  • Parent が再レンダーされても handleAction は同じ参照
  • Child は Props が変わらないとレンダーされないため高速

使わないほうがいいケース

  • 軽量コンポーネント/関数
    関数の生成コストよりReactのレンダーコストのほうが低い場合
  • 短命な値
    毎回異なる依存配列を生成すると逆効果
  • 過度のメモ化
    コードが複雑になり可読性や保守性が低下

判断基準
パフォーマンス問題が「本当に関数参照の変化」に起因しているかプロファイラで確認しましょう。

複数フックとの連携パターン

useCallback + useEffect

エフェクト内で関数を参照する場合、依存配列に含めないと stale closure が起きます。

function Search({ query }) {
  const [results, setResults] = useState([]);
  const fetchResults = useCallback(async () => {
    const res = await fetch(`/api?q=${query}`);
    setResults(await res.json());
  }, [query]);

  useEffect(() => {
    fetchResults();
  }, [fetchResults]); // fetchResults を正しく依存に含める

  return /* 結果表示 */;
}

useCallback + useMemo

関数による計算結果をさらにメモ化したい場合

const expensiveCompute = useCallback((x) => heavyCalc(x), []);
const memoizedResult = useMemo(() => expensiveCompute(input), [input, expensiveCompute]);

useCallback でメモ化された関数をテストする際は、renderHookact を使うと楽です。

コールバック関数のテスト

useCallback でメモ化された関数をテストする際は、renderHookact を使うと楽です。

import { renderHook, act } from '@testing-library/react-hooks';

test('fetchResults が更新時に変化する', async () => {
  const { result, rerender } = renderHook(
    ({ query }) => useCallback(/*…*/, [query]),
    { initialProps: { query: 'a' } }
  );
  const first = result.current;
  rerender({ query: 'b' });
  expect(result.current).not.toBe(first);
});

パフォーマンス計測とベンチマーク

  • Profiler API で実際のレンダー回数を計測
  • React DevTools のProfilerタブでコールバック関数生成の影響を見る
  • useCallback 無し / 有りでどれだけ再レンダーが減るかを数値で比較

よくある誤解とQ&A

  1. 「depsが空なら一度だけ実行される」
    useCallback では「関数参照が一度だけ生成される」。 useEffect([], …) とは別。
  2. 「関数を渡すだけで最適化される」
    → 子コンポーネントも React.memo などで参照比較しなければ効果なし。
  3. 「常に使えば良い」
    → 過度のメモ化は逆効果。必要な箇所だけに絞りましょう。

まとめ

useCallback は関数インスタンスの再生成を防ぎ、再レンダーを制御する強力なフックです。依存配列の正確な管理、React.memo との併用、過度なメモ化の回避などポイントを押さえれば、アプリ全体のパフォーマンスが向上します。まずはパフォーマンスプロファイリングを行い、本当に必要な箇所だけに導入してみてください。

]]>
https://techgrowup.net/react-usecallback/feed/ 0
React.Component徹底ガイド──クラスコンポーネントの基本からライフサイクル・State管理・最適化まで https://techgrowup.net/react-class-component/ https://techgrowup.net/react-class-component/?noamp=mobile#respond Sun, 27 Apr 2025 20:00:00 +0000 https://techgrowup.net/?p=2824 はじめに

Reactのコンポーネントは関数コンポーネントが主流になっていますが、クラスベースのReact.Componentも依然として理解しておく価値があります。特に既存の大規模プロジェクトやError Boundary(エラーバウンダリ)実装にはクラスコンポーネントが欠かせません。提供する機能を1つずつ丁寧に見ていきましょう。

React.Componentとは

React.Component は、ユーザー定義のクラスコンポーネントが継承する基底クラスです。内部では以下を提供します。

  • props:親から渡される読み取り専用データ
  • state:コンポーネント内部で変化する値
  • render():UIの描画を司る必須メソッド
  • ライフサイクルメソッド:マウント・更新・アンマウント時のフック
  • setState()state更新と再レンダーのトリガー
  • エラーバウンダリstatic getDerivedStateFromError / componentDidCatch
import React from 'react';

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };
  }
  render() {
    return <div>カウント: {this.state.count}</div>;
  }
}

propsの扱い

this.props には親から渡された全ての値が含まれています。コンポーネント内で変更してはいけません。

class Greeting extends React.Component {
  render() {
    return <h1>こんにちは、{this.props.name}さん!</h1>;
  }
}

// 呼び出し側
<Greeting name="太郎" />
  • 型注釈:PropTypes で型・必須チェックを行う例も多い
  • デフォルトPropsMyComponent.defaultProps = { count: 10 };

stateとsetState

state はコンポーネントの内部情報を保持し、setState で更新します。setState非同期バッチ処理されるため注意が必要です。

class Counter extends React.Component {
  state = { count: 0 };

  handleClick = () => {
    this.setState({ count: this.state.count + 1 });
    // 複数回連続呼び出すと反映タイミングがずれる可能性あり
  }

  render() {
    return <button onClick={this.handleClick}>{this.state.count}</button>;
  }
}
  • 関数型更新:前回のstateを確実に利用したい場合
this.setState(prev => ({ count: prev.count + 1 }));
  • マージ:オブジェクトをマージする形で更新される

ライフサイクルメソッド一覧

クラスコンポーネントは以下のメソッドをオーバーライドできます。

フェーズメソッド説明
マウント前static getDerivedStateFromProps(props, state)Propsからstateを生成(副作用禁止)
マウント後componentDidMount()API呼び出しやDOM操作を実行
更新判定shouldComponentUpdate(nextProps, nextState)レンダーをスキップする判断
更新前getSnapshotBeforeUpdate(prevProps, prevState)更新前のDOM情報を取得(返却値は次のcomponentDidUpdateに渡る)
更新後componentDidUpdate(prevProps, prevState, snapshot)更新後に副作用を実行
アンマウント前componentWillUnmount()タイマー解除、購読解除などのクリーンアップ
エラー処理static getDerivedStateFromError(error)エラー発生時にstateを更新
componentDidCatch(error, info)UIエラーのログ送信など

例:データフェッチ

class DataFetcher extends React.Component {
  state = { data: null };

  componentDidMount() {
    fetch(this.props.url)
      .then(r => r.json())
      .then(json => this.setState({ data: json }));
  }

  render() {
    return this.state.data
      ? <div>{this.state.data.title}</div>
      : <p>読み込み中…</p>;
  }
}

Error Boundary(エラーバウンダリ)

クラスコンポーネントのみ実装可能な仕組みで、子コンポーネントでのレンダーエラーをキャッチできます。

class ErrorBoundary extends React.Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  componentDidCatch(error, info) {
    logErrorToService(error, info);
  }

  render() {
    if (this.state.hasError) {
      return <h2>エラーが発生しました</h2>;
    }
    return this.props.children;
  }
}
  • 再起動:Error Boundaryをリセットするロジックを外部から制御可能
  • 注意:副作用フックの代替ではない

コンテキストとの連携

static contextType = MyContext をクラスに指定すると this.context で値を参照できます。

MyComponent.contextType = ThemeContext;
class MyComponent extends React.Component {
  render() {
    return <div style={{ color: this.context.primary }}></div>;
  }
}

パフォーマンス最適化

shouldComponentUpdate

再レンダーを制御し、不要なrender()呼び出しを抑えます。

class PureCounter extends React.Component {
  shouldComponentUpdate(nextProps) {
    return nextProps.value !== this.props.value;
  }
  render() { /* … */ }
}

PureComponent

React.PureComponent を継承すると自動的に shallow 比較で shouldComponentUpdate を実装します。

メソッドのバインディング

this の参照を安定させるため、コンストラクタでバインディングするか、クラスフィールド構文を使います。

// コンストラクタ内で
constructor(props) {
  super(props);
  this.handleClick = this.handleClick.bind(this);
}

// クラスフィールド
handleClick = () => { /* this が正しい */ }

デフォルトPropsとdisplayName

  • defaultProps
MyComponent.defaultProps = { text: 'デフォルトテキスト' };
  • displayName
    • デバッグ時の表示名を変更できます。
MyComponent.displayName = 'カスタム名';

まとめ

React.Component を理解すると、既存のクラスコンポーネントやError Boundaryが読み書きできるようになります。ライフサイクルメソッド、state / props、パフォーマンス最適化パターンなどを活用し、堅牢で読みやすいUIコンポーネントを実装しましょう。

]]>
https://techgrowup.net/react-class-component/feed/ 0
Reactエフェクトのライフサイクル完全ガイド──useEffectがいつ動く?依存関係・クリーンアップ・タイミングを徹底解説 https://techgrowup.net/react-lifecycle-effects/ https://techgrowup.net/react-lifecycle-effects/?noamp=mobile#respond Sun, 27 Apr 2025 19:00:00 +0000 https://techgrowup.net/?p=2821 はじめに

Reactコンポーネントで副作用(データ取得・購読・手動DOM操作等)を扱う際の要がuseEffectフックです。しかし「いつ呼ばれるのか」「どこでクリーンアップすべきか」「依存配列の指定をどう書くべきか」を誤解しやすく、バグや無限ループ、不要な再実行に悩む開発者は少なくありません。本稿ではuseEffectuseLayoutEffectのライフサイクルを丁寧に紐解き、実践的なコード例とともに解説します。

Reactエフェクトの基本概念

エフェクト(副作用)とは、ReactのUI更新の外側で起きる処理全般を指します。具体例としては:

  • 外部APIからデータを取得する
  • WebSocketやイベントリスナーを登録・解除する
  • 手動でDOMを操作する(サードパーティライブラリ連携)

useEffectフックは、レンダー後に“副作用”を実行し、必要ならクリーンアップ(解除)も指定できる仕組みです。

import { useEffect } from 'react';

function Example({ url }) {
  useEffect(() => {
    fetch(url).then(r => r.json()).then(data => {
      console.log(data);
    });
  }, [url]);
  return <div>データを取得中…</div>;
}
  • 第1引数:エフェクト用コールバック
  • 第2引数:依存配列。中身が変わったときだけ再実行

ライフサイクルフロー全体像

  1. マウント後:最初のレンダーが完了した直後に実行
  2. 更新後:依存配列の値が前回と異なるとレンダー後に再実行
  3. アンマウント前:前回のエフェクトで返したクリーンアップ関数を実行
  4. 依存配列なし:マウント後のみ実行、更新時には再実行されない
  5. 依存配列を省略:レンダーごとに実行し、旧エフェクトは都度クリーンアップ

以下、各ステップを深掘りします。

マウント後の初回実行

useEffect(fn, deps)マウント直後 にまず実行されます。DOMが構築された後なので、document.getElementById など直接DOM操作しても正しく参照できるタイミングです。

function InitLogger() {
  useEffect(() => {
    console.log('コンポーネントが画面に追加されました');
  }, []); // 依存配列空でマウント後のみ
  return <div>初期化ログを出力</div>;
}
  • 依存配列を空にすると、初回レンダー後だけに限定
  • 一度きりのサブスクリプションやログに最適

依存配列による再実行制御

依存配列 ([a, b, …]) に指定した値が前回と異なる場合、更新後に再実行されます。

function Fetcher({ userId }) {
  const [profile, setProfile] = useState(null);
  useEffect(() => {
    let active = true;
    fetch(`/api/users/${userId}`)
      .then(r => r.json())
      .then(data => {
        if (active) setProfile(data);
      });
    return () => { active = false }; // クリーンアップ
  }, [userId]); 
  // userIdが変わるたびに実行/前回のフェッチはキャンセル
  return profile ? <UserCard data={profile} /> : <p>読み込み中…</p>;
}
  • 古いフェッチ結果を書き込まないためのフラグ制御
  • 依存の追加漏れが無限ループや stale closure を招く

クリーンアップ関数の重要性

エフェクト内で何らかの購読(Subscription)やタイマーを登録した場合、コンポーネントの破棄前に必ず解除する必要があります。useEffectコールバックが返す関数がクリーンアップに該当します。

function Clock() {
  const [time, setTime] = useState(Date.now());
  useEffect(() => {
    const id = setInterval(() => setTime(Date.now()), 1000);
    return () => clearInterval(id); // アンマウント時に解除
  }, []);
  return <div>現在時刻:{new Date(time).toLocaleTimeString()}</div>;
}
  • クリーンアップがないと、メモリリークや不要なタイマーが残り続ける
  • レンダーごとに再登録してしまう実装ミスにも注意

useLayoutEffectuseEffectの違い

  • useEffect:レンダー後に非同期的に実行される
  • useLayoutEffect:レンダー直後、ブラウザの描画前に同期的に実行される

レイアウトやサイズ計算をDOMに対して行う場合は、レイアウトズレを防ぐために useLayoutEffect を使います。それ以外は通常の useEffect を優先しましょう。

import { useLayoutEffect, useRef } from 'react';

function MeasureBox() {
  const ref = useRef();
  useLayoutEffect(() => {
    console.log(ref.current.getBoundingClientRect());
  }, []);
  return <div ref={ref}>サイズを測定</div>;
}

副次的レンダーとスケジューリング

React 18では厳格モード(Strict Mode)使用時に、開発中の副作用が2回実行される仕様があります。これは初回マウント→アンマウント→再マウントの動きを通して、クリーンアップ漏れを早期に検出するためです。本番ビルドでは1回だけ実行されますが、開発体験向上のため注意して実装しましょう。

パフォーマンス最適化パターン

  1. 依存配列の最小化
    不要な変数を入れると再実行が多発。逆に入れ忘れると stale data の原因。
  2. useCallback / useMemo
    エフェクト中で関数や計算結果を依存させる場合はメモ化して参照の安定化を図る。
  3. サブスクリプション分離
    複数の副作用を一つの useEffect にまとめず、責務ごとに分けると、不要な再実行を抑えやすい。
useEffect(() => { /* データ取得 */ }, [id]);
useEffect(() => { /* 購読登録 */ }, [socket]);

よくある落とし穴と対策

症状原因対策
無限ループ依存配列に頻繁に変わる値が含まれる必要最小限の依存に絞る
クリーンアップ漏れreturn を忘れた購読/タイマーは必ず return で解除
stale closure(古い状態を参照)クロージャ内で古い変数を直接参照依存配列に必要な値をすべて含める、またはフラグ制御
レンダリング前の計測ずれuseEffectでDOM計測useLayoutEffectに移行

実践例:データフェッチ×キャンセルパターン

import { useState, useEffect } from 'react';

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  useEffect(() => {
    const controller = new AbortController();
    fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
      .then(r => r.json())
      .then(setResults)
      .catch(err => {
        if (err.name !== 'AbortError') console.error(err);
      });
    return () => controller.abort(); // クエリ変更時・アンマウント時に中断
  }, [query]);
  return (
    <ul>
      {results.map(r => <li key={r.id}>{r.title}</li>)}
    </ul>
  );
}

まとめ

Reactの副作用ライフサイクルを正しく理解すると、UI更新と非同期処理、リソース管理がスムーズになります。useEffectuseLayoutEffectの使い分け、依存配列の設計、クリーンアップの徹底、パフォーマンス最適化パターンをマスターし、副作用バグから解放された堅牢なコンポーネントを開発しましょう。

]]>
https://techgrowup.net/react-lifecycle-effects/feed/ 0
Reactコンポーネント徹底ガイド──基本から応用パターンまでわかりやすく解説 https://techgrowup.net/react-component-basic/ https://techgrowup.net/react-component-basic/?noamp=mobile#respond Sun, 27 Apr 2025 18:00:00 +0000 https://techgrowup.net/?p=2817 はじめに

Reactを初めて学ぶ人が最初につまづきやすいのが「コンポーネント」の概念です。コンポーネントはUIを部品化し、再利用可能な塊として扱う手法。公式ドキュメント (https://react.dev/learn#components) では「コンポーネントは関数と同じ」と説明していますが、実際にコードを書くときにはPropsやState、Hooksと組み合わせるため、学習のハードルが高く感じられることもあります。本記事ではReact 18+最新情報を交え、関数コンポーネント、クラスコンポーネント、Props、State、Hooks、コンポジションや最適化パターンまで丁寧に解説します。

コンポーネントとは何か

コンポーネントは「UI部品」のようなもので、HTMLとJavaScriptを一体化して定義できます。例えば「ボタン」や「入力フォーム」のようなパーツを1つのファイルとして切り出し、 他の箇所から呼び出して使い回せるのが最大の利点です。

// Button.jsx
export function Button({ label, onClick }) {
  return <button onClick={onClick}>{label}</button>;
}

上記のように、Button コンポーネントを定義し、labelonClick をPropsで受け取ることで、使う場所ごとに見た目や挙動を変えられます。

関数コンポーネントの基礎

宣言方法

関数コンポーネントはJavaScriptの関数として定義し、必ず1つのJSX要素を返します。

function Greeting({ name }) {
  return <h1>こんにちは、{name}さん!</h1>;
}

ESLintやTypeScript環境では React.FC 型を使って型注釈を付けるケースもあります。

Propsの受け渡し

Propsはコンポーネントを呼び出す側で指定します。

<Greeting name="太郎" />
<Greeting name="花子" />

複数のPropsを使う場合はオブジェクト形式でまとめて受け取り、分割代入で取り出します。

function Profile({ avatarUrl, username, bio }) { /* … */ }

クラスコンポーネントとの違い

かつてReactはクラスコンポーネントが主流でした。2025年現在はHooksの登場で関数コンポーネントが主役ですが、既存コードやライフサイクルメソッドの学習に役立つため、基本を抑えておくと理解が深まります。

import React from 'react';

class Greeting extends React.Component {
  render() {
    return <h1>こんにちは、{this.props.name}さん!</h1>;
  }
}
  • State管理:クラスでは this.statethis.setState()
  • ライフサイクルcomponentDidMount, componentDidUpdate, componentWillUnmount

Hooksを使えば同等の処理が関数コンポーネントで実現可能です。

Stateの扱い方

useStateフック

Stateは「変化する値」を保持し、再レンダーを引き起こす仕組みです。関数コンポーネントでは useState を使って簡単にStateを定義できます。

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    <>
      <p>現在のカウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>増やす</button>
    </>
  );
}
  • 初期値useState(0)0 部分
  • 更新関数setCount を呼ぶと再レンダーが発生

Stateの注意点

  • 不変性:Stateオブジェクトを直接変更せず、必ず更新関数を使う
  • 遅延初期化:複雑な計算が必要な場合は関数を渡して一度だけ実行
const [todos, setTodos] = useState(() => loadTodosFromLocalStorage());

Props vs State

項目PropsState
定義場所親コンポーネントが渡す自身のコンポーネント内で定義
役割親からの読み取り専用の値パーツ内部で変化する値
更新親が再レンダーされる必要あり更新関数を呼ぶだけで自動的に再レンダー

Propsを変更したい場合は「親コンポーネント」が再レンダーされて新しいPropsを渡す仕組みになります。

コンポーネントの合成(Composition)

Reactではコンポジションが主流で、親子や兄弟のようにコンポーネントを入れ子にして機能を組み立てます。

function Card({ children }) {
  return <div className="card">{children}</div>;
}

function App() {
  return (
    <Card>
      <h2>タイトル</h2>
      <p>内容テキスト</p>
    </Card>
  );
}
  • children<Card>…</Card> の中身を受け取る特殊Props
  • 再利用性:見た目のフレームと中身を分離できるため、多用途に使える

ライフサイクルとuseEffect

useEffectの基本

副作用(データフェッチ、購読、手動DOM操作など)を扱う際に使うフック。

import { useEffect, useState } from 'react';

function DataFetcher({ url }) {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch(url)
      .then(r => r.json())
      .then(json => setData(json));
  }, [url]); // urlが変わったときだけ再実行
  return data ? <pre>{JSON.stringify(data)}</pre> : <p>読み込み中…</p>;
}
  • 第2引数:依存配列。空配列 [] ならマウント時のみ実行
  • クリーンアップ:関数を返すとアンマウント時に実行
useEffect(() => {
  const id = setInterval(tick, 1000);
  return () => clearInterval(id);
}, []);

クラスのライフサイクル対応

  • componentDidMountuseEffect(() => {...}, [])
  • componentDidUpdateuseEffect(() => {...}, [deps])
  • componentWillUnmountuseEffect(() => { return cleanup }, [])

パフォーマンス最適化

React.memo

関数コンポーネントをラップして、Propsが変わらない限り再レンダーを防ぐ。

const ExpensiveComponent = React.memo(function({ value }) {
  // 重い計算...
  return <span>{value}</span>;
});

useCallback / useMemo

  • useCallback:コールバック関数をメモ化
const handleClick = useCallback(() => { /* ... */ }, [deps]);
  • useMemo:計算結果をメモ化
const result = useMemo(() => expensiveCalculation(a, b), [a, b]);

Virtualized List

大量データをリスト表示する場合は react-windowreact-virtualized を使って、可視領域のみDOM生成する。

コンポーネント設計パターン

Container / Presentational

  • Container:データ取得やState管理を担当
  • Presentational:UI描画のみを担当
function UserContainer() {
  const [user, setUser] = useState(null);
  useEffect(() => fetchUser().then(setUser), []);
  return user ? <UserProfile data={user} /> : <Loading />;
}
function UserProfile({ data }) { /* UIのみ */ }

Compound Components

親コンポーネントと子要素が連携して振る舞うパターン。

function Tabs({ children }) {
  const [active, setActive] = useState(0);
  return /* contextでactiveとsetActiveを渡す */;
}
Tabs.List = function({ children }) { /* タブ見出し */ };
Tabs.Panel = function({ index, children }) { /* コンテンツ */ };

テストと型安全

テスト例(React Testing Library)

import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('クリックで増加', () => {
  const { getByText } = render(<Counter />);
  const btn = getByText(/増やす/);
  fireEvent.click(btn);
  expect(getByText(/現在のカウント: 1/)).toBeInTheDocument();
});

TypeScriptとの併用

コンポーネントに型注釈を付けると、Propsのうっかりミスをコンパイル時に検出できます。

interface ButtonProps { label: string; onClick: () => void; }
export function Button({ label, onClick }: ButtonProps) { /* ... */ }

まとめ

Reactコンポーネントは「UIを部品化して組み合わせる」ための基本単位です。関数コンポーネントとクラスコンポーネントの違い、PropsとState、Hooks、コンポジション、最適化パターンをしっかり理解すれば、堅牢で再利用性の高いUIを効率的に開発できます。公式ドキュメントを自分のプロジェクトに落とし込む際には、ここで解説した多彩なパターンをぜひ試してみてください。

]]>
https://techgrowup.net/react-component-basic/feed/ 0