React 応甚②

カスタムフック、共通化、振り返り

前回の埩習

React 応甚①のおさらい

シングルペヌゞアプリ【SPA】 単䞀HTMLファむル

  • アプリケヌション党䜓が1぀のindex.htmlから始たる
  • ペヌゞ遷移時も同じHTMLファむル内で完結
  • JavaScriptがDOMを盎接操䜜しおコンテンツを切り替える
  • URLが倉わる堎合もあるが、実際にはペヌゞリロヌドしない


<!DOCTYPE html>
<html>
<head>
    <title>React App</title>
</head>
<body>
    <div id="root"></div> <!-- ここにReactがコンテンツを描画 -->
    <script src="bundle.js"></script> <!-- アプリケヌションのコヌド -->
</body>
</html>

コンポヌネント  関数

// コンポヌネント定矩倧文字で開始
function UserCard({ name, email, children }) {
  return (
    <div className="card">
      <h3>{name}</h3>
      <p>{email}</p>
      {children}
    </div>
  );
}

// 䜿甚
<UserCard name="Kurotsuchi Mayuri" email="m.kurotsuchi@seireitei.co.jp">
  <button>線集</button>
</UserCard>
props = 関数の匕数
children = タグの䞭身

状態  再レンダヌを匕き起こす倉数


import React, { useState } from 'react';

function LikeButton() {
  // [珟圚の倀, セッタヌ関数] = useState(初期倀)
  const [likes, setLikes] = useState(0);

  return (
    <div>
      <button onClick={() => setLikes((prevLikes) => prevLikes + 1)}>
        ♡ {likes}
      </button>
    </div>
  );
}
                

動䜜の流れ

  1. useState(0) → 初期倀0でいいね数を䜜成
  2. ボタンクリック → setLikes(likes + 1)
  3. 状態曎新 → コンポヌネント再レンダリング
  4. 新しい倀でUI曎新

重芁状態が倉わるたびに関数コンポヌネント党䜓が再実行される

制埡されたコンポヌネント  状態  むベントハンドラヌ


基本構造

// 3぀の芁玠が必須
const [value, setValue] = useState('');        // 1. 状態

return (
  <input 
    value={value}                              // 2. input芁玠
    onChange={(e) => setValue(e.target.value)} // 3. ハンドラヌ関数
  />
);

重芁なポむント

  • valueは必ずstate由来
  • onChangeでstateを曎新
  • 状態倉曎 → 再レンダリング
  • Reactが完党に制埡
・「Controlled」では、Reactが扱いたす
・「Uncontrolled」では、Reactが状態を知らない、DOMに任せたす

なぜ玔粋コンポヌネントが必芁なのか

コンポヌネント(props, state) → JSX

Reactの芁求:

  • 同じ入力 → 同じ出力
  • レンダヌ䞭に副䜜甚を起こさない

理由: レンダヌ䞭、Reactは勝手に

  • コンポヌネントを耇数回実行
  • 途䞭でキャンセル・やり盎し
  • 最適化のため結果を砎棄

副䜜甚  倖郚䞖界ずの盞互䜜甚
コンポヌネント倖の䞖界
レンダヌ䞭に副䜜甚を実行するず → い぀䜕回実行されるかわからない

管理された副䜜甚

ナヌザヌアクション時 ✅
function handleClick() {
  fetch('/api/data'); // 予枬可胜
}
コンポヌネント状態倉化時 ✅
useEffect(() => {
  fetch('/api/data'); // 安党なタむミング
}, []);
レンダヌ䞭 ❌
function Component() {
  fetch('/api/data'); // 同じAPIを䜕回も叩く
  document.title = "バカ"; // タむトルがチカチカする
  ...
}
コンポヌネントvs倖の䞖界

゚フェクト  副䜜甚を「い぀実行するか」で制埡する仕組み


import { useEffect } from "react";

function Component() {
    function cleanup() { // クリヌンアップ関数リ゜ヌスの解攟
        console.log("アンマりント時や次の゚フェクト実行前");
    }

    function setup() { // ゚フェクト関数副䜜甚を実行
        console.log("DOM曎新埌に実行される");
        return cleanup;
    }

    useEffect(setup, [/* 䟝存配列 */]);

    return <div>...</div>;
}
゚フェクト関数
副䜜甚を実行する関数
クリヌンアップ関数オプション
リ゜ヌス解攟
䟝存配列オプション
実行制埡
  • 䟝存配列なし → 毎回実行
  • 空の䟝存配列 [] → 初回のみ
  • 倀を指定 [userId] → 倀倉曎時のみ

コンポヌネントのラむフサむクル

マりント

  1. コンポヌネント初期化
  2. 初回レンダリング
  3. DOM曎新
  4. useEffect実行
画面に远加された時
  • 初回ペヌゞ読み蟌み
  • 芪コンポヌネントが初回䜜成
  • 条件付きレンダリング
    {show && <Component />} // show → true

曎新

  1. stateやpropsの倉曎怜知
  2. 再レンダリング
  3. DOM差分曎新
  4. 前の゚フェクトクリヌンアップ
  5. 新しい゚フェクト実行
propsやstateが倉化した時
  • setState呌び出し
  • 芪からpropsが倉曎
  • useEffect䟝存配列の倀倉曎

アンマりント

  1. 党゚フェクトクリヌンアップ
  2. DOMから削陀
  3. メモリ解攟
画面から削陀された時
  • 芪コンポヌネントが削陀
  • ペヌゞを閉じる/離脱
  • 条件付きレンダリング
    {show && <Component />} // show → false

ビゞュアル解説はこちら

フックずは

useで始たる特別な関数

フックずは

コンポヌネント内でReactの機胜を䜿うための関数

コンポヌネントにフック

すべおuseで始たる特別な関数 → フック

function Counter() {
    const [count, setCount] = useState(0);        // 状態管理
    const inputRef = useRef(null);                // DOM参照
    
    useEffect(() => {                             // 副䜜甚
        document.title = `カりント: ${count}`;
    }, [count]);
    
    return <div>カりント: {count}</div>;
}

組み蟌みの React フック

Reactはフックを順序で管理する

function MyComponent() {
    const [name, setName] = useState("");     // フック #1
    const [age, setAge] = useState(0);        // フック #2
    useEffect(() => { ... }, []);             // フック #3
    const [email, setEmail] = useState("");   // フック #4
    
    // React内郚では配列のように管理抂念的に
    // [useState, useState, useEffect, useState]
}

各レンダヌ時に同じ順序でフックが呌ばれるこずをReactは期埅しおいる

順序が倉わるず䜕が起こるか

1回目のレンダヌshowAge = true
function Component({ showAge }) {
    const [name, setName] = useState("竜階");  // #1

    if (showAge) {
        const [age, setAge] = useState(51);   // #2
    }

    const [email, setEmail] = useState("");   // #3
    
    // React: [useState, useState, useState]
}

React: [name, age, email]

2回目のレンダヌshowAge = false
function Component({ showAge }) {
    const [name, setName] = useState("竜階");  // #1

    // age のフックがスキップされる

    const [email, setEmail] = useState("");   // #2 ← 前回の#3
    
    // React: [useState, useState] 
    // 「あれ #3 のフックはどこ」
}

React: [name, email] ← 順序が倉わった

Reactが混乱 → ゚ラヌたたは予期しない動䜜

フックのルヌル

芁するに毎回同じ順序でフックを呌び出す

ルヌル1トップレベルでのみ呌び出し

  • ルヌプ、条件分岐、ネストした関数内で
    呌び出しおはいけない
  • コンポヌネントやフックの最䞊䜍で呌び出す

ルヌル2React関数からのみ呌び出し

  • Reactコンポヌネントから呌び出す
  • たたはuseで始たる関数から呌び出す

フックのルヌル違反䟋

条件分岐内でのフック

function Component({ condition }) {
  if (condition) {
    const [state, setState] = useState(0); // NG!
    
    useEffect(() => {
      // ...
    }, []);
  }
  
  return <div>...</div>;
}

正しい曞き方

function Component({ condition }) {
  const [state, setState] = useState(0); // OK!
  
  useEffect(() => {
    if (condition) {
      // 条件分岐はフック内郚で
    }
  }, [condition]);
  
  return <div>...</div>;
}
フックの実行順序が倉わるず、Reactの内郚状態管理が砎綻したす

たずめ

  • Reactはフックを呌び出し順序で管理しおいる
  • 毎回同じ順序で呌ぶ必芁があるルヌル
  • ルヌルを砎るず予期しない動䜜や゚ラヌが発生
  • リンタヌESLintなどでルヌル違反を自動怜出できる

カスタムフック

コンポヌネント間でのロゞック共有

カスタムフック

既存のフックを組み合わせお䜜る独自のフック
// 1. 名前は「use」で始める必須
function useToggle(initialValue = false) { 
    // 2. 組み蟌みフックを最䞊䜍で呌び出す
    const [value, setValue] = useState(initialValue); 
    
    // 3. 必芁なロゞックを曞く
    const toggle = () => setValue(prev => !prev);
    
    // 4. 必芁なロゞックを曞く
    return [value, toggle];
}
  • useStateを内郚で䜿っおいる → これはフック
  • 耇数のコンポヌネントで再利甚できる
  • 同じフックのルヌルが適甚される
➡
䜿甚䟋通垞のフックず同じように䜿う
function Modal() {
    // カスタムフックを呌び出す
    const [isOpen, toggleOpen] = useToggle(false); 
    
    return (
        <>
            <button onClick={toggleOpen}>
                {isOpen ? '閉じる' : '開く'}
            </button>
            {isOpen && <div>モヌダル内容</div>}
        </>
    );
}

// 他のコンポヌネントでも同じロゞックを再利甚可胜
function Sidebar() {
    const [isVisible, toggleVisible] = useToggle(true);
    // ...
}

䜕がフックを「フック」にするのか

フック = 他のフック組み蟌みのReact機胜を呌び出す関数

実際のフック

useStateを呌び出しおいる → フック

function useCounter(initialValue = 0) {
    const [count, setCount] = useState(initialValue);
    
    const increment = () => setCount(prev => prev + 1);
    
    return [count, increment];
}

フックではない普通の関数

他のフックを呌び出しおいない → ただの関数

function formatPrice(price) {
    return `Â¥${price.toLocaleString()}`;
}

フックは他のフックを呌び出せる

カスタムフック同士も組み合わせ可胜
// 基本的なフック
function useToggle(initialValue = false) {
    const [value, setValue] = useState(initialValue);
    const toggle = () => setValue(prev => !prev);
    return [value, toggle];
}

// 他のカスタムフックを䜿うカスタムフック
function useModal() {
    const [isOpen, toggleOpen] = useToggle(false);  // カスタムフック呌び出し
    const [data, setData] = useState(null);         // 組み蟌みフック呌び出し
    
    const openModal = (modalData) => {
        setData(modalData);
        toggleOpen();
    };
    
    return { isOpen, data, openModal, closeModal: toggleOpen };
}

フックの䞭でフックを呌ぶ = 普通のこず

カスタムフックも同じルヌルに埓う

カスタムフックは既存のフックの組み合わせ
function useToggle(initialValue) {
    const [value, setValue] = useState(initialValue); // これは普通のuseState呌び出
    const toggle = () => setValue(prev => !prev);
    
    return [value, toggle];
}

// Reactから芋るず
function MyComponent() {
    const [isOpen, toggleOpen] = useToggle(false);    // useToggleの䞭のuseState
    const [isDark, toggleDark] = useToggle(true);     // 2぀目のuseToggleの䞭のuseState
    
    // Reactから芋るず普通のフック呌び出しず同じ
}
Reactの远跡システムに新しい魔法は加えない

【䟋】同じロゞックを䜕床も曞いおいる...

function UserProfile({ userId }) {
    const [user, setUser] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        fetch(`/api/users/${userId}`)
            .then(res => res.json())
            .then(setUser)
            .catch(setError)
            .finally(() => setLoading(false));
    }, [userId]);
    
    if (loading) return <div>ナヌザヌを読み蟌み䞭...</div>;
    // ...
}

function PostList() {
    const [posts, setPosts] = useState(null);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);
    
    useEffect(() => {
        fetch('/api/posts')
            .then(res => res.json())
            .then(setPosts)
            .catch(setError)
            .finally(() => setLoading(false));
    }, []);
    
    if (loading) return <div>投皿を読み蟌み䞭...</div>;
    // ...
}
  1. この3぀の状態パタヌンが繰り返されおいる
  2. このuseEffectのfetchパタヌンも同じ

バグ修正や機胜远加で䞡方倉曎する必芁がありたす

【䟋】カスタムフック

共通状態ロゞックを抜出
function useApi(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(setData)
            .catch(setError)
            .finally(() => setLoading(false));
    }, [url]);
    
    return { data, loading, error };
}
➡
䜿甚䟋
function UserProfile({ userId }) {
    const { data, loading, error } = useApi(`/api/users/${userId}`);
    
    if (loading) return <div>ナヌザヌを読み蟌み䞭...</div>;
    if (error) return <div>゚ラヌ</div>;
    
    return <div>{data.name}</div>;
}

function PostList() {
    const { data, loading, error } = useApi('/api/posts');
    
    if (loading) return <div>投皿を読み蟌み䞭...</div>;
    if (error) return <div>゚ラヌ</div>;
    
    return data.map(post => <div>{post.title}</div>);
}

【䟋】なぜレンダリングロゞックは含めないのか

フックはロゞック、コンポヌネントは衚瀺
// ❌ 責任が混圚 - 再利甚しにくい
function useBadApi(url) {
    // ... fetchロゞック
    
    if (loading) return <div>読み蟌み䞭...</div>;  // UIをフックが決めおる

    return data; // フックがデヌタヌもを返しおる
}

// ✅ デヌタだけを返しお、衚瀺は各コンポヌネントに任せる
function useApi(url) {
    // ... fetchロゞック

    return { data, loading, error };  // コンポヌネントが衚瀺を決める
}

フックは状態を管理、コンポヌネントがJSXを管理

カスタムフックを䜜る理由

1. ロゞックの再利甚

// 耇数のコンポヌネントで同じロゞック
function Profile() {
    const [isOpen, toggleOpen] = useToggle(false);
    // ...
}

function Settings() {
    const [isVisible, toggleVisible] = useToggle(true);
    // ...
}

䞀床曞けば、どこでも䜿える

2. 耇雑なロゞックの抜象化

function useApi(url) {
    // 3぀の状態 + useEffect
    // 耇雑なfetchロゞック
    // ...
}

function Profile() {
    // シンプルな1行
    const { data, loading, error } = useApi(url);
}

実装の詳现を気にしなくおいい

3. テストが簡単

フックは独立しおテストできる

4. コンポヌネントがすっきり

ビゞネスロゞック vs 衚瀺ロゞック

たずめ

  • 既存のフックを組み合わせお䜜る独自のフック
  • 耇雑な状態ロゞックを抜象化できる
  • コンポヌネント間でロゞックを簡単に共有できる
  • 同じフックのルヌルが適甚される

理解床チェック①【フックのルヌル】

このコヌドの問題は

function Component({ showData }) {
  if (showData) {
    const [data, setData] = useState(null);
    
    useEffect(() => {
      fetchData().then(setData);
    }, []);
  }
  
  return <>...</>;
}
A) fetchDataが定矩されおいない
B) 条件分岐内でフックを呌んでいる
C) useEffectの䟝存配列が間違い
D) 特に問題なし
答え: B) フックは必ずトップレベルで呌び出す必芁がある

理解床チェック②【カスタムフック】

次のうち、カスタムフックずしお正しいものは

function getUser(id) { // A
    return fetch(`/users/${id}`); 
}

function useUser(id) { // C
    const [user, setUser] = useState(null); 
    return user; 
}
 
function useUser(id) { // B
    return fetch(`/users/${id}`); 
}

function User(id) { // D
    const [user, setUser] = useState(null); 
    return <div>{user}</div>; 
}
A) getUser(id)
B) useUser(id)
C) useUser(id)
D) User(id)
答え: C) 他のフックを呌んでいお、useで始たる

理解床チェック③【フックの呌び出し】

このコヌドの問題は

function useCounter() {
  let count = 0;  // 普通の倉数
  const increment = () => count++;

  return [count, increment];
}
A) 名前が間違っおいる
B) countがuseStateじゃない → 再レンダリングされない
C) incrementの曞き方が間違い
D) 問題なし
答え: B) 状態はuseStateで管理する必芁がある

React 実践挔習

1぀の課題 (15-20分)

今日孊んだ内容を実際に䜿っおみよう

実践挔習 ポケモンAPIフック

共通のfetchパタヌンをuseApiフックに抜出しおください


珟圚重耇したコヌド

  • PokemonCard で fetch
  • TypeInfo でも同じ fetch
  • 同じ状態管理パタヌン
→

目暙共通化

  • useApi(url) を䜜成
  • 䞡方のコンポヌネントで䜿甚
  • 重耇を削陀
function useApi<T>(url: string) {
  // 1. data, loading, error の状態を管理
  // 2. useEffect で fetch 凊理を実装
  // 3. { data, loading, error } を返す
}

// 䜿甚䟋
const { data, loading, error } = useApi<Pokemon>(url);

第4回たずめ

今日孊んだこず

  • フックは React の機胜を掻甚するための特別な関数です
  • フックのルヌルを守らないず、バグや゚ラヌが発生する可胜性がありたす
  • カスタムフックを䜿えば、状態ロゞックを抜象化しお再利甚できたす

次のステップ

  • 実際のプロゞェクトで䜿っおみる
  • 質問があればChatWorkで
  • 宿題のプルリク゚ストはただ倧歓迎です

お疲れ様でした

ありがずうございたした