<CodeLearn/>
React レッスン5

Hooks入門

Reactの強力なHookシステムを理解しよう

Hooksとは?

Hooksは、関数コンポーネントでReactの機能(状態管理、副作用処理など)を 利用するための仕組みです。React 16.8で導入され、現在のReact開発の中心的な機能です。

主要なHooksには以下があります:

  • useState — 状態管理
  • useEffect — 副作用処理(API呼び出し、タイマーなど)
  • useRef — DOM参照やミュータブルな値の保持
  • useMemo — 計算結果のメモ化
  • useCallback — 関数のメモ化
  • useContext — コンテキスト(グローバル状態)の利用

Hooksのルール(重要)

Hooksを使う際には、2つの重要なルールがあります。違反するとバグの原因になります。

// ルール1: トップレベルでのみ呼び出す
// → ループ、条件分岐、ネストされた関数の中でHooksを呼んではいけない

function MyComponent() {
  // OK: トップレベル
  const [count, setCount] = useState(0);
  const [name, setName] = useState("");

  // NG: 条件分岐の中
  // if (count > 0) {
  //   const [extra, setExtra] = useState(""); // エラー!
  // }

  // NG: ループの中
  // for (let i = 0; i < 3; i++) {
  //   const [item, setItem] = useState(""); // エラー!
  // }

  return <div>{count}</div>;
}

// ルール2: React関数コンポーネントまたはカスタムHookの中でのみ呼び出す
// → 通常のJavaScript関数の中では使えない

// NG: 普通の関数
// function helper() {
//   const [value, setValue] = useState(""); // エラー!
// }

// OK: コンポーネント
function MyComponent() {
  const [value, setValue] = useState(""); // OK
  return <p>{value}</p>;
}

// OK: カスタムHook(useで始まる関数)
function useMyHook() {
  const [value, setValue] = useState(""); // OK
  return [value, setValue];
}

useRef

useRef は、 レンダリング間で値を保持するためのHookです。DOM要素への参照にもよく使われます。useRefの変更は再レンダリングを引き起こしません

import { useRef, useState } from "react";

// DOM要素への参照
function FocusInput() {
  const inputRef = useRef(null);

  const handleClick = () => {
    // DOM要素に直接アクセス
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} placeholder="ここにフォーカス" />
      <button onClick={handleClick}>フォーカスする</button>
    </div>
  );
}

// レンダリング回数のカウント(再レンダリングなし)
function RenderCounter() {
  const [count, setCount] = useState(0);
  const renderCount = useRef(0);

  // レンダーのたびに増えるが、再レンダリングは起きない
  renderCount.current += 1;

  return (
    <div>
      <p>カウント: {count}</p>
      <p>レンダリング回数: {renderCount.current}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

// 前回の値を記憶する
function PreviousValue() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef(0);

  // 現在の値を保存(次のレンダリングで「前回の値」として使える)
  const prevCount = prevCountRef.current;
  prevCountRef.current = count;

  return (
    <div>
      <p>現在: {count}、前回: {prevCount}</p>
      <button onClick={() => setCount(c => c + 1)}>+1</button>
    </div>
  );
}

useMemoとuseCallback

パフォーマンス最適化のためのHooksです。重い計算や関数の再作成を防ぎます。

import { useState, useMemo, useCallback } from "react";

function ExpensiveComponent({ items, filter }) {
  // useMemo: 計算結果をメモ化(依存配列が変わるまで再計算しない)
  const filteredItems = useMemo(() => {
    console.log("フィルタリング実行...");
    return items.filter(item =>
      item.name.includes(filter)
    );
  }, [items, filter]);  // items または filter が変わった時だけ再計算

  // useCallback: 関数をメモ化(依存配列が変わるまで同じ関数を返す)
  const handleClick = useCallback((id) => {
    console.log("クリック:", id);
  }, []);  // 依存なし → 常に同じ関数

  return (
    <div>
      <p>{filteredItems.length}件</p>
      {filteredItems.map(item => (
        <div key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </div>
      ))}
    </div>
  );
}

// 注意: useMemo/useCallbackは最適化のためのもの
// すべての値や関数に使う必要はありません
// パフォーマンスに問題がある場合にのみ検討しましょう

カスタムHook

ロジックを再利用可能な関数にまとめたものがカスタムHookです。 名前は必ず use で始めます。

import { useState, useEffect } from "react";

// カスタムHook: カウンター機能
function useCounter(initialValue = 0) {
  const [count, setCount] = useState(initialValue);

  const increment = () => setCount(c => c + 1);
  const decrement = () => setCount(c => c - 1);
  const reset = () => setCount(initialValue);

  return { count, increment, decrement, reset };
}

// カスタムHook: ローカルストレージと同期
function useLocalStorage(key, initialValue) {
  const [value, setValue] = useState(() => {
    const saved = localStorage.getItem(key);
    return saved ? JSON.parse(saved) : initialValue;
  });

  useEffect(() => {
    localStorage.setItem(key, JSON.stringify(value));
  }, [key, value]);

  return [value, setValue];
}

// カスタムHook: ウィンドウサイズの取得
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    const handleResize = () => {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    };
    window.addEventListener("resize", handleResize);
    return () => window.removeEventListener("resize", handleResize);
  }, []);

  return size;
}

// 使い方
function App() {
  const { count, increment, decrement, reset } = useCounter(0);
  const [name, setName] = useLocalStorage("userName", "");
  const { width, height } = useWindowSize();

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={increment}>+</button>
      <button onClick={decrement}>-</button>
      <button onClick={reset}>リセット</button>

      <input value={name} onChange={e => setName(e.target.value)} />

      <p>画面サイズ: {width} x {height}</p>
    </div>
  );
}

まとめ

  • Hooksは関数コンポーネントでReactの機能を使うための仕組み
  • Hooksはトップレベルでのみ呼び出す(条件分岐やループの中はNG)
  • useRef はDOM参照やレンダリング間の値保持に使う
  • useMemo / useCallback はパフォーマンス最適化用
  • カスタムHookでロジックを再利用可能にする