<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>useEffect</title>
	<atom:link href="https://techgrowup.net/tag/useeffect/feed/" rel="self" type="application/rss+xml" />
	<link>https://techgrowup.net</link>
	<description>エンジニアを強くする</description>
	<lastBuildDate>Mon, 05 May 2025 11:00:00 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://techgrowup.net/wp-content/uploads/2021/05/hp-icon-150x150.png</url>
	<title>useEffect</title>
	<link>https://techgrowup.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Custom Hooks完全ガイド──Reactでロジックを再利用しコンポーネントを超DRY</title>
		<link>https://techgrowup.net/react-custom-hooks/</link>
					<comments>https://techgrowup.net/react-custom-hooks/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Mon, 05 May 2025 11:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[Custom Hook]]></category>
		<category><![CDATA[useEffect]]></category>
		<category><![CDATA[useState]]></category>
		<category><![CDATA[パフォーマンス]]></category>
		<category><![CDATA[ロジック共有]]></category>
		<category><![CDATA[再利用]]></category>
		<category><![CDATA[型安全]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2846</guid>

					<description><![CDATA[はじめに React 公式チュートリアルでも強調される「コンポーネントは UI を、Hooks はロジックを記述する」という原則。しかし現場のコードベースでは、似たような副作用や状態初期化ロジックを複数コンポーネント間で [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">はじめに</h1>



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



<h2 class="wp-block-heading">Custom Hooks の基本構文</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useState, useEffect } from 'react';

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

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

  return time;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useClock</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">time</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setTime</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Date</span><span style="color: #D4D4D4">());</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">id</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">setInterval</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setTime</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Date</span><span style="color: #D4D4D4">()), </span><span style="color: #B5CEA8">1000</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">clearInterval</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  }, []);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">time</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>関数名</strong>は <code>use</code> から始める</li>



<li><strong>ビルトイン Hook</strong> は通常どおり使用可能</li>



<li><strong>依存配列</strong>は呼び出し元コンポーネントではなく、Hook 内部で完結</li>
</ul>



<h2 class="wp-block-heading">よくあるアンチパターン: カスタムフックの中で条件付きフック呼び出し</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function useWrong(flag) {
  if (flag) {
    // これはルール違反: フックはトップレベルで呼ぶ必要がある
    useEffect(() =&gt; console.log('Bad'), []);
  }
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useWrong</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">flag</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">flag</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #6A9955">// これはルール違反: フックはトップレベルで呼ぶ必要がある</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;Bad&#39;</span><span style="color: #D4D4D4">), []);</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



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



<h2 class="wp-block-heading">事例1: API フェッチロジックの共通化</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="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(() =&gt; {
    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 () =&gt; {
      ignore = true;
      controller.abort();
    };
  }, [url, JSON.stringify(options)]);

  return { status, data, error };
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useFetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">options</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">status</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setStatus</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;idle&#39;</span><span style="color: #D4D4D4">);  </span><span style="color: #6A9955">// idle | loading | success | error</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setData</span><span style="color: #D4D4D4">]   = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">error</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setError</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ignore</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">controller</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">AbortController</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">async</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">start</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">setStatus</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;loading&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">try</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">res</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">, { ...</span><span style="color: #9CDCFE">options</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">signal:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">controller</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">signal</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">res</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">ok</span><span style="color: #D4D4D4">) </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Error</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">res</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">statusText</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">json</span><span style="color: #D4D4D4"> = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">res</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">ignore</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #DCDCAA">setData</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">json</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #DCDCAA">setStatus</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;success&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">      } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">ignore</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #DCDCAA">setError</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #DCDCAA">setStatus</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;error&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">      }</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">start</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">ignore</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">controller</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">abort</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    };</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">stringify</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">options</span><span style="color: #D4D4D4">)]);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">status</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">ポイント解説</h3>



<ul class="wp-block-list">
<li><strong>AbortController</strong> でコンポーネントのアンマウント時にリクエストを中断</li>



<li>依存配列に <code>options</code> がオブジェクトの場合は <code>JSON.stringify</code> で比較簡略化</li>



<li><code>status</code> を文字列列挙にすることで UI が状態ごとに切り替えやすくなる</li>
</ul>



<h2 class="wp-block-heading">事例2: フォーム入力とバリデーション</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="export function useForm(initial) {
  const [values, setValues] = useState(initial);
  const [errors, setErrors] = useState({});

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

  const validate = (rules) =&gt; {
    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 = () =&gt; setValues(initial);

  return { values, errors, onChange, validate, reset };
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useForm</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">initial</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">values</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setValues</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">initial</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">errors</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setErrors</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">({});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">onChange</span><span style="color: #D4D4D4"> = (</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">name</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">value</span><span style="color: #D4D4D4"> } = </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">target</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">setValues</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({ ...</span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">[name]:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4"> }));</span></span>
<span class="line"><span style="color: #D4D4D4">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">validate</span><span style="color: #D4D4D4"> = (</span><span style="color: #9CDCFE">rules</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">newErrors</span><span style="color: #D4D4D4"> = {};</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">for</span><span style="color: #D4D4D4"> (</span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">key</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">in</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">rules</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">error</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">rules</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4">](</span><span style="color: #9CDCFE">values</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">newErrors</span><span style="color: #D4D4D4">[</span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4">] = </span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">setErrors</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">newErrors</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">Object</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">keys</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">newErrors</span><span style="color: #D4D4D4">).</span><span style="color: #9CDCFE">length</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  };</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">reset</span><span style="color: #D4D4D4"> = () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setValues</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">initial</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">values</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">errors</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">onChange</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">validate</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">reset</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">使用例</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Signup() {
  const { values, errors, onChange, validate } = useForm({ email: '', pw: '' });

  const handleSubmit = () =&gt; {
    if (validate({
      email: v =&gt; /@/.test(v) ? '' : 'メールが不正',
      pw: v =&gt; v.length &gt;= 8 ? '' : '8文字以上必要'
    })) {
      // 送信
    }
  };
  return (/* JSX */);
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Signup</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">values</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">errors</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">onChange</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">validate</span><span style="color: #D4D4D4"> } = </span><span style="color: #DCDCAA">useForm</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">email:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">pw:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;&#39;</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">handleSubmit</span><span style="color: #D4D4D4"> = () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #DCDCAA">validate</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">email</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D16969"> /@/</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">test</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4">) ? </span><span style="color: #CE9178">&#39;&#39;</span><span style="color: #D4D4D4"> : </span><span style="color: #CE9178">&#39;メールが不正&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">pw</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">v</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">length</span><span style="color: #D4D4D4"> &gt;= </span><span style="color: #B5CEA8">8</span><span style="color: #D4D4D4"> ? </span><span style="color: #CE9178">&#39;&#39;</span><span style="color: #D4D4D4"> : </span><span style="color: #CE9178">&#39;8文字以上必要&#39;</span></span>
<span class="line"><span style="color: #D4D4D4">    })) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #6A9955">// 送信</span></span>
<span class="line"><span style="color: #D4D4D4">    }</span></span>
<span class="line"><span style="color: #D4D4D4">  };</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span><span style="color: #6A9955">/* JSX */</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">パフォーマンスとメモ化のベストプラクティス</h2>



<p>Custom Hook 内で <code>useMemo</code> や <code>useCallback</code> を使う場面:</p>



<ol class="wp-block-list">
<li><strong>計算コストが高いロジックをキャッシュ</strong></li>



<li><strong>子に渡すコールバックを安定参照に</strong></li>



<li><strong>オブジェクトや配列を返す場合</strong>—参照が変わると下位ツリーが再レンダー</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function usePagination(total, perPage) {
  const pageCount = useMemo(() =&gt; Math.ceil(total / perPage), [total, perPage]);
  return pageCount;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">usePagination</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">total</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">perPage</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">pageCount</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">Math</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">ceil</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">total</span><span style="color: #D4D4D4"> / </span><span style="color: #9CDCFE">perPage</span><span style="color: #D4D4D4">), [</span><span style="color: #9CDCFE">total</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">perPage</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">pageCount</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">TypeScriptとの組み合わせ</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function useToggle&lt;S extends string&gt;(on: S, off: S) {
  const [state, setState] = useState&lt;S&gt;(off);
  const toggle = () =&gt; setState(s =&gt; s === on ? off : on);
  return { state, toggle };
}

const { state } = useToggle&lt;'on' | 'off'&gt;('on', 'off');" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useToggle</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">S</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">extends</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #9CDCFE">on</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">S</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">off</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">S</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">state</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setState</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">S</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #9CDCFE">off</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">toggle</span><span style="color: #D4D4D4"> = () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">s</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">s</span><span style="color: #D4D4D4"> === </span><span style="color: #9CDCFE">on</span><span style="color: #D4D4D4"> ? </span><span style="color: #9CDCFE">off</span><span style="color: #D4D4D4"> : </span><span style="color: #9CDCFE">on</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">toggle</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">state</span><span style="color: #D4D4D4"> } = </span><span style="color: #DCDCAA">useToggle</span><span style="color: #D4D4D4">&lt;</span><span style="color: #CE9178">&#39;on&#39;</span><span style="color: #D4D4D4"> | </span><span style="color: #CE9178">&#39;off&#39;</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #CE9178">&#39;on&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #CE9178">&#39;off&#39;</span><span style="color: #D4D4D4">);</span></span></code></pre></div>



<h2 class="wp-block-heading">テスト戦略</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';

test('カウンタが増える', () =&gt; {
  const { result } = renderHook(() =&gt; useCounter());
  act(() =&gt; result.current.inc());
  expect(result.current.count).toBe(1);
});" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">renderHook</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">act</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;@testing-library/react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useCounter</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;./useCounter&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #DCDCAA">test</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;カウンタが増える&#39;</span><span style="color: #D4D4D4">, () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">result</span><span style="color: #D4D4D4"> } = </span><span style="color: #DCDCAA">renderHook</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useCounter</span><span style="color: #D4D4D4">());</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">act</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">result</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">inc</span><span style="color: #D4D4D4">());</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">expect</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">result</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">count</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">toBe</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">});</span></span></code></pre></div>



<h2 class="wp-block-heading">高度: イベントリスナーを扱う useEventListener フック</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useEffect, useRef } from 'react';

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

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

  useEffect(() =&gt; {
    const el = target?.current || target;
    if (!el?.addEventListener) return;
    const listener = (e) =&gt; saved.current(e);
    el.addEventListener(type, listener);
    return () =&gt; el.removeEventListener(type, listener);
  }, [type, target]);
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useRef</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useEventListener</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">handler</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">target</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">window</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">saved</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</span><span style="color: #D4D4D4">();</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">saved</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">handler</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">handler</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">el</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">target</span><span style="color: #D4D4D4">?.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4"> || </span><span style="color: #9CDCFE">target</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!</span><span style="color: #9CDCFE">el</span><span style="color: #D4D4D4">?.</span><span style="color: #9CDCFE">addEventListener</span><span style="color: #D4D4D4">) </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">listener</span><span style="color: #D4D4D4"> = (</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">saved</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">current</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">el</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">addEventListener</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">listener</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">el</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">removeEventListener</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">listener</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">target</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">利用例: Escape キーでモーダルを閉じる</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Modal({ onClose }) {
  useEventListener('keydown', e =&gt; {
    if (e.key === 'Escape') onClose();
  });
  /* ... */
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Modal</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">onClose</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEventListener</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;keydown&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4"> === </span><span style="color: #CE9178">&#39;Escape&#39;</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">onClose</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  });</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">/* ... */</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">React 18 Concurrent Rendering と Custom Hooks</h2>



<h3 class="wp-block-heading">中断と再実行</h3>



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



<ul class="wp-block-list">
<li><strong>NG</strong>: <code>useMemo(() => performSideEffect(), [])</code></li>



<li><strong>OK</strong> : <code>useEffect</code> 内で副作用を実行し、クリーンアップを返す</li>
</ul>



<h3 class="wp-block-heading">スケルトン UI の設計</h3>



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



<h2 class="wp-block-heading">パフォーマンス計測: React Profiler で Hook のコストを可視化</h2>



<ol class="wp-block-list">
<li>React DevTools の Profiler タブを開き「Record」</li>



<li>画面操作を実行</li>



<li>Flamegraph または Ranked view で再レンダー時間を検査</li>



<li>Custom Hook 導入前後で差分を比較</li>
</ol>



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



<h2 class="wp-block-heading">他ライブラリの Custom Hook パターン</h2>



<ul class="wp-block-list">
<li><strong>TanStack Query</strong>: <code>useQuery</code>, <code>useMutation</code></li>



<li><strong>Redux Toolkit</strong>: <code>useSelector</code>, <code>useDispatch</code></li>



<li><strong>Zustand</strong>: <code>useStore(selector)</code></li>
</ul>



<h2 class="wp-block-heading">カスタム Hook のドキュメントとディスカバビリティ</h2>



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



<ul class="wp-block-list">
<li><strong>命名規約</strong>：機能＋動詞 (<code>useFetchUser</code>, <code>useDebouncedValue</code>)</li>



<li><strong>Storybook Docs</strong>: Hook 用ストーリを作り動作を可視化</li>



<li><strong>JSDoc/TSDoc</strong> と例コードを README に添付</li>
</ul>



<h2 class="wp-block-heading">落とし穴と対策一覧</h2>



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



<h2 class="wp-block-heading">いまさら聞けない React ルールオブフックス</h2>



<ol class="wp-block-list">
<li><strong>トップレベルでのみ呼ぶ</strong></li>



<li><strong>関数コンポーネント or Custom Hook 内で呼ぶ</strong></li>
</ol>



<h2 class="wp-block-heading">エラーハンドリング: useSafeAsync フック</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function useSafeAsync(fn, deps = []) {
  const mounted = useRef(true);
  useEffect(() =&gt; {
    mounted.current = true;
    fn().catch(console.error);
    return () =&gt; (mounted.current = false);
  }, deps);
  const safeSet = updater =&gt; {
    if (mounted.current) updater();
  };
  return safeSet;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useSafeAsync</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">fn</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">deps</span><span style="color: #D4D4D4"> = []) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">mounted</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">mounted</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">fn</span><span style="color: #D4D4D4">().</span><span style="color: #DCDCAA">catch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">error</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">mounted</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  }, </span><span style="color: #9CDCFE">deps</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">safeSet</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">updater</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">mounted</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">updater</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  };</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">safeSet</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">フックファクトリパターン</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function createUseApi(base) {
  return function useApi(path) {
    return useFetch(base + path);
  };
}
const useGitHub = createUseApi('https://api.github.com');" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">createUseApi</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">base</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useApi</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">path</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useFetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">base</span><span style="color: #D4D4D4"> + </span><span style="color: #9CDCFE">path</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  };</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">useGitHub</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createUseApi</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;https://api.github.com&#39;</span><span style="color: #D4D4D4">);</span></span></code></pre></div>



<h2 class="wp-block-heading">まとめ</h2>



<p>この記事では Custom Hook の意義から実装、型安全、テスト、パフォーマンス、外部 API 連携、アクセシビリティまで幅広く網羅しました。これらのノウハウを活かし、あなたの React プロジェクトを <strong>再利用性が高く、バグが少なく、パフォーマンスに優れた</strong> コードベースへ進化させてください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-custom-hooks/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Reactエフェクトのライフサイクル完全ガイド──useEffectがいつ動く？依存関係・クリーンアップ・タイミングを徹底解説</title>
		<link>https://techgrowup.net/react-lifecycle-effects/</link>
					<comments>https://techgrowup.net/react-lifecycle-effects/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sun, 27 Apr 2025 19:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[useEffect]]></category>
		<category><![CDATA[クリーンアップ]]></category>
		<category><![CDATA[パフォーマンス]]></category>
		<category><![CDATA[フック]]></category>
		<category><![CDATA[ライフサイクル]]></category>
		<category><![CDATA[依存配列]]></category>
		<category><![CDATA[副作用]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2821</guid>

					<description><![CDATA[はじめに Reactコンポーネントで副作用（データ取得・購読・手動DOM操作等）を扱う際の要がuseEffectフックです。しかし「いつ呼ばれるのか」「どこでクリーンアップすべきか」「依存配列の指定をどう書くべきか」を誤 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">はじめに</h1>



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



<h2 class="wp-block-heading">Reactエフェクトの基本概念</h2>



<p>エフェクト（副作用）とは、ReactのUI更新の外側で起きる処理全般を指します。具体例としては：</p>



<ul class="wp-block-list">
<li>外部APIからデータを取得する</li>



<li>WebSocketやイベントリスナーを登録・解除する</li>



<li>手動でDOMを操作する（サードパーティライブラリ連携）</li>
</ul>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useEffect } from 'react';

function Example({ url }) {
  useEffect(() =&gt; {
    fetch(url).then(r =&gt; r.json()).then(data =&gt; {
      console.log(data);
    });
  }, [url]);
  return &lt;div&gt;データを取得中…&lt;/div&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Example</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">()).</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    });</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">データを取得中…</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>第1引数</strong>：エフェクト用コールバック</li>



<li><strong>第2引数</strong>：依存配列。中身が変わったときだけ再実行</li>
</ul>



<h2 class="wp-block-heading">ライフサイクルフロー全体像</h2>



<ol class="wp-block-list">
<li><strong>マウント後</strong>：最初のレンダーが完了した直後に実行</li>



<li><strong>更新後</strong>：依存配列の値が前回と異なるとレンダー後に再実行</li>



<li><strong>アンマウント前</strong>：前回のエフェクトで返したクリーンアップ関数を実行</li>



<li><strong>依存配列なし</strong>：マウント後のみ実行、更新時には再実行されない</li>



<li><strong>依存配列を省略</strong>：レンダーごとに実行し、旧エフェクトは都度クリーンアップ</li>
</ol>



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



<h2 class="wp-block-heading">マウント後の初回実行</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function InitLogger() {
  useEffect(() =&gt; {
    console.log('コンポーネントが画面に追加されました');
  }, []); // 依存配列空でマウント後のみ
  return &lt;div&gt;初期化ログを出力&lt;/div&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">InitLogger</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;コンポーネントが画面に追加されました&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  }, []); </span><span style="color: #6A9955">// 依存配列空でマウント後のみ</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">初期化ログを出力</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li>依存配列を空にすると、初回レンダー後だけに限定</li>



<li>一度きりのサブスクリプションやログに最適</li>
</ul>



<h2 class="wp-block-heading">依存配列による再実行制御</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Fetcher({ userId }) {
  const [profile, setProfile] = useState(null);
  useEffect(() =&gt; {
    let active = true;
    fetch(`/api/users/${userId}`)
      .then(r =&gt; r.json())
      .then(data =&gt; {
        if (active) setProfile(data);
      });
    return () =&gt; { active = false }; // クリーンアップ
  }, [userId]); 
  // userIdが変わるたびに実行／前回のフェッチはキャンセル
  return profile ? &lt;UserCard data={profile} /&gt; : &lt;p&gt;読み込み中…&lt;/p&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Fetcher</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">userId</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">profile</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setProfile</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">active</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">`/api/users/</span><span style="color: #569CD6">${</span><span style="color: #9CDCFE">userId</span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">())</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">active</span><span style="color: #D4D4D4">) </span><span style="color: #DCDCAA">setProfile</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">      });</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">active</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4"> }; </span><span style="color: #6A9955">// クリーンアップ</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">userId</span><span style="color: #D4D4D4">]); </span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// userIdが変わるたびに実行／前回のフェッチはキャンセル</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">profile</span><span style="color: #D4D4D4"> ? </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">UserCard</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">profile</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4"> : </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">読み込み中…</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li>古いフェッチ結果を書き込まないための<strong>フラグ制御</strong></li>



<li>依存の追加漏れが無限ループや stale closure を招く</li>
</ul>



<h2 class="wp-block-heading">クリーンアップ関数の重要性</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Clock() {
  const [time, setTime] = useState(Date.now());
  useEffect(() =&gt; {
    const id = setInterval(() =&gt; setTime(Date.now()), 1000);
    return () =&gt; clearInterval(id); // アンマウント時に解除
  }, []);
  return &lt;div&gt;現在時刻：{new Date(time).toLocaleTimeString()}&lt;/div&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Clock</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">time</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setTime</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">Date</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">now</span><span style="color: #D4D4D4">());</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">id</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">setInterval</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setTime</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">Date</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">now</span><span style="color: #D4D4D4">()), </span><span style="color: #B5CEA8">1000</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">clearInterval</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4">); </span><span style="color: #6A9955">// アンマウント時に解除</span></span>
<span class="line"><span style="color: #D4D4D4">  }, []);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">現在時刻：</span><span style="color: #569CD6">{new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Date</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">time</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">toLocaleTimeString</span><span style="color: #D4D4D4">()</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li>クリーンアップがないと、メモリリークや不要なタイマーが残り続ける</li>



<li><strong>レンダーごと</strong>に再登録してしまう実装ミスにも注意</li>
</ul>



<h2 class="wp-block-heading"><code>useLayoutEffect</code>と<code>useEffect</code>の違い</h2>



<ul class="wp-block-list">
<li><strong><code>useEffect</code></strong>：レンダー後に非同期的に実行される</li>



<li><strong><code>useLayoutEffect</code></strong>：レンダー直後、ブラウザの描画前に同期的に実行される</li>
</ul>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useLayoutEffect, useRef } from 'react';

function MeasureBox() {
  const ref = useRef();
  useLayoutEffect(() =&gt; {
    console.log(ref.current.getBoundingClientRect());
  }, []);
  return &lt;div ref={ref}&gt;サイズを測定&lt;/div&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useLayoutEffect</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useRef</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">MeasureBox</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">ref</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useLayoutEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">log</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">ref</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">getBoundingClientRect</span><span style="color: #D4D4D4">());</span></span>
<span class="line"><span style="color: #D4D4D4">  }, []);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">div</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ref</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">ref</span><span style="color: #569CD6">}</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">サイズを測定</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">副次的レンダーとスケジューリング</h2>



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



<h2 class="wp-block-heading">パフォーマンス最適化パターン</h2>



<ol class="wp-block-list">
<li><strong>依存配列の最小化</strong><br>不要な変数を入れると再実行が多発。逆に入れ忘れると stale data の原因。</li>



<li><strong><code>useCallback</code> / <code>useMemo</code></strong><br>エフェクト中で関数や計算結果を依存させる場合はメモ化して参照の安定化を図る。</li>



<li><strong>サブスクリプション分離</strong><br>複数の副作用を一つの <code>useEffect</code> にまとめず、責務ごとに分けると、不要な再実行を抑えやすい。</li>
</ol>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="useEffect(() =&gt; { /* データ取得 */ }, [id]);
useEffect(() =&gt; { /* 購読登録 */ }, [socket]);" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #6A9955">/* データ取得 */</span><span style="color: #D4D4D4"> }, [</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #6A9955">/* 購読登録 */</span><span style="color: #D4D4D4"> }, [</span><span style="color: #9CDCFE">socket</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<h2 class="wp-block-heading">よくある落とし穴と対策</h2>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>症状</th><th>原因</th><th>対策</th></tr></thead><tbody><tr><td>無限ループ</td><td>依存配列に頻繁に変わる値が含まれる</td><td>必要最小限の依存に絞る</td></tr><tr><td>クリーンアップ漏れ</td><td>return を忘れた</td><td>購読/タイマーは必ず return で解除</td></tr><tr><td>stale closure（古い状態を参照）</td><td>クロージャ内で古い変数を直接参照</td><td>依存配列に必要な値をすべて含める、またはフラグ制御</td></tr><tr><td>レンダリング前の計測ずれ</td><td>useEffectでDOM計測</td><td>useLayoutEffectに移行</td></tr></tbody></table></div></figure>



<h2 class="wp-block-heading">実践例：データフェッチ×キャンセルパターン</h2>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useState, useEffect } from 'react';

function SearchResults({ query }) {
  const [results, setResults] = useState([]);
  useEffect(() =&gt; {
    const controller = new AbortController();
    fetch(`/api/search?q=${encodeURIComponent(query)}`, { signal: controller.signal })
      .then(r =&gt; r.json())
      .then(setResults)
      .catch(err =&gt; {
        if (err.name !== 'AbortError') console.error(err);
      });
    return () =&gt; controller.abort(); // クエリ変更時・アンマウント時に中断
  }, [query]);
  return (
    &lt;ul&gt;
      {results.map(r =&gt; &lt;li key={r.id}&gt;{r.title}&lt;/li&gt;)}
    &lt;/ul&gt;
  );
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">SearchResults</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">query</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">results</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setResults</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">([]);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">controller</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">new</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">AbortController</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">`/api/search?q=</span><span style="color: #569CD6">${</span><span style="color: #DCDCAA">encodeURIComponent</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">query</span><span style="color: #D4D4D4">)</span><span style="color: #569CD6">}</span><span style="color: #CE9178">`</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">signal:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">controller</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">signal</span><span style="color: #D4D4D4"> })</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">())</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">setResults</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">catch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">err</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">err</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4"> !== </span><span style="color: #CE9178">&#39;AbortError&#39;</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">error</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">err</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">      });</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">controller</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">abort</span><span style="color: #D4D4D4">(); </span><span style="color: #6A9955">// クエリ変更時・アンマウント時に中断</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">query</span><span style="color: #D4D4D4">]);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">ul</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">results</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">li</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">id</span><span style="color: #569CD6">}</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">title</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">li</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">)</span><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">ul</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">まとめ</h2>



<p>Reactの副作用ライフサイクルを正しく理解すると、UI更新と非同期処理、リソース管理がスムーズになります。<code>useEffect</code>と<code>useLayoutEffect</code>の使い分け、依存配列の設計、クリーンアップの徹底、パフォーマンス最適化パターンをマスターし、副作用バグから解放された堅牢なコンポーネントを開発しましょう。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-lifecycle-effects/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Reactコンポーネント徹底ガイド──基本から応用パターンまでわかりやすく解説</title>
		<link>https://techgrowup.net/react-component-basic/</link>
					<comments>https://techgrowup.net/react-component-basic/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sun, 27 Apr 2025 18:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Hooks]]></category>
		<category><![CDATA[Props]]></category>
		<category><![CDATA[State]]></category>
		<category><![CDATA[useEffect]]></category>
		<category><![CDATA[クラスコンポーネント]]></category>
		<category><![CDATA[コンポーネント]]></category>
		<category><![CDATA[関数コンポーネント]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2817</guid>

					<description><![CDATA[はじめに Reactを初めて学ぶ人が最初につまづきやすいのが「コンポーネント」の概念です。コンポーネントはUIを部品化し、再利用可能な塊として扱う手法。公式ドキュメント （https://react.dev/learn# [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">はじめに</h1>



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



<h2 class="wp-block-heading">コンポーネントとは何か</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="// Button.jsx
export function Button({ label, onClick }) {
  return &lt;button onClick={onClick}&gt;{label}&lt;/button&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #6A9955">// Button.jsx</span></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Button</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">onClick</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onClick</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">onClick</span><span style="color: #569CD6">}</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">label</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



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



<h2 class="wp-block-heading">関数コンポーネントの基礎</h2>



<h3 class="wp-block-heading">宣言方法</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Greeting({ name }) {
  return &lt;h1&gt;こんにちは、{name}さん！&lt;/h1&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Greeting</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">h1</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">こんにちは、</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">name</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4">さん！</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">h1</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



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



<h3 class="wp-block-heading">Propsの受け渡し</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="&lt;Greeting name=&quot;太郎&quot; /&gt;
&lt;Greeting name=&quot;花子&quot; /&gt;" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Greeting</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;太郎&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span></span>
<span class="line"><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Greeting</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;花子&quot;</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span></span></code></pre></div>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Profile({ avatarUrl, username, bio }) { /* … */ }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Profile</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">avatarUrl</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">username</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">bio</span><span style="color: #D4D4D4"> }) { </span><span style="color: #6A9955">/* … */</span><span style="color: #D4D4D4"> }</span></span></code></pre></div>



<h2 class="wp-block-heading">クラスコンポーネントとの違い</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import React from 'react';

class Greeting extends React.Component {
  render() {
    return &lt;h1&gt;こんにちは、{this.props.name}さん！&lt;/h1&gt;;
  }
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Greeting</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">extends</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">React</span><span style="color: #D4D4D4">.</span><span style="color: #4EC9B0">Component</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">render</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">h1</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">こんにちは、</span><span style="color: #569CD6">{this</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4">さん！</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">h1</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">  }</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>State管理</strong>：クラスでは <code>this.state</code> と <code>this.setState()</code></li>



<li><strong>ライフサイクル</strong>：<code>componentDidMount</code>, <code>componentDidUpdate</code>, <code>componentWillUnmount</code></li>
</ul>



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



<h2 class="wp-block-heading">Stateの扱い方</h2>



<h3 class="wp-block-heading">useStateフック</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);
  return (
    &lt;&gt;
      &lt;p&gt;現在のカウント: {count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(count + 1)}&gt;増やす&lt;/button&gt;
    &lt;/&gt;
  );
}
" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Counter</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">count</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setCount</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">現在のカウント: </span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">count</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">button</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onClick</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #D4D4D4">() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setCount</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">count</span><span style="color: #D4D4D4"> + </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">)</span><span style="color: #569CD6">}</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">増やす</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">button</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;/&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>初期値</strong>：<code>useState(0)</code> の <code>0</code> 部分</li>



<li><strong>更新関数</strong>：<code>setCount</code> を呼ぶと再レンダーが発生</li>
</ul>



<h3 class="wp-block-heading">Stateの注意点</h3>



<ul class="wp-block-list">
<li><strong>不変性</strong>：Stateオブジェクトを直接変更せず、必ず更新関数を使う</li>



<li><strong>遅延初期化</strong>：複雑な計算が必要な場合は関数を渡して一度だけ実行</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const [todos, setTodos] = useState(() =&gt; loadTodosFromLocalStorage());" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">todos</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setTodos</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">loadTodosFromLocalStorage</span><span style="color: #D4D4D4">());</span></span></code></pre></div>



<h2 class="wp-block-heading">Props vs State</h2>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>項目</th><th>Props</th><th>State</th></tr></thead><tbody><tr><td>定義場所</td><td>親コンポーネントが渡す</td><td>自身のコンポーネント内で定義</td></tr><tr><td>役割</td><td>親からの読み取り専用の値</td><td>パーツ内部で変化する値</td></tr><tr><td>更新</td><td>親が再レンダーされる必要あり</td><td>更新関数を呼ぶだけで自動的に再レンダー</td></tr></tbody></table></div></figure>



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



<h2 class="wp-block-heading">コンポーネントの合成（Composition）</h2>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Card({ children }) {
  return &lt;div className=&quot;card&quot;&gt;{children}&lt;/div&gt;;
}

function App() {
  return (
    &lt;Card&gt;
      &lt;h2&gt;タイトル&lt;/h2&gt;
      &lt;p&gt;内容テキスト&lt;/p&gt;
    &lt;/Card&gt;
  );
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Card</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">children</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">div</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">className</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;card&quot;</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">children</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">div</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">App</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> (</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Card</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">h2</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">タイトル</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">h2</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">内容テキスト</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;/</span><span style="color: #4EC9B0">Card</span><span style="color: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">  );</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>children</strong>：<code>&lt;Card>…&lt;/Card></code> の中身を受け取る特殊Props</li>



<li><strong>再利用性</strong>：見た目のフレームと中身を分離できるため、多用途に使える</li>
</ul>



<h2 class="wp-block-heading">ライフサイクルとuseEffect</h2>



<h3 class="wp-block-heading">useEffectの基本</h3>



<p>副作用（データフェッチ、購読、手動DOM操作など）を扱う際に使うフック。</p>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { useEffect, useState } from 'react';

function DataFetcher({ url }) {
  const [data, setData] = useState(null);
  useEffect(() =&gt; {
    fetch(url)
      .then(r =&gt; r.json())
      .then(json =&gt; setData(json));
  }, [url]); // urlが変わったときだけ再実行
  return data ? &lt;pre&gt;{JSON.stringify(data)}&lt;/pre&gt; : &lt;p&gt;読み込み中…&lt;/p&gt;;
}" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useEffect</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useState</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">DataFetcher</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">data</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setData</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">fetch</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">r</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">json</span><span style="color: #D4D4D4">())</span></span>
<span class="line"><span style="color: #D4D4D4">      .</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">json</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setData</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">json</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">url</span><span style="color: #D4D4D4">]); </span><span style="color: #6A9955">// urlが変わったときだけ再実行</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> ? </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">pre</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #4FC1FF">JSON</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">stringify</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">)</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">pre</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4"> : </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">読み込み中…</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">p</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>第2引数</strong>：依存配列。空配列 <code>[]</code> ならマウント時のみ実行</li>



<li><strong>クリーンアップ</strong>：関数を返すとアンマウント時に実行</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="useEffect(() =&gt; {
  const id = setInterval(tick, 1000);
  return () =&gt; clearInterval(id);
}, []);" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">id</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">setInterval</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">tick</span><span style="color: #D4D4D4">, </span><span style="color: #B5CEA8">1000</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">clearInterval</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">}, []);</span></span></code></pre></div>



<h3 class="wp-block-heading">クラスのライフサイクル対応</h3>



<ul class="wp-block-list">
<li><code>componentDidMount</code> → <code>useEffect(() => {...}, [])</code></li>



<li><code>componentDidUpdate</code> → <code>useEffect(() => {...}, [deps])</code></li>



<li><code>componentWillUnmount</code> → <code>useEffect(() => { return cleanup }, [])</code></li>
</ul>



<h2 class="wp-block-heading">パフォーマンス最適化</h2>



<h3 class="wp-block-heading">React.memo</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const ExpensiveComponent = React.memo(function({ value }) {
  // 重い計算...
  return &lt;span&gt;{value}&lt;/span&gt;;
});" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">ExpensiveComponent</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">memo</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">function</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #6A9955">// 重い計算...</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">span</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">value</span><span style="color: #569CD6">}</span><span style="color: #808080">&lt;/</span><span style="color: #569CD6">span</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">});</span></span></code></pre></div>



<h3 class="wp-block-heading">useCallback / useMemo</h3>



<ul class="wp-block-list">
<li><strong>useCallback</strong>：コールバック関数をメモ化</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const handleClick = useCallback(() =&gt; { /* ... */ }, [deps]);" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">handleClick</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #6A9955">/* ... */</span><span style="color: #D4D4D4"> }, [</span><span style="color: #9CDCFE">deps</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>useMemo</strong>：計算結果をメモ化</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="const result = useMemo(() =&gt; expensiveCalculation(a, b), [a, b]);" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">result</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">expensiveCalculation</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">a</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">b</span><span style="color: #D4D4D4">), [</span><span style="color: #9CDCFE">a</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">b</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<h3 class="wp-block-heading">Virtualized List</h3>



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



<h2 class="wp-block-heading">コンポーネント設計パターン</h2>



<h3 class="wp-block-heading">Container / Presentational</h3>



<ul class="wp-block-list">
<li><strong>Container</strong>：データ取得やState管理を担当</li>



<li><strong>Presentational</strong>：UI描画のみを担当</li>
</ul>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function UserContainer() {
  const [user, setUser] = useState(null);
  useEffect(() =&gt; fetchUser().then(setUser), []);
  return user ? &lt;UserProfile data={user} /&gt; : &lt;Loading /&gt;;
}
function UserProfile({ data }) { /* UIのみ */ }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">UserContainer</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">user</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setUser</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">fetchUser</span><span style="color: #D4D4D4">().</span><span style="color: #DCDCAA">then</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">setUser</span><span style="color: #D4D4D4">), []);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">user</span><span style="color: #D4D4D4"> ? </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">UserProfile</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">user</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4"> : </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Loading</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">UserProfile</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">data</span><span style="color: #D4D4D4"> }) { </span><span style="color: #6A9955">/* UIのみ */</span><span style="color: #D4D4D4"> }</span></span></code></pre></div>



<h3 class="wp-block-heading">Compound Components</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="function Tabs({ children }) {
  const [active, setActive] = useState(0);
  return /* contextでactiveとsetActiveを渡す */;
}
Tabs.List = function({ children }) { /* タブ見出し */ };
Tabs.Panel = function({ index, children }) { /* コンテンツ */ };" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Tabs</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">children</span><span style="color: #D4D4D4"> }) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [</span><span style="color: #4FC1FF">active</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setActive</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #6A9955">/* contextでactiveとsetActiveを渡す */</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"><span style="color: #9CDCFE">Tabs</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">List</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">children</span><span style="color: #D4D4D4"> }) { </span><span style="color: #6A9955">/* タブ見出し */</span><span style="color: #D4D4D4"> };</span></span>
<span class="line"><span style="color: #9CDCFE">Tabs</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">Panel</span><span style="color: #D4D4D4"> = </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">index</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">children</span><span style="color: #D4D4D4"> }) { </span><span style="color: #6A9955">/* コンテンツ */</span><span style="color: #D4D4D4"> };</span></span></code></pre></div>



<h2 class="wp-block-heading">テストと型安全</h2>



<h3 class="wp-block-heading">テスト例（React Testing Library）</h3>



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';
test('クリックで増加', () =&gt; {
  const { getByText } = render(&lt;Counter /&gt;);
  const btn = getByText(/増やす/);
  fireEvent.click(btn);
  expect(getByText(/現在のカウント: 1/)).toBeInTheDocument();
});" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">render</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">fireEvent</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;@testing-library/react&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">Counter</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;./Counter&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #DCDCAA">test</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;クリックで増加&#39;</span><span style="color: #D4D4D4">, () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> { </span><span style="color: #4FC1FF">getByText</span><span style="color: #D4D4D4"> } = </span><span style="color: #DCDCAA">render</span><span style="color: #D4D4D4">(</span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Counter</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">btn</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">getByText</span><span style="color: #D4D4D4">(</span><span style="color: #D16969">/増やす/</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">fireEvent</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">click</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">btn</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">expect</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">getByText</span><span style="color: #D4D4D4">(</span><span style="color: #D16969">/現在のカウント: 1/</span><span style="color: #D4D4D4">)).</span><span style="color: #DCDCAA">toBeInTheDocument</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">});</span></span></code></pre></div>



<h3 class="wp-block-heading">TypeScriptとの併用</h3>



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



<div class="wp-block-kevinbatdorf-code-block-pro" data-code-block-pro-font-family="Code-Pro-JetBrains-Mono" style="font-size:.875rem;font-family:Code-Pro-JetBrains-Mono,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;line-height:1.25rem;--cbp-tab-width:2;tab-size:var(--cbp-tab-width, 2)"><span style="display:block;padding:16px 0 0 16px;margin-bottom:-1px;width:100%;text-align:left;background-color:#1E1E1E"><svg xmlns="http://www.w3.org/2000/svg" width="54" height="14" viewBox="0 0 54 14"><g fill="none" fill-rule="evenodd" transform="translate(1 1)"><circle cx="6" cy="6" r="6" fill="#FF5F56" stroke="#E0443E" stroke-width=".5"></circle><circle cx="26" cy="6" r="6" fill="#FFBD2E" stroke="#DEA123" stroke-width=".5"></circle><circle cx="46" cy="6" r="6" fill="#27C93F" stroke="#1AAB29" stroke-width=".5"></circle></g></svg></span><span role="button" tabindex="0" data-code="interface ButtonProps { label: string; onClick: () =&gt; void; }
export function Button({ label, onClick }: ButtonProps) { /* ... */ }" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><svg xmlns="http://www.w3.org/2000/svg" style="width:24px;height:24px" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path class="with-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4"></path><path class="without-check" stroke-linecap="round" stroke-linejoin="round" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2"></path></svg></span><pre class="shiki dark-plus" style="background-color: #1E1E1E" tabindex="0"><code><span class="line"><span style="color: #569CD6">interface</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">ButtonProps</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">string</span><span style="color: #D4D4D4">; </span><span style="color: #DCDCAA">onClick</span><span style="color: #D4D4D4">: () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">void</span><span style="color: #D4D4D4">; }</span></span>
<span class="line"><span style="color: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Button</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">label</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">onClick</span><span style="color: #D4D4D4"> }: </span><span style="color: #4EC9B0">ButtonProps</span><span style="color: #D4D4D4">) { </span><span style="color: #6A9955">/* ... */</span><span style="color: #D4D4D4"> }</span></span></code></pre></div>



<h2 class="wp-block-heading">まとめ</h2>



<p>Reactコンポーネントは「UIを部品化して組み合わせる」ための基本単位です。関数コンポーネントとクラスコンポーネントの違い、PropsとState、Hooks、コンポジション、最適化パターンをしっかり理解すれば、堅牢で再利用性の高いUIを効率的に開発できます。公式ドキュメントを自分のプロジェクトに落とし込む際には、ここで解説した多彩なパターンをぜひ試してみてください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-component-basic/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Disk: Enhanced  を使用したページ キャッシュ

Served from: techgrowup.net @ 2026-04-09 13:38:20 by W3 Total Cache
-->