React Hooksで変わる開発体験|初心者が陥る罠と実践的な使い方

私が初めてReact Hooksに触れたとき、正直言って「なんだこれ、意味わからん」って思ったんです。

当時、クライアントから「Reactで開発してほしい」と依頼を受けて、いざコードを見たら見慣れないuseStateとかuseEffectとかが並んでて。ネットで調べても「関数コンポーネントでstateが使える!」みたいな説明ばかりで、肝心の「で、どう使うの?」って部分がピンとこなかった。

結局、最初の案件では無限ループを何度も発生させてブラウザを固まらせ、「これ、本当に便利なの…?」と疑問を抱きながらデバッグに追われる日々でした。

でも今、私はReact Hooksなしでは開発したくないと思ってます。

この記事では、当時の私みたいに「Hooksって何がいいの?」「どう使えばいいの?」と悩んでいる人に向けて、実践的な使い方と陥りがちな罠、そして副業案件で求められるスキルまでを解説していきます。

この記事を読めば、React Hooksの基本がわかるだけでなく、実際の案件で使えるレベルまでスキルアップできる道筋が見えてくるはずです。

目次

React Hooksが登場した背景を知っておく

技術を学ぶ時、「なぜそれが生まれたのか」を知っておくと理解が深まります。React Hooksも例外じゃありません。

Reactは元々、クラスコンポーネントという書き方が主流でした。stateを持たせたり、ライフサイクルメソッドを使ったりするには、必ずクラスで書く必要があったんです。

でも、クラスコンポーネントには問題があった。

書くコード量が多い、thisの扱いが面倒、ロジックの再利用がしにくい…。私も昔、何度も「あれ、thisって何を指してるんだっけ?」って混乱したことがあります。深夜のデバッグで頭が回らなくなってくると、もう地獄ですよ。

そこで2019年、React 16.8で登場したのがHooksです。

深夜にノートパソコンでコーディングに苦戦する男性の線画イラスト

Hooksを使えば、関数コンポーネントでもstateやライフサイクルの機能が使える。クラスを書く必要がなくなり、コードがシンプルになる。これが大きな変革でした。

実際、現場でも「新しいプロジェクトはHooksで書く」というのがスタンダードになってます。古いコードでクラスコンポーネントを見かけることはあるけど、新規で書くことはほぼないですね。

つまり、今からReactを学ぶなら、Hooksは避けて通れない道なんです。

初心者が必ずハマる「JavaScript理解不足」の罠

React Hooksでつまづく人の多くは、実はReactが悪いんじゃありません。JavaScriptの理解が不十分なんです。

これ、私が数百人の初心者をメンターしてきて確信してる部分です。

特に以下のJavaScriptの概念を理解してないと、Hooksの学習で確実に詰まります。

分割代入

// これがわからないとuseStateで困る
const [count, setCount] = useState(0);

これ、配列の分割代入って文法なんですが、知らないと「なんで配列じゃないのに[]使ってるの?」ってなります。私も最初そうでした。

アロー関数

// これが理解できないと先に進めない
const handleClick = () => {
  setCount(count + 1);
};

アロー関数の書き方、thisの扱い、これを知らないとHooksのコールバック関数で混乱します。

クロージャ

これが一番厄介。useStateやuseEffectの仕組みを理解するには、クロージャの概念が不可欠です。

関数が外側のスコープの変数を「覚えている」って仕組み。これを知らないと、「なんでこの値が古いままなの?」っていう現象に遭遇して迷宮入りします。

参考書を広げながら困った表情で頭を抱える女性の線画イラスト

私が初心者に教える時は、必ずこの3つのJavaScript基礎を先に固めてもらいます。急がば回れ、ですよ。

useStateの正しい使い方と失敗例

さて、React Hooksの基本中の基本、useStateから解説していきます。

useStateは、関数コンポーネントに状態(state)を持たせるためのHookです。

import { useState } from 'react';

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

  return (
    <div>
      <p>カウント: {count}</p>
      <button onClick={() => setCount(count + 1)}>
        +1する
      </button>
    </div>
  );
}

ボタンを押すと数字が増える、シンプルなカウンターアプリです。

よくある失敗:直接変更しようとする

// これはダメ!
function BadExample() {
  const [count, setCount] = useState(0);

  const increment = () => {
    count = count + 1;  // これはエラーになる
  };

  return <button onClick={increment}>増やす</button>;
}

Reactのstateは、必ずsetXXXという関数を使って更新します。直接変更しようとすると動きません。

私も昔、「なんで更新されないの?」って1時間くらい悩んだことがあります。結局、console.logで確認したら値は変わってるのに画面が更新されてなくて、「あ、set関数使ってない…」って気づいた時の恥ずかしさといったら。

複数のstateを扱う

function UserForm() {
  const [name, setName] = useState('');
  const [age, setAge] = useState(20);
  const [email, setEmail] = useState('');

  return (
    <form>
      <input 
        value={name} 
        onChange={(e) => setName(e.target.value)} 
      />
      <input 
        type="number" 
        value={age} 
        onChange={(e) => setAge(Number(e.target.value))} 
      />
      <input 
        value={email} 
        onChange={(e) => setEmail(e.target.value)} 
      />
    </form>
  );
}

複数のstateが必要な場合、useStateを複数回呼び出せばOKです。

ただし、関連するデータはオブジェクトでまとめた方がいい場合もあります。

function UserForm() {
  const [user, setUser] = useState({
    name: '',
    age: 20,
    email: ''
  });

  const updateName = (value) => {
    setUser({ ...user, name: value });
  };

  return (
    <input 
      value={user.name} 
      onChange={(e) => updateName(e.target.value)} 
    />
  );
}

スプレッド構文(…)を使って、既存の値を保持しつつ一部だけ更新する。これができないと、他のフィールドが消えちゃいます。

現場では、フォームのように関連データが多い場合はオブジェクトでまとめることが多いですね。

useEffectで副作用を扱う|ここで9割が挫折する

useEffectは、React Hooksの中で最もつまづきやすいHookです。

私がメンターしてきた人の中でも、「useEffectがわからない」って悩む人が圧倒的に多い。

useEffectは「副作用」を扱うHookです。副作用って何かっていうと、コンポーネント外の何かに影響を与える処理のこと。

  • データの取得(API通信)
  • DOM操作
  • タイマーの設定
  • ログの記録

こういった「画面表示以外の処理」をuseEffectで書きます。

カフェでノートパソコンを見ながら悩む表情の男性の線画イラスト

基本的な使い方

import { useState, useEffect } from 'react';

function UserProfile() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    // コンポーネントがマウントされた時に実行
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []); // 空の配列 = 初回のみ実行

  if (!user) return <p>読み込み中...</p>;

  return <div>{user.name}</div>;
}

useEffectの第2引数に空の配列[]を渡すと、コンポーネントの初回レンダリング時だけ実行されます。

最大の罠:無限ループ

// これは無限ループになる!
function BadExample() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setCount(count + 1);  // countを更新
  }, [count]);  // countが変わったら実行

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

何が起きるかというと:

  1. countが更新される
  2. useEffectが実行される
  3. またcountが更新される
  4. またuseEffectが実行される
  5. 以下無限ループ

ブラウザが固まります。私も最初の頃、これで何度もタブを閉じる羽目になりました。

依存配列の正しい扱い方

function SearchResults({ keyword }) {
  const [results, setResults] = useState([]);

  useEffect(() => {
    // keywordが変わった時だけ検索を実行
    fetch(`/api/search?q=${keyword}`)
      .then(res => res.json())
      .then(data => setResults(data));
  }, [keyword]); // keywordを依存配列に入れる

  return (
    <ul>
      {results.map(item => <li key={item.id}>{item.title}</li>)}
    </ul>
  );
}

依存配列には、useEffect内で使っている変数を入れます。

ESLintのプラグイン(eslint-plugin-react-hooks)を入れておくと、依存配列の漏れを警告してくれるので、必ず導入しましょう。これだけで無駄なバグを防げます。

クリーンアップ処理

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds(prev => prev + 1);
    }, 1000);

    // クリーンアップ関数
    return () => {
      clearInterval(interval);
    };
  }, []);

  return <div>{seconds}秒経過</div>;
}

タイマーやイベントリスナーを設定した場合、コンポーネントが消える時にクリーンアップしないとメモリリークの原因になります。

return文でクリーンアップ関数を返す。これを忘れると、画面遷移した後もタイマーが動き続けたりします。地味にハマるポイントです。

実際の案件でよく使うHooksの組み合わせ

実務では、useStateとuseEffectだけじゃなく、他のHooksも組み合わせて使います。

useContextでグローバル状態管理

import { createContext, useContext, useState } from 'react';

// コンテキストを作成
const UserContext = createContext();

function App() {
  const [user, setUser] = useState(null);

  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Header />
      <MainContent />
    </UserContext.Provider>
  );
}

// どこからでもuserにアクセスできる
function Header() {
  const { user } = useContext(UserContext);

  return <div>ようこそ、{user?.name}さん</div>;
}

useContextは、propsをバケツリレー式に渡さなくても、深い階層のコンポーネントでデータを使えるようにするHookです。

ログイン情報やテーマ設定など、アプリ全体で使うデータに向いてます。

ただし、何でもかんでもContextに入れると、逆に管理が大変になります。本当にグローバルに必要なものだけ、という使い分けが大事。

コワーキングスペースでノートを取りながら真剣な表情でコーディングする女性の線画イラスト

useCallbackでパフォーマンス最適化

import { useState, useCallback } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([]);

  // この関数をメモ化
  const addTodo = useCallback((text) => {
    setTodos(prev => [...prev, { id: Date.now(), text }]);
  }, []);

  return <TodoForm onAdd={addTodo} />;
}

useCallbackは、関数をメモ化して不要な再生成を防ぐHookです。

子コンポーネントに関数を渡す時、毎回新しい関数が作られると子コンポーネントも再レンダリングされちゃう。それを防ぐために使います。

正直、小さいアプリなら気にしなくていいです。でも、案件で大きめのアプリを作る時は、パフォーマンスのボトルネックになりやすいので、知っておくべき。

カスタムフックで処理を再利用

// フェッチ処理をカスタムフックに
function useFetch(url) {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch(url)
      .then(res => res.json())
      .then(data => {
        setData(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [url]);

  return { data, loading, error };
}

// 使う側
function UserList() {
  const { data, loading, error } = useFetch('/api/users');

  if (loading) return <p>読み込み中...</p>;
  if (error) return <p>エラー: {error.message}</p>;

  return (
    <ul>
      {data.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

カスタムフックは、複数のコンポーネントで使い回せるロジックを切り出す仕組みです。

命名規則として、必ずuseから始める名前にします。これがルールです。

現場では、API通信、フォームのバリデーション、認証状態の管理など、よく使う処理をカスタムフックにまとめることが多いですね。

React案件で副業するために必要なスキル

ここまでReact Hooksの基本を解説してきましたが、「じゃあ、実際に案件を取るにはどうすればいいの?」って話をします。

私が見てきた中で、React案件を獲得できる人とできない人の違いは明確です。

実務経験が最重要

クラウドソーシングでもフリーランスエージェントでも、ほぼ100%「実務経験○年以上」って条件があります。

React案件の平均単価は月76万円前後と高めですが、未経験だと月10~30万円の低単価案件しか取れません。

実務経験1~2年で月45万円、3年以上で月60~100万円が相場です。

「未経験でも取れる案件はないんですか?」ってよく聞かれますが、正直厳しい。あっても、テスターとか簡単な修正作業とかです。

じゃあどうするか。

まずは正社員か契約社員でReactの実務経験を積む。それが最短ルートです。副業は、実務経験を積んでから考える方が現実的。

夜の自宅でノートパソコンの画面を見つめる集中した表情の男性の線画イラスト

TypeScriptは必須スキル

現場のReact案件、ほぼTypeScriptです。

import { useState } from 'react';

interface User {
  id: number;
  name: string;
  email: string;
}

function UserProfile() {
  const [user, setUser] = useState<User | null>(null);

  return <div>{user?.name}</div>;
}

型定義があると、バグが減る、補完が効く、チーム開発がしやすい。メリットしかありません。

TypeScript抜きでReact案件に応募しても、書類で落とされます。それくらい必須です。

Next.jsも視野に入れる

Reactのフレームワーク、Next.jsを使う案件も増えてます。

サーバーサイドレンダリング、静的サイト生成、APIルート…Reactだけより幅広い機能が使えます。

案件の募集要項でも「React + Next.js経験者優遇」ってよく見かけます。

Reactである程度慣れたら、Next.jsにも触れておくと案件の幅が広がります。

実際の案件例

私が見てきた案件だと:

  • 月60万円:SPA開発、React + TypeScript、週3日リモート
  • 月80万円:ECサイトのフロントエンド開発、Next.js使用、週5日
  • 月45万円:既存システムの保守・改修、React経験2年以上

こんな感じです。

単価を上げたいなら、バックエンド(Node.js、PHP、Python)も少しできると強い。フルスタックで動ける人は重宝されます。

初心者が陥る学習の落とし穴

React Hooksを学ぶ上で、多くの人が陥る罠をいくつか挙げておきます。

公式ドキュメントを読まない

これ、本当にもったいない。

Reactの公式ドキュメントは、かなり丁寧に書かれてます。日本語訳もあります。

ネットの記事ばかり見てると、古い情報や間違った情報に当たることがある。公式を読む習慣をつけましょう。

私も昔、「公式って難しそう」って避けてたんですが、読んでみたら意外とわかりやすかった。今は初心者にも必ず公式を勧めてます。

とりあえずコピペで動かす

「動けばいいや」でコピペしてると、確実に行き詰まります。

なぜそのコードを書くのか、何をしているのか、理解しながら進めないと、トラブルが起きた時に対処できません。

私が教えてる時も、「このコード、何してるかわかる?」って必ず聞きます。説明できないなら、理解してない証拠です。

完璧を求めすぎる

逆に、完璧主義で動けなくなる人もいます。

「useEffectの依存配列、これで本当に正しいのかな…」って悩んで手が止まる。

最初はエラーが出ても当たり前です。動かしてみて、エラーを読んで、修正して、また動かす。このサイクルが学習です。

完璧なコードは誰も書けません。動くコードから始めて、少しずつ改善していけばいいんです。

朝のカフェでコーヒーを飲みながらリラックスした表情でノートパソコンを操作する女性の線画イラスト

環境構築で諦める

Node.jsのインストール、create-react-appの実行、エラーが出る…

環境構築でつまづいて諦める人、めちゃくちゃ多いです。

今はCodeSandboxやStackBlitzみたいなオンラインエディタがあるので、ブラウザだけで試せます。最初はそれでもいい。

環境構築は慣れてからでも遅くないです。

よくある質問に答える

Q. 独学でReactを習得できますか?

できます。私も独学でした。

ただし、正しい学習順序を踏むこと。JavaScript → React基礎 → Hooks → TypeScriptという順番です。

いきなりHooksから始めると混乱します。JavaScriptの基礎を固めてから進んでください。

Q. スクールに通った方がいいですか?

お金と時間に余裕があるなら、スクールもありです。

メンターがいると、わからない時すぐ聞けるのは大きいメリット。独学だと調べるのに時間がかかりますから。

ただし、スクール出たからって即案件が取れるわけじゃありません。実務経験は別です。

Q. Reactの案件、未経験でも取れますか?

厳しいですが、ゼロではありません。

クラウドソーシングで、単価は低いけど「未経験OK」の案件はあります。ただし競争率が高い。

現実的には、まず企業で実務経験を積む方が早いです。

Q. 学習にどれくらい時間がかかりますか?

人によりますが、毎日2~3時間勉強して、3ヶ月くらいで基礎は固まります。

案件レベルになるには、さらに実践経験が必要。半年~1年は見ておいた方がいいです。

焦らず、コツコツ積み上げることが大事です。

Q. おすすめの学習教材はありますか?

公式ドキュメントが最強です。あとはUdemyの動画講座もわかりやすい。

書籍なら「基礎から学ぶ React/React Hooks」が初心者向けで評判いいです。

ただし、教材を読むだけじゃダメ。必ず手を動かしてコードを書いてください。

まとめ:React Hooksは怖くない

長くなりましたが、React Hooksについて解説してきました。

最初は難しく感じるかもしれません。私も最初はそうでした。useEffectの無限ループで何度もブラウザを固めたし、依存配列の意味がわからなくて何時間も悩んだこともあります。

でも、基礎を押さえて、手を動かして、エラーと向き合っていけば、必ず理解できます。

React Hooksは、習得すればフロントエンド開発の幅が一気に広がります。案件の選択肢も増えるし、単価も上がる。努力する価値は十分あります。

まずはuseStateとuseEffectから。小さなアプリでいいので、実際に作ってみてください。

Todoアプリでもカウンターでもいい。動くものを作る経験が、一番の学びになります。

そして、実務経験を積んで、TypeScriptやNext.jsにも挑戦して、着実にスキルアップしていく。

焦らず、でも止まらず、一歩ずつ進んでいけば、必ず案件を取れるレベルに到達できます。

私もあなたを応援してます。頑張ってください。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いたエンジニア

玉城 悠斗のアバター 玉城 悠斗 サーバーサイドエンジニア

サーバーサイドに精通したエンジニアで、特にPHP・Goでの開発経験が豊富。設計力に優れ、堅牢で拡張性のある構築が得意。話しやすい雰囲気でチームからの信頼が厚い。釣りとキャンプが趣味で、自然の中でアイデアを練ることもある。

目次