<?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>【TechGrowUp】</title>
	<atom:link href="https://techgrowup.net/feed/" rel="self" type="application/rss+xml" />
	<link>https://techgrowup.net</link>
	<description>エンジニアを強くする</description>
	<lastBuildDate>Sun, 31 May 2026 14:19:42 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://techgrowup.net/wp-content/uploads/2021/05/hp-icon-150x150.png</url>
	<title>【TechGrowUp】</title>
	<link>https://techgrowup.net</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>FlutterでGoogle Places APIが403になる原因と対策｜アプリ制限付きAPIキーはSDK経由が安全</title>
		<link>https://techgrowup.net/flutter-google-places-api-403-app-restriction/</link>
					<comments>https://techgrowup.net/flutter-google-places-api-403-app-restriction/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sun, 31 May 2026 14:19:41 +0000</pubDate>
				<category><![CDATA[エラー集]]></category>
		<category><![CDATA[403エラー]]></category>
		<category><![CDATA[Androidアプリ開発]]></category>
		<category><![CDATA[APIキー制限]]></category>
		<category><![CDATA[Flutter]]></category>
		<category><![CDATA[flutter_google_places_sdk]]></category>
		<category><![CDATA[Google Maps Platform]]></category>
		<category><![CDATA[Google Places API]]></category>
		<category><![CDATA[iOSアプリ開発]]></category>
		<category><![CDATA[MethodChannel]]></category>
		<category><![CDATA[Places SDK]]></category>
		<category><![CDATA[REQUEST_DENIED]]></category>
		<category><![CDATA[旅行アプリ]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2923</guid>

					<description><![CDATA[はじめに FlutterアプリでGoogle Places APIを使って、目的地検索や場所候補の補完を実装しようとしたとき、意外とハマりやすいのが APIキー制限 です。 特に、Google Cloud Console [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">はじめに</h2>



<p class="wp-block-paragraph">FlutterアプリでGoogle Places APIを使って、目的地検索や場所候補の補完を実装しようとしたとき、意外とハマりやすいのが <strong>APIキー制限</strong> です。</p>



<p class="wp-block-paragraph">特に、Google Cloud ConsoleでAPIキーに <strong>Androidアプリ制限</strong> や <strong>iOSアプリ制限</strong> を設定したうえで、FlutterのDartコードからPlaces APIをHTTPで直接呼び出すと、<code>403</code> や <code>REQUEST_DENIED</code> が返ってくることがあります。</p>



<p class="wp-block-paragraph">一見すると「FlutterでGoogle Places APIが使えないの？」と思ってしまいますが、原因はFlutterそのものではありません。結論から言うと、<strong>Places APIのWeb Serviceを直接叩く場合、Android/iOSアプリ制限付きAPIキーは基本的に使えません。</strong></p>



<p class="wp-block-paragraph">モバイルアプリでAndroid/iOSアプリ制限付きAPIキーを使いたい場合は、Androidなら <strong>Places SDK for Android</strong>、iOSなら <strong>Places SDK for iOS</strong> を使う必要があります。</p>



<p class="wp-block-paragraph">この記事では、FlutterでGoogle Places APIを使うときに403が返る理由と、Flutterアプリで安全にPlaces検索を実装する方法を整理します。</p>



<p class="wp-block-paragraph">また、筆者が開発している旅行準備アプリ <strong>「旅じたく」</strong> でも、目的地検索や場所候補の入力体験を改善するためにGoogle Places APIの活用を検討しています。</p>



<p class="wp-block-paragraph">この記事は単なる一般論ではなく、<strong>旅じたくの目的地検索機能を改善するための技術検証</strong> として、FlutterでGoogle Places APIをどう扱うべきかを整理したものです。</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>旅じたくについて</strong><br>「旅じたく」は、旅行前の準備をひとつにまとめられる旅行準備アプリです。持ち物、予定、予約メモ、予算、行きたい場所などを旅行ごとに整理できます。<br>今回扱うGoogle Places APIは、今後「目的地入力」や「行きたい場所」の入力体験を改善するための技術検証として位置づけています。</p>
</blockquote>


<a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6770222625?ct=tabijitaku_lp&#038;mt=8" title="旅じたくアプリ - App Store" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img fetchpriority="high" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/61806b46d335cd0616f022cd1a772eeb.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">旅じたくアプリ &#8211; App Store</div><div class="blogcard-snippet external-blogcard-snippet">NINJA SYSTEMの「旅じたく」をApp Storeでダウンロードしてください。スクリーンショット、評価とレビュー、ユーザのヒント、「旅じたく」に似たゲームを見ることなどができます。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://apps.apple.com/jp/app/%E6%97%85%E3%81%98%E3%81%9F%E3%81%8F/id6770222625" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">apps.apple.com</div></div></div></div></a>

<a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.tripmemo&#038;utm_source=ninja_site&#038;utm_medium=lp&#038;utm_campaign=tabijitaku_lp&#038;hl=ja&#038;gl=JP" title="旅じたく - 旅行準備・持ち物 - Google Play のアプリ" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/a5231a8c768556a5b9ab58f8b5ee03db.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">旅じたく &#8211; 旅行準備・持ち物 &#8211; Google Play のアプリ</div><div class="blogcard-snippet external-blogcard-snippet">旅行前の持ち物・予定・予算・予約をまとめて整理</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://play.google.com/store/apps/details?id=jp.ninja.tripmemo&#038;hl=ja" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">play.google.com</div></div></div></div></a>


<h2 class="wp-block-heading">この記事でわかること</h2>



<p class="wp-block-paragraph">この記事では、以下の内容を解説します。</p>



<ul class="wp-block-list">
<li>FlutterからPlaces APIを直接叩くと403になる理由</li>



<li>Android/iOSアプリ制限付きAPIキーが使えるケース・使えないケース</li>



<li>FlutterでPlaces SDKを使うための選択肢</li>



<li><code>flutter_google_places_sdk</code> を使うサンプル</li>



<li>MethodChannelでネイティブSDKを呼び出す実装イメージ</li>



<li>旅行アプリ「旅じたく」での活用イメージ</li>



<li>個人開発アプリでどの方式を選ぶべきか</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">前提：Google Places APIには複数の使い方がある</h2>



<p class="wp-block-paragraph">Google Places APIと一口に言っても、実際にはいくつかの利用方法があります。</p>



<p class="wp-block-paragraph">代表的には以下です。</p>



<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>Places API / Places API (New) Web Service</td><td>サーバー・Web Service</td><td>HTTPSリクエスト</td></tr><tr><td>Places SDK for Android</td><td>Androidアプリ</td><td>AndroidネイティブSDK</td></tr><tr><td>Places SDK for iOS</td><td>iOSアプリ</td><td>iOSネイティブSDK</td></tr><tr><td>Places Library, Maps JavaScript API</td><td>Web</td><td>JavaScript</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">Flutterアプリの場合、ついDartの<code>http</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>final response = await http.get(
  Uri.parse('https://places.googleapis.com/v1/places:searchText?...'),
);
</textarea></pre><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">final</span><span style="color: #D4D4D4"> response = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> http.</span><span style="color: #DCDCAA">get</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Uri</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">parse</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;https://places.googleapis.com/v1/places:searchText?...&#39;</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">);</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">しかし、この呼び方は <strong>Places APIのWeb Serviceを直接叩いている</strong> 形です。</p>



<p class="wp-block-paragraph">つまり、Androidアプリ上で動いていたとしても、Google側から見ると「Android SDK経由の呼び出し」ではなく、ただのHTTPSリクエストです。</p>



<p class="wp-block-paragraph">ここが今回の落とし穴です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">なぜ403になるのか</h2>



<p class="wp-block-paragraph">Google Cloud Consoleでは、APIキーに対して以下のようなアプリケーション制限を設定できます。</p>



<ul class="wp-block-list">
<li>HTTPリファラー制限</li>



<li>IPアドレス制限</li>



<li>Androidアプリ制限</li>



<li>iOSアプリ制限</li>
</ul>



<p class="wp-block-paragraph">Androidアプリ制限の場合は、パッケージ名と署名証明書のSHA-1フィンガープリントを設定します。</p>



<p class="wp-block-paragraph">iOSアプリ制限の場合は、Bundle IDを設定します。</p>



<p class="wp-block-paragraph">一見すると、FlutterアプリでもAndroid/iOSアプリとして動いているので、この制限が使えそうに見えます。</p>



<p class="wp-block-paragraph">しかし、Places APIのWeb ServiceをDartのHTTP通信で直接呼び出した場合、そのリクエストはネイティブSDK経由ではありません。</p>



<p class="wp-block-paragraph">そのため、Android/iOSアプリ制限付きAPIキーを使っていると、Google側でそのキーを正しく検証できず、<code>403</code> や <code>REQUEST_DENIED</code> になります。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">NG例：FlutterからREST APIを直接呼び出す</h2>



<p class="wp-block-paragraph">例えば、以下のようなコードです。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import 'dart:convert';
import 'package:http/http.dart' as http;

class BadPlacesApiClient {
  BadPlacesApiClient(this.apiKey);

  final String apiKey;

  Future&lt;void> searchText(String query) async {
    final uri = Uri.https(
      'places.googleapis.com',
      '/v1/places:searchText',
    );

    final response = await http.post(
      uri,
      headers: {
        'Content-Type': 'application/json',
        'X-Goog-Api-Key': apiKey,
        'X-Goog-FieldMask': 'places.id,places.displayName,places.formattedAddress',
      },
      body: jsonEncode({
        'textQuery': query,
        'languageCode': 'ja',
        'regionCode': 'JP',
      }),
    );

    if (response.statusCode == 403) {
      throw Exception('403 Forbidden: APIキー制限により拒否されている可能性があります');
    }

    print(response.body);
  }
}
</textarea></pre><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">import</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;dart:convert&#39;</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;package:http/http.dart&#39;</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">as</span><span style="color: #D4D4D4"> http;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">BadPlacesApiClient</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">BadPlacesApiClient</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.apiKey);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> apiKey;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Future</span><span style="color: #D4D4D4">&lt;</span><span style="color: #569CD6">void</span><span style="color: #D4D4D4">&gt; </span><span style="color: #DCDCAA">searchText</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> query) </span><span style="color: #C586C0">async</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> uri = </span><span style="color: #4EC9B0">Uri</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">https</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #CE9178">&#39;places.googleapis.com&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #CE9178">&#39;/v1/places:searchText&#39;</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">final</span><span style="color: #D4D4D4"> response = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> http.</span><span style="color: #DCDCAA">post</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      uri,</span></span>
<span class="line"><span style="color: #D4D4D4">      headers: {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;Content-Type&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&#39;application/json&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;X-Goog-Api-Key&#39;</span><span style="color: #D4D4D4">: apiKey,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;X-Goog-FieldMask&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&#39;places.id,places.displayName,places.formattedAddress&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">      },</span></span>
<span class="line"><span style="color: #D4D4D4">      body: </span><span style="color: #DCDCAA">jsonEncode</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;textQuery&#39;</span><span style="color: #D4D4D4">: query,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;languageCode&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&#39;ja&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;regionCode&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&#39;JP&#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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (response.statusCode == </span><span style="color: #B5CEA8">403</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">throw</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Exception</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;403 Forbidden: APIキー制限により拒否されている可能性があります&#39;</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: #DCDCAA">print</span><span style="color: #D4D4D4">(response.body);</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>



<p class="wp-block-paragraph">このコード自体が必ず悪いわけではありません。</p>



<p class="wp-block-paragraph">ただし、<strong>Android/iOSアプリ制限付きAPIキーを使う構成とは相性が悪い</strong> です。</p>



<p class="wp-block-paragraph">REST APIを直接使うなら、基本的にはサーバー側から呼び出す構成にするのが安全です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">FlutterでGoogle Placesを使う選択肢</h2>



<p class="wp-block-paragraph">FlutterでPlaces検索を実装する場合、現実的な選択肢は大きく4つあります。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>方式</th><th>概要</th><th>メリット</th><th>デメリット</th></tr></thead><tbody><tr><td>DartからREST直叩き</td><td>Flutter内でHTTP通信</td><td>実装が簡単</td><td>アプリ制限キーが使えない。キー漏洩リスクが高い</td></tr><tr><td>Flutterプラグイン利用</td><td>ネイティブSDKをラップしたプラグインを使う</td><td>実装が比較的簡単。アプリ制限キーを使いやすい</td><td>非公式プラグインの場合、保守状況に依存</td></tr><tr><td>MethodChannel自作</td><td>Android/iOSのPlaces SDKを自前で呼ぶ</td><td>制御しやすい。長期運用向き</td><td>Android/iOS両方の実装が必要</td></tr><tr><td>Backend Proxy</td><td>Cloudflare WorkersやFirebase Functions等で中継</td><td>APIキーをアプリに含めなくてよい</td><td>サーバー実装・運用が必要</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">個人開発アプリでは、まず <strong>既存のFlutterプラグインを検証</strong> し、要件に合わなければ <strong>MethodChannel自作</strong>、サーバー側の機能が増えてきたら <strong>Backend Proxy</strong> を検討する流れが現実的です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">方法1：flutter_google_places_sdkを使う</h2>



<p class="wp-block-paragraph">Flutter向けにGoogle公式のPlaces SDKがあるわけではありませんが、ネイティブSDKをラップしたFlutterプラグインはいくつか存在します。</p>



<p class="wp-block-paragraph">その一つが <code>flutter_google_places_sdk</code> です。</p>



<p class="wp-block-paragraph">このプラグインは、Android/iOSそれぞれのネイティブライブラリを利用する方針のため、単純なHTTPリクエスト方式よりもアプリ制限付きAPIキーと相性が良いです。</p>



<h3 class="wp-block-heading">pubspec.yaml</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>dependencies:
  flutter:
    sdk: flutter
  flutter_google_places_sdk: ^0.4.3
</textarea></pre><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">dependencies</span><span style="color: #D4D4D4">:</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">flutter</span><span style="color: #D4D4D4">:</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">sdk</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">flutter</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">flutter_google_places_sdk</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">^0.4.3</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">※バージョンは例です。実際に導入する際はpub.devで最新バージョンと変更履歴を確認してください。</p>



<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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class PlaceSuggestion {
  const PlaceSuggestion({
    required this.placeId,
    required this.primaryText,
    required this.secondaryText,
  });

  final String placeId;
  final String primaryText;
  final String secondaryText;
}
</textarea></pre><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">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">PlaceSuggestion</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: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.primaryText,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.secondaryText,</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">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> placeId;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> primaryText;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> secondaryText;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<h3 class="wp-block-heading">Places検索サービス</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import 'package:flutter_google_places_sdk/flutter_google_places_sdk.dart';

class PlacesSearchService {
  PlacesSearchService({required String apiKey})
      : _places = FlutterGooglePlacesSdk(apiKey);

  final FlutterGooglePlacesSdk _places;

  Future&lt;List&lt;PlaceSuggestion>> search(String input) async {
    if (input.trim().isEmpty) {
      return const [];
    }

    final result = await _places.findAutocompletePredictions(
      input,
      countries: const &#91;'JP'&#93;,
    );

    return result.predictions.map((prediction) {
      return PlaceSuggestion(
        placeId: prediction.placeId,
        primaryText: prediction.primaryText,
        secondaryText: prediction.secondaryText,
      );
    }).toList();
  }
}
</textarea></pre><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">import</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;package:flutter_google_places_sdk/flutter_google_places_sdk.dart&#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">PlacesSearchService</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">PlacesSearchService</span><span style="color: #D4D4D4">({</span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> apiKey})</span></span>
<span class="line"><span style="color: #D4D4D4">      : _places = </span><span style="color: #4EC9B0">FlutterGooglePlacesSdk</span><span style="color: #D4D4D4">(apiKey);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">FlutterGooglePlacesSdk</span><span style="color: #D4D4D4"> _places;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Future</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">List</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">&gt;&gt; </span><span style="color: #DCDCAA">search</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> input) </span><span style="color: #C586C0">async</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"> (input.</span><span style="color: #DCDCAA">trim</span><span style="color: #D4D4D4">().isEmpty) {</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">const</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">final</span><span style="color: #D4D4D4"> result = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> _places.</span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      input,</span></span>
<span class="line"><span style="color: #D4D4D4">      countries: </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> &#91;</span><span style="color: #CE9178">&#39;JP&#39;</span><span style="color: #D4D4D4">&#93;,</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"> result.predictions.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">((prediction) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        placeId: prediction.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">        primaryText: prediction.primaryText,</span></span>
<span class="line"><span style="color: #D4D4D4">        secondaryText: prediction.secondaryText,</span></span>
<span class="line"><span style="color: #D4D4D4">      );</span></span>
<span class="line"><span style="color: #D4D4D4">    }).</span><span style="color: #DCDCAA">toList</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></code></pre></div>



<h3 class="wp-block-heading">UI側の例</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import 'package:flutter/material.dart';

class DestinationSearchField extends StatefulWidget {
  const DestinationSearchField({super.key, required this.service});

  final PlacesSearchService service;

  @override
  State&lt;DestinationSearchField> createState() => _DestinationSearchFieldState();
}

class _DestinationSearchFieldState extends State&lt;DestinationSearchField> {
  final _controller = TextEditingController();
  List&lt;PlaceSuggestion> _suggestions = const [];
  bool _loading = false;

  Future&lt;void> _onChanged(String value) async {
    setState(() => _loading = true);

    try {
      final suggestions = await widget.service.search(value);
      if (!mounted) return;
      setState(() => _suggestions = suggestions);
    } catch (e) {
      if (!mounted) return;
      setState(() => _suggestions = const []);
      debugPrint('Places search failed: $e');
    } finally {
      if (mounted) {
        setState(() => _loading = false);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        TextField(
          controller: _controller,
          decoration: InputDecoration(
            labelText: '目的地',
            hintText: '例：札幌、京都駅、東京タワー',
            suffixIcon: _loading
                ? const Padding(
                    padding: EdgeInsets.all(12),
                    child: SizedBox(
                      width: 16,
                      height: 16,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    ),
                  )
                : null,
          ),
          onChanged: _onChanged,
        ),
        const SizedBox(height: 8),
        ..._suggestions.map(
          (suggestion) => ListTile(
            title: Text(suggestion.primaryText),
            subtitle: Text(suggestion.secondaryText),
            onTap: () {
              _controller.text = suggestion.primaryText;
              setState(() => _suggestions = const []);

              // ここでplaceIdを保存しておくと、あとから詳細情報を取得しやすい
              debugPrint('Selected placeId: ${suggestion.placeId}');
            },
          ),
        ),
      ],
    );
  }
}
</textarea></pre><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">import</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;package:flutter/material.dart&#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">DestinationSearchField</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">extends</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">StatefulWidget</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: #4EC9B0">DestinationSearchField</span><span style="color: #D4D4D4">({</span><span style="color: #569CD6">super</span><span style="color: #D4D4D4">.key, </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.service});</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">PlacesSearchService</span><span style="color: #D4D4D4"> service;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">@override</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">State</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">DestinationSearchField</span><span style="color: #D4D4D4">&gt; </span><span style="color: #DCDCAA">createState</span><span style="color: #D4D4D4">() =&gt; </span><span style="color: #4EC9B0">_DestinationSearchFieldState</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">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">_DestinationSearchFieldState</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">extends</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">State</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">DestinationSearchField</span><span style="color: #D4D4D4">&gt; {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> _controller = </span><span style="color: #4EC9B0">TextEditingController</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">List</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">&gt; _suggestions = </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> [];</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">bool</span><span style="color: #D4D4D4"> _loading = </span><span style="color: #569CD6">false</span><span style="color: #D4D4D4">;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Future</span><span style="color: #D4D4D4">&lt;</span><span style="color: #569CD6">void</span><span style="color: #D4D4D4">&gt; </span><span style="color: #DCDCAA">_onChanged</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> value) </span><span style="color: #C586C0">async</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(() =&gt; _loading = </span><span style="color: #569CD6">true</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></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">final</span><span style="color: #D4D4D4"> suggestions = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> widget.service.</span><span style="color: #DCDCAA">search</span><span style="color: #D4D4D4">(value);</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!mounted) </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(() =&gt; _suggestions = suggestions);</span></span>
<span class="line"><span style="color: #D4D4D4">    } </span><span style="color: #C586C0">catch</span><span style="color: #D4D4D4"> (e) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!mounted) </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(() =&gt; _suggestions = </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> []);</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">debugPrint</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;Places search failed: $</span><span style="color: #9CDCFE">e</span><span style="color: #CE9178">&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    } </span><span style="color: #C586C0">finally</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"> (mounted) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(() =&gt; _loading = </span><span style="color: #569CD6">false</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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">@override</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Widget</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">build</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">BuildContext</span><span style="color: #D4D4D4"> context) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Column</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      crossAxisAlignment: </span><span style="color: #4EC9B0">CrossAxisAlignment</span><span style="color: #D4D4D4">.start,</span></span>
<span class="line"><span style="color: #D4D4D4">      children: [</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #4EC9B0">TextField</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">          controller: _controller,</span></span>
<span class="line"><span style="color: #D4D4D4">          decoration: </span><span style="color: #4EC9B0">InputDecoration</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">            labelText: </span><span style="color: #CE9178">&#39;目的地&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">            hintText: </span><span style="color: #CE9178">&#39;例：札幌、京都駅、東京タワー&#39;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">            suffixIcon: _loading</span></span>
<span class="line"><span style="color: #D4D4D4">                ? </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Padding</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">                    padding: </span><span style="color: #4EC9B0">EdgeInsets</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">all</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">12</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">                    child: </span><span style="color: #4EC9B0">SizedBox</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">                      width: </span><span style="color: #B5CEA8">16</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                      height: </span><span style="color: #B5CEA8">16</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                      child: </span><span style="color: #4EC9B0">CircularProgressIndicator</span><span style="color: #D4D4D4">(strokeWidth: </span><span style="color: #B5CEA8">2</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 style="color: #569CD6">null</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">          ),</span></span>
<span class="line"><span style="color: #D4D4D4">          onChanged: _onChanged,</span></span>
<span class="line"><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: #4EC9B0">SizedBox</span><span style="color: #D4D4D4">(height: </span><span style="color: #B5CEA8">8</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">        ..._suggestions.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">          (suggestion) =&gt; </span><span style="color: #4EC9B0">ListTile</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">            title: </span><span style="color: #4EC9B0">Text</span><span style="color: #D4D4D4">(suggestion.primaryText),</span></span>
<span class="line"><span style="color: #D4D4D4">            subtitle: </span><span style="color: #4EC9B0">Text</span><span style="color: #D4D4D4">(suggestion.secondaryText),</span></span>
<span class="line"><span style="color: #D4D4D4">            onTap: () {</span></span>
<span class="line"><span style="color: #D4D4D4">              _controller.text = suggestion.primaryText;</span></span>
<span class="line"><span style="color: #D4D4D4">              </span><span style="color: #DCDCAA">setState</span><span style="color: #D4D4D4">(() =&gt; _suggestions = </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> []);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">              </span><span style="color: #6A9955">// ここでplaceIdを保存しておくと、あとから詳細情報を取得しやすい</span></span>
<span class="line"><span style="color: #D4D4D4">              </span><span style="color: #DCDCAA">debugPrint</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;Selected placeId: ${</span><span style="color: #9CDCFE">suggestion</span><span style="color: #CE9178">.</span><span style="color: #9CDCFE">placeId</span><span style="color: #CE9178">}&#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>
<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></code></pre></div>



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



<p class="wp-block-paragraph">この方法は実装が簡単ですが、プラグインはGoogle公式ではないため、以下は確認しておくべきです。</p>



<ul class="wp-block-list">
<li>メンテナンス頻度</li>



<li>Android/iOSの対応状況</li>



<li>Places API (New) への対応状況</li>



<li>必要な機能が揃っているか</li>



<li>ライセンス</li>



<li>FlutterやネイティブSDKのバージョン追従</li>
</ul>



<p class="wp-block-paragraph">個人開発のMVPや小規模アプリでは十分現実的ですが、長期的に細かい制御をしたい場合はMethodChannel自作も候補になります。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">方法2：MethodChannelでネイティブSDKを呼ぶ</h2>



<p class="wp-block-paragraph">既存プラグインに依存したくない場合は、FlutterからMethodChannelでAndroid/iOSのPlaces SDKを呼び出す方法があります。</p>



<p class="wp-block-paragraph">構成は以下のようになります。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Flutter
  ↓ MethodChannel
Android: Places SDK for Android
iOS: Places SDK for iOS
  ↓
Google Places</textarea></pre><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: #D4D4D4">Flutter</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓ MethodChannel</span></span>
<span class="line"><span style="color: #D4D4D4">Android: Places SDK for Android</span></span>
<span class="line"><span style="color: #D4D4D4">iOS: Places SDK for iOS</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓</span></span>
<span class="line"><span style="color: #D4D4D4">Google Places</span></span></code></pre></div>



<p class="wp-block-paragraph">Flutter側は共通のインターフェースを持ち、Android/iOS側でそれぞれネイティブSDKを実装します。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Flutter側：MethodChannelクライアント</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import 'package:flutter/services.dart';

class NativePlacesService {
  static const _channel = MethodChannel('jp.ninja.tripmemo/places');

  Future&lt;List&lt;PlaceSuggestion>> findAutocompletePredictions(String query) async {
    if (query.trim().isEmpty) {
      return const [];
    }

    final result = await _channel.invokeMethod&lt;List&lt;dynamic>>(
      'findAutocompletePredictions',
      {
        'query': query,
        'country': 'JP',
      },
    );

    if (result == null) {
      return const [];
    }

    return result.map((item) {
      final map = Map&lt;String, dynamic>.from(item as Map);
      return PlaceSuggestion(
        placeId: map&#91;'placeId'&#93; as String? ?? '',
        primaryText: map&#91;'primaryText'&#93; as String? ?? '',
        secondaryText: map&#91;'secondaryText'&#93; as String? ?? '',
      );
    }).toList();
  }
}
</textarea></pre><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">import</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;package:flutter/services.dart&#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">NativePlacesService</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">static</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> _channel = </span><span style="color: #4EC9B0">MethodChannel</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;jp.ninja.tripmemo/places&#39;</span><span style="color: #D4D4D4">);</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #4EC9B0">Future</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">List</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">&gt;&gt; </span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> query) </span><span style="color: #C586C0">async</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"> (query.</span><span style="color: #DCDCAA">trim</span><span style="color: #D4D4D4">().isEmpty) {</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">const</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">final</span><span style="color: #D4D4D4"> result = </span><span style="color: #C586C0">await</span><span style="color: #D4D4D4"> _channel.</span><span style="color: #DCDCAA">invokeMethod</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">List</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">dynamic</span><span style="color: #D4D4D4">&gt;&gt;(</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #CE9178">&#39;findAutocompletePredictions&#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: #CE9178">&#39;query&#39;</span><span style="color: #D4D4D4">: query,</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #CE9178">&#39;country&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&#39;JP&#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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (result == </span><span style="color: #569CD6">null</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">const</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"> result.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">((item) {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> map = </span><span style="color: #4EC9B0">Map</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">dynamic</span><span style="color: #D4D4D4">&gt;.</span><span style="color: #DCDCAA">from</span><span style="color: #D4D4D4">(item </span><span style="color: #569CD6">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Map</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: #4EC9B0">PlaceSuggestion</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        placeId: map&#91;</span><span style="color: #CE9178">&#39;placeId&#39;</span><span style="color: #D4D4D4">&#93; </span><span style="color: #569CD6">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</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">        primaryText: map&#91;</span><span style="color: #CE9178">&#39;primaryText&#39;</span><span style="color: #D4D4D4">&#93; </span><span style="color: #569CD6">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</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">        secondaryText: map&#91;</span><span style="color: #CE9178">&#39;secondaryText&#39;</span><span style="color: #D4D4D4">&#93; </span><span style="color: #569CD6">as</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</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>
<span class="line"><span style="color: #D4D4D4">    }).</span><span style="color: #DCDCAA">toList</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></code></pre></div>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Android側：Kotlin実装イメージ</h2>



<p class="wp-block-paragraph"><code>android/app/build.gradle.kts</code> にPlaces SDKを追加します。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>dependencies {
    implementation("com.google.android.libraries.places:places:5.1.1")
}
</textarea></pre><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">dependencies</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">implementation</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;com.google.android.libraries.places:places:5.1.1&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">※バージョンは例です。実際には最新のPlaces SDK for Androidのバージョンを確認してください。</p>



<p class="wp-block-paragraph"><code>MainActivity.kt</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>package jp.ninja.tripmemo

import android.os.Bundle
import com.google.android.libraries.places.api.Places
import com.google.android.libraries.places.api.model.AutocompletePrediction
import com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest
import com.google.android.libraries.places.api.net.PlacesClient
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val channelName = "jp.ninja.tripmemo/places"
    private lateinit var placesClient: PlacesClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val apiKey = BuildConfig.PLACES_API_KEY
        if (!Places.isInitialized()) {
            Places.initializeWithNewPlacesApiEnabled(applicationContext, apiKey)
        }
        placesClient = Places.createClient(this)
    }

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channelName)
            .setMethodCallHandler { call, result ->
                when (call.method) {
                    "findAutocompletePredictions" -> {
                        val query = call.argument&lt;String>("query").orEmpty()
                        val country = call.argument&lt;String>("country") ?: "JP"
                        findAutocompletePredictions(query, country, result)
                    }
                    else -> result.notImplemented()
                }
            }
    }

    private fun findAutocompletePredictions(
        query: String,
        country: String,
        result: MethodChannel.Result
    ) {
        val request = FindAutocompletePredictionsRequest.builder()
            .setQuery(query)
            .setCountries(country)
            .build()

        placesClient.findAutocompletePredictions(request)
            .addOnSuccessListener { response ->
                val predictions = response.autocompletePredictions.map { prediction ->
                    mapPrediction(prediction)
                }
                result.success(predictions)
            }
            .addOnFailureListener { exception ->
                result.error(
                    "PLACES_ERROR",
                    exception.message,
                    null,
                )
            }
    }

    private fun mapPrediction(prediction: AutocompletePrediction): Map&lt;String, String> {
        return mapOf(
            "placeId" to prediction.placeId,
            "primaryText" to prediction.getPrimaryText(null).toString(),
            "secondaryText" to prediction.getSecondaryText(null).toString(),
            "fullText" to prediction.getFullText(null).toString(),
        )
    }
}
</textarea></pre><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">package</span><span style="color: #D4D4D4"> jp.ninja.tripmemo</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> android.os.Bundle</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> com.google.android.libraries.places.api.Places</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> com.google.android.libraries.places.api.model.AutocompletePrediction</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> com.google.android.libraries.places.api.net.FindAutocompletePredictionsRequest</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> com.google.android.libraries.places.api.net.PlacesClient</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> io.flutter.embedding.android.FlutterActivity</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> io.flutter.embedding.engine.FlutterEngine</span></span>
<span class="line"><span style="color: #569CD6">import</span><span style="color: #D4D4D4"> io.flutter.plugin.common.MethodChannel</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">MainActivity</span><span style="color: #D4D4D4"> : </span><span style="color: #4EC9B0">FlutterActivity</span><span style="color: #D4D4D4">() {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">val</span><span style="color: #D4D4D4"> channelName = </span><span style="color: #CE9178">&quot;jp.ninja.tripmemo/places&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">lateinit</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">var</span><span style="color: #D4D4D4"> placesClient: </span><span style="color: #4EC9B0">PlacesClient</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">override</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">fun</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">onCreate</span><span style="color: #D4D4D4">(savedInstanceState: </span><span style="color: #4EC9B0">Bundle</span><span style="color: #D4D4D4">?) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">super</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">onCreate</span><span style="color: #D4D4D4">(savedInstanceState)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">val</span><span style="color: #D4D4D4"> apiKey = BuildConfig.PLACES_API_KEY</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> (!Places.</span><span style="color: #DCDCAA">isInitialized</span><span style="color: #D4D4D4">()) {</span></span>
<span class="line"><span style="color: #D4D4D4">            Places.</span><span style="color: #DCDCAA">initializeWithNewPlacesApiEnabled</span><span style="color: #D4D4D4">(applicationContext, apiKey)</span></span>
<span class="line"><span style="color: #D4D4D4">        }</span></span>
<span class="line"><span style="color: #D4D4D4">        placesClient = Places.</span><span style="color: #DCDCAA">createClient</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">this</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">override</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">fun</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">configureFlutterEngine</span><span style="color: #D4D4D4">(flutterEngine: </span><span style="color: #4EC9B0">FlutterEngine</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">super</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">configureFlutterEngine</span><span style="color: #D4D4D4">(flutterEngine)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">MethodChannel</span><span style="color: #D4D4D4">(flutterEngine.dartExecutor.binaryMessenger, channelName)</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">setMethodCallHandler</span><span style="color: #D4D4D4"> { call, result </span><span style="color: #569CD6">-&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #C586C0">when</span><span style="color: #D4D4D4"> (call.method) {</span></span>
<span class="line"><span style="color: #D4D4D4">                    </span><span style="color: #CE9178">&quot;findAutocompletePredictions&quot;</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">val</span><span style="color: #D4D4D4"> query = call.</span><span style="color: #DCDCAA">argument</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #CE9178">&quot;query&quot;</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">orEmpty</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">                        </span><span style="color: #569CD6">val</span><span style="color: #D4D4D4"> country = call.</span><span style="color: #DCDCAA">argument</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #CE9178">&quot;country&quot;</span><span style="color: #D4D4D4">) ?: </span><span style="color: #CE9178">&quot;JP&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">                        </span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(query, country, result)</span></span>
<span class="line"><span style="color: #D4D4D4">                    }</span></span>
<span class="line"><span style="color: #D4D4D4">                    </span><span style="color: #C586C0">else</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">-&gt;</span><span style="color: #D4D4D4"> result.</span><span style="color: #DCDCAA">notImplemented</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">fun</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">        query: </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        country: </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        result: </span><span style="color: #4EC9B0">MethodChannel</span><span style="color: #D4D4D4">.Result</span></span>
<span class="line"><span style="color: #D4D4D4">    ) {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">val</span><span style="color: #D4D4D4"> request = FindAutocompletePredictionsRequest.</span><span style="color: #DCDCAA">builder</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">setQuery</span><span style="color: #D4D4D4">(query)</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">setCountries</span><span style="color: #D4D4D4">(country)</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">build</span><span style="color: #D4D4D4">()</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">        placesClient.</span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(request)</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">addOnSuccessListener</span><span style="color: #D4D4D4"> { response </span><span style="color: #569CD6">-&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">                </span><span style="color: #569CD6">val</span><span style="color: #D4D4D4"> predictions = response.autocompletePredictions.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4"> { prediction </span><span style="color: #569CD6">-&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">                    </span><span style="color: #DCDCAA">mapPrediction</span><span style="color: #D4D4D4">(prediction)</span></span>
<span class="line"><span style="color: #D4D4D4">                }</span></span>
<span class="line"><span style="color: #D4D4D4">                result.</span><span style="color: #DCDCAA">success</span><span style="color: #D4D4D4">(predictions)</span></span>
<span class="line"><span style="color: #D4D4D4">            }</span></span>
<span class="line"><span style="color: #D4D4D4">            .</span><span style="color: #DCDCAA">addOnFailureListener</span><span style="color: #D4D4D4"> { exception </span><span style="color: #569CD6">-&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">                result.</span><span style="color: #DCDCAA">error</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">                    </span><span style="color: #CE9178">&quot;PLACES_ERROR&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">                    exception.message,</span></span>
<span class="line"><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>
<span class="line"><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">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">fun</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">mapPrediction</span><span style="color: #D4D4D4">(prediction: </span><span style="color: #4EC9B0">AutocompletePrediction</span><span style="color: #D4D4D4">): </span><span style="color: #4EC9B0">Map</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">, </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">&gt; {</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">mapOf</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;placeId&quot;</span><span style="color: #D4D4D4"> to prediction.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;primaryText&quot;</span><span style="color: #D4D4D4"> to prediction.</span><span style="color: #DCDCAA">getPrimaryText</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">toString</span><span style="color: #D4D4D4">(),</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;secondaryText&quot;</span><span style="color: #D4D4D4"> to prediction.</span><span style="color: #DCDCAA">getSecondaryText</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">toString</span><span style="color: #D4D4D4">(),</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;fullText&quot;</span><span style="color: #D4D4D4"> to prediction.</span><span style="color: #DCDCAA">getFullText</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">toString</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></code></pre></div>



<p class="wp-block-paragraph">※SDKバージョンによってメソッド名や戻り値の型が変わる可能性があります。実装時は利用しているPlaces SDKのリファレンスに合わせて調整してください。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">Android側：APIキー管理</h2>



<p class="wp-block-paragraph">本番ではAPIキーをソースコードに直書きしないようにします。</p>



<p class="wp-block-paragraph">例えば、<code>local.properties</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>PLACES_API_KEY=YOUR_API_KEY</textarea></pre><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: #D4D4D4">PLACES_API_KEY=YOUR_API_KEY</span></span></code></pre></div>



<p class="wp-block-paragraph">Gradle側で <code>BuildConfig.PLACES_API_KEY</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>android {
    defaultConfig {
        buildConfigField(
            "String",
            "PLACES_API_KEY",
            "\"${project.findProperty("PLACES_API_KEY") ?: ""}\""
        )
    }
}
</textarea></pre><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">android</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">defaultConfig</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">buildConfigField</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;String&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;PLACES_API_KEY&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">            </span><span style="color: #CE9178">&quot;</span><span style="color: #D7BA7D">\&quot;</span><span style="color: #569CD6">${</span><span style="color: #D4D4D4">project.</span><span style="color: #DCDCAA">findProperty</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;PLACES_API_KEY&quot;</span><span style="color: #D4D4D4">) ?: </span><span style="color: #CE9178">&quot;&quot;</span><span style="color: #569CD6">}</span><span style="color: #D7BA7D">\&quot;</span><span style="color: #CE9178">&quot;</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></code></pre></div>



<p class="wp-block-paragraph">実際にはGoogle公式が案内しているSecrets Gradle Pluginの利用も検討できます。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">iOS側：Swift実装イメージ</h2>



<p class="wp-block-paragraph">iOS側では、Places SDK for iOSを導入し、AppDelegateなどでAPIキーを設定します。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>import UIKit
import Flutter
import GooglePlaces

@main
@objc class AppDelegate: FlutterAppDelegate {
  private let channelName = "jp.ninja.tripmemo/places"

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: &#91;UIApplication.LaunchOptionsKey: Any&#93;?
  ) -> Bool {
    GMSPlacesClient.provideAPIKey("YOUR_API_KEY")

    let controller = window?.rootViewController as! FlutterViewController
    let channel = FlutterMethodChannel(
      name: channelName,
      binaryMessenger: controller.binaryMessenger
    )

    channel.setMethodCallHandler { &#91;weak self&#93; call, result in
      switch call.method {
      case "findAutocompletePredictions":
        guard
          let args = call.arguments as? &#91;String: Any&#93;,
          let query = args&#91;"query"&#93; as? String
        else {
          result(FlutterError(code: "INVALID_ARGUMENT", message: "query is required", details: nil))
          return
        }

        let country = args&#91;"country"&#93; as? String ?? "JP"
        self?.findAutocompletePredictions(query: query, country: country, result: result)

      default:
        result(FlutterMethodNotImplemented)
      }
    }

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func findAutocompletePredictions(
    query: String,
    country: String,
    result: @escaping FlutterResult
  ) {
    let token = GMSAutocompleteSessionToken()
    let filter = GMSAutocompleteFilter()
    filter.countries = &#91;country&#93;

    let request = GMSAutocompleteRequest(query: query)
    request.sessionToken = token
    request.filter = filter

    GMSPlacesClient.shared().fetchAutocompleteSuggestions(from: request) { suggestions, error in
      if let error = error {
        result(FlutterError(code: "PLACES_ERROR", message: error.localizedDescription, details: nil))
        return
      }

      let values = suggestions?.compactMap { suggestion -> &#91;String: String&#93;? in
        guard let place = suggestion.placeSuggestion else {
          return nil
        }

        return &#91;
          "placeId": place.placeID,
          "primaryText": place.attributedPrimaryText.string,
          "secondaryText": place.attributedSecondaryText?.string ?? "",
          "fullText": place.attributedFullText.string,
        &#93;
      } ?? []

      result(values)
    }
  }
}
</textarea></pre><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: #4EC9B0">UIKit</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Flutter</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">GooglePlaces</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">@main</span></span>
<span class="line"><span style="color: #569CD6">@objc</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">AppDelegate</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">FlutterAppDelegate </span><span style="color: #D4D4D4">{</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> channelName = </span><span style="color: #CE9178">&quot;jp.ninja.tripmemo/places&quot;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">override</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">func</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">application</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">_</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">application</span><span style="color: #D4D4D4">: UIApplication,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">didFinishLaunchingWithOptions</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">launchOptions</span><span style="color: #D4D4D4">: &#91;UIApplication.LaunchOptionsKey: </span><span style="color: #4EC9B0">Any</span><span style="color: #D4D4D4">&#93;?</span></span>
<span class="line"><span style="color: #D4D4D4">  ) -&gt; </span><span style="color: #4EC9B0">Bool</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">    GMSPlacesClient.</span><span style="color: #DCDCAA">provideAPIKey</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&quot;YOUR_API_KEY&quot;</span><span style="color: #D4D4D4">)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> controller = window?.</span><span style="color: #9CDCFE">rootViewController</span><span style="color: #D4D4D4"> as! FlutterViewController</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> channel = </span><span style="color: #DCDCAA">FlutterMethodChannel</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">name</span><span style="color: #D4D4D4">: channelName,</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #DCDCAA">binaryMessenger</span><span style="color: #D4D4D4">: controller.</span><span style="color: #9CDCFE">binaryMessenger</span></span>
<span class="line"><span style="color: #D4D4D4">    )</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    channel.</span><span style="color: #DCDCAA">setMethodCallHandler</span><span style="color: #D4D4D4"> { &#91;</span><span style="color: #569CD6">weak</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">self</span><span style="color: #D4D4D4">&#93; call, result </span><span style="color: #C586C0">in</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">switch</span><span style="color: #D4D4D4"> call.method {</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&quot;findAutocompletePredictions&quot;</span><span style="color: #D4D4D4">:</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">guard</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> args = call.arguments as? &#91;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">Any</span><span style="color: #D4D4D4">&#93;,</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> query = args&#91;</span><span style="color: #CE9178">&quot;query&quot;</span><span style="color: #D4D4D4">&#93; as? </span><span style="color: #4EC9B0">String</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">else</span><span style="color: #D4D4D4"> {</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #DCDCAA">result</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">FlutterError</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">code</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;INVALID_ARGUMENT&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">message</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;query is required&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">details</span><span style="color: #D4D4D4">: </span><span style="color: #569CD6">nil</span><span style="color: #D4D4D4">))</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #C586C0">return</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">let</span><span style="color: #D4D4D4"> country = args&#91;</span><span style="color: #CE9178">&quot;country&quot;</span><span style="color: #D4D4D4">&#93; as? </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> ?? </span><span style="color: #CE9178">&quot;JP&quot;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #569CD6">self</span><span style="color: #D4D4D4">?.</span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">query</span><span style="color: #D4D4D4">: query, </span><span style="color: #DCDCAA">country</span><span style="color: #D4D4D4">: country, </span><span style="color: #DCDCAA">result</span><span style="color: #D4D4D4">: result)</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4">:</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">result</span><span style="color: #D4D4D4">(FlutterMethodNotImplemented)</span></span>
<span class="line"><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">    GeneratedPluginRegistrant.</span><span style="color: #DCDCAA">register</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">with</span><span style="color: #D4D4D4">: </span><span style="color: #569CD6">self</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">super</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">application</span><span style="color: #D4D4D4">(application, </span><span style="color: #DCDCAA">didFinishLaunchingWithOptions</span><span style="color: #D4D4D4">: launchOptions)</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">private</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">func</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">findAutocompletePredictions</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">query</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">country</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">result</span><span style="color: #D4D4D4">: </span><span style="color: #569CD6">@escaping</span><span style="color: #D4D4D4"> FlutterResult</span></span>
<span class="line"><span style="color: #D4D4D4">  ) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> token = </span><span style="color: #DCDCAA">GMSAutocompleteSessionToken</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"> filter = </span><span style="color: #DCDCAA">GMSAutocompleteFilter</span><span style="color: #D4D4D4">()</span></span>
<span class="line"><span style="color: #D4D4D4">    filter.</span><span style="color: #9CDCFE">countries</span><span style="color: #D4D4D4"> = &#91;country&#93;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> request = </span><span style="color: #DCDCAA">GMSAutocompleteRequest</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">query</span><span style="color: #D4D4D4">: query)</span></span>
<span class="line"><span style="color: #D4D4D4">    request.</span><span style="color: #9CDCFE">sessionToken</span><span style="color: #D4D4D4"> = token</span></span>
<span class="line"><span style="color: #D4D4D4">    request.</span><span style="color: #9CDCFE">filter</span><span style="color: #D4D4D4"> = filter</span></span>
<span class="line"></span>
<span class="line"><span style="color: #D4D4D4">    GMSPlacesClient.</span><span style="color: #DCDCAA">shared</span><span style="color: #D4D4D4">().</span><span style="color: #DCDCAA">fetchAutocompleteSuggestions</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">from</span><span style="color: #D4D4D4">: request) { suggestions, error </span><span style="color: #C586C0">in</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #C586C0">if</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> error = error {</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #DCDCAA">result</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">FlutterError</span><span style="color: #D4D4D4">(</span><span style="color: #DCDCAA">code</span><span style="color: #D4D4D4">: </span><span style="color: #CE9178">&quot;PLACES_ERROR&quot;</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">message</span><span style="color: #D4D4D4">: error.</span><span style="color: #9CDCFE">localizedDescription</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">details</span><span style="color: #D4D4D4">: </span><span style="color: #569CD6">nil</span><span style="color: #D4D4D4">))</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">return</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">let</span><span style="color: #D4D4D4"> values = suggestions?.</span><span style="color: #DCDCAA">compactMap</span><span style="color: #D4D4D4"> { suggestion -&gt; &#91;</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">&#93;? </span><span style="color: #C586C0">in</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #C586C0">guard</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">let</span><span style="color: #D4D4D4"> place = suggestion.placeSuggestion </span><span style="color: #C586C0">else</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">nil</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"> &#91;</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #CE9178">&quot;placeId&quot;</span><span style="color: #D4D4D4">: place.</span><span style="color: #9CDCFE">placeID</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #CE9178">&quot;primaryText&quot;</span><span style="color: #D4D4D4">: place.</span><span style="color: #9CDCFE">attributedPrimaryText</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #CE9178">&quot;secondaryText&quot;</span><span style="color: #D4D4D4">: place.</span><span style="color: #9CDCFE">attributedSecondaryText</span><span style="color: #D4D4D4">?.</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4"> ?? </span><span style="color: #CE9178">&quot;&quot;</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">          </span><span style="color: #CE9178">&quot;fullText&quot;</span><span style="color: #D4D4D4">: place.</span><span style="color: #9CDCFE">attributedFullText</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">string</span><span style="color: #D4D4D4">,</span></span>
<span class="line"><span style="color: #D4D4D4">        &#93;</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: #DCDCAA">result</span><span style="color: #D4D4D4">(values)</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></code></pre></div>



<p class="wp-block-paragraph">※iOS SDKもバージョンによってAPIが変わる可能性があります。特にPlaces SDK for iOSとPlaces Swift SDKではAPIの書き方が異なるため、導入バージョンに合わせて調整してください。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">方法3：Backend Proxy方式はどうか</h2>



<p class="wp-block-paragraph">もう一つの選択肢として、Flutterアプリから直接Google Placesを呼ばず、Cloudflare WorkersやFirebase Functionsなどの自前バックエンドを経由する方法もあります。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Flutter
  ↓
Backend / Cloudflare Workers / Firebase Functions
  ↓
Google Places API</textarea></pre><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: #D4D4D4">Flutter</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓</span></span>
<span class="line"><span style="color: #D4D4D4">Backend / Cloudflare Workers / Firebase Functions</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓</span></span>
<span class="line"><span style="color: #D4D4D4">Google Places API</span></span></code></pre></div>



<p class="wp-block-paragraph">この方式のメリットは、Google APIキーをアプリに含めなくてよいことです。</p>



<p class="wp-block-paragraph">一方で、以下のような設計が必要になります。</p>



<ul class="wp-block-list">
<li>自前APIの認証</li>



<li>レート制限</li>



<li>不正利用対策</li>



<li>キャッシュ</li>



<li>ログ監視</li>



<li>Google APIへのリクエスト制限</li>
</ul>



<p class="wp-block-paragraph">個人開発アプリでは、最初からBackend Proxyを用意すると実装量が増えます。</p>



<p class="wp-block-paragraph">ただし、将来的にAIによる旅程生成やサーバー側のランキング処理、ユーザーごとの検索履歴管理などを入れるなら、Backend Proxy方式も有力です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">旅じたくではどう使うか</h2>



<p class="wp-block-paragraph">ここからは、実際に筆者が開発している旅行準備アプリ <strong>「旅じたく」</strong> を例に、Google Places APIをどう活用できるかを考えてみます。</p>



<p class="wp-block-paragraph">旅じたくは、旅行前の準備をまとめて整理できるアプリです。</p>



<p class="wp-block-paragraph">旅行ごとに、以下のような情報を管理できます。</p>



<ul class="wp-block-list">
<li>持ち物</li>



<li>予定</li>



<li>予約メモ</li>



<li>予算</li>



<li>行きたい場所</li>



<li>関連リンク</li>



<li>旅行メモ</li>
</ul>



<p class="wp-block-paragraph">旅行準備では、情報がいろいろな場所に散らばりがちです。</p>



<p class="wp-block-paragraph">例えば、宿泊予約はメール、行きたい場所はGoogle Maps、予算はメモ、持ち物は別のチェックリスト、といった形です。</p>



<p class="wp-block-paragraph">旅じたくでは、こうした旅行前の情報を <strong>旅行単位でまとめて管理する</strong> ことを目指しています。</p>



<p class="wp-block-paragraph">その中で、Google Places APIが特に活きるのが <strong>目的地検索</strong> と <strong>行きたい場所の入力</strong> です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">目的地入力でGoogle Places APIを使うメリット</h2>



<p class="wp-block-paragraph">旅じたくでは、旅行作成時に目的地を入力します。</p>



<p class="wp-block-paragraph">ここでGoogle Places APIを使うと、以下のような体験改善ができます。</p>



<ul class="wp-block-list">
<li>「札幌」「京都駅」「成田空港」などの候補を補完できる</li>



<li><code>placeId</code> を保存して、後から住所や緯度経度を取得できる</li>



<li>地図表示や天気取得と連携しやすくなる</li>



<li>入力ゆれを減らせる</li>



<li>行きたい場所機能にも応用できる</li>



<li>旅行テンプレート機能にも活用できる</li>
</ul>



<p class="wp-block-paragraph">例えば、ユーザーが「京都」と入力したときに、以下のような候補を表示できます。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>京都駅
京都府京都市下京区

清水寺
京都府京都市東山区

嵐山
京都府京都市右京区
</textarea></pre><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: #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>
<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></code></pre></div>



<p class="wp-block-paragraph">ユーザーが候補を選択したら、アプリ内では表示名だけでなく <code>placeId</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class TripDestination {
  const TripDestination({
    required this.name,
    required this.placeId,
    this.address,
    this.latitude,
    this.longitude,
  });

  final String name;
  final String placeId;
  final String? address;
  final double? latitude;
  final double? longitude;
}
</textarea></pre><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">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">TripDestination</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: #4EC9B0">TripDestination</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.name,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.address,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.latitude,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.longitude,</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">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> name;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> placeId;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">? address;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">double</span><span style="color: #D4D4D4">? latitude;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">double</span><span style="color: #D4D4D4">? longitude;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">最初のMVPでは、Autocompleteで選択した <code>placeId</code> と表示名だけ保存し、必要になったタイミングでPlace Detailsを取得する形でも十分です。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">旅じたくでの実装イメージ</h2>



<p class="wp-block-paragraph">旅じたくで目的地検索を入れるなら、まずは以下のような流れがよさそうです。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>旅行作成画面
  ↓
目的地入力
  ↓
Google Places Autocompleteで候補表示
  ↓
ユーザーが候補を選択
  ↓
旅行データにplaceIdと表示名を保存
  ↓
必要に応じてPlace Detailsで住所・緯度経度を取得
  ↓
地図・天気・行きたい場所機能へ連携</textarea></pre><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: #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">Google Places Autocompleteで候補表示</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">旅行データにplaceIdと表示名を保存</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓</span></span>
<span class="line"><span style="color: #D4D4D4">必要に応じてPlace Detailsで住所・緯度経度を取得</span></span>
<span class="line"><span style="color: #D4D4D4">  ↓</span></span>
<span class="line"><span style="color: #D4D4D4">地図・天気・行きたい場所機能へ連携</span></span></code></pre></div>



<p class="wp-block-paragraph">アプリ内のデータとしては、最初からすべての詳細情報を保存する必要はありません。</p>



<p class="wp-block-paragraph">まずは以下のような最小構成で十分です。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class TripPlace {
  const TripPlace({
    required this.placeId,
    required this.name,
    this.address,
  });

  final String placeId;
  final String name;
  final String? address;
}
</textarea></pre><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">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">TripPlace</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: #4EC9B0">TripPlace</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.name,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.address,</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">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> placeId;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> name;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">? address;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">後から地図表示や天気取得を強化したくなったタイミングで、緯度経度などを追加していくのが扱いやすいです。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>class TripPlaceDetail {
  const TripPlaceDetail({
    required this.placeId,
    required this.name,
    required this.latitude,
    required this.longitude,
    this.address,
  });

  final String placeId;
  final String name;
  final double latitude;
  final double longitude;
  final String? address;
}
</textarea></pre><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">class</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">TripPlaceDetail</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: #4EC9B0">TripPlaceDetail</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.placeId,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.name,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.latitude,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">required</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.longitude,</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">this</span><span style="color: #D4D4D4">.address,</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">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> placeId;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> name;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">double</span><span style="color: #D4D4D4"> latitude;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">double</span><span style="color: #D4D4D4"> longitude;</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">final</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4">? address;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">こうしておくと、旅じたくの中で以下のような拡張がしやすくなります。</p>



<ul class="wp-block-list">
<li>旅行先の天気表示</li>



<li>行きたい場所の地図表示</li>



<li>現在地から目的地までの導線</li>



<li>旅行中モードで次の目的地を表示</li>



<li>旅程テンプレートでスポット候補を扱う</li>



<li>旅行前の準備状況と目的地情報の連携</li>
</ul>



<p class="wp-block-paragraph">単なる「場所検索」ではなく、旅行準備アプリ全体の使いやすさにつながる機能になります。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">実装時の注意点</h2>



<p class="wp-block-paragraph">Google Places APIは便利ですが、実装時にはいくつか注意点があります。</p>



<h3 class="wp-block-heading">1. APIキーは分ける</h3>



<p class="wp-block-paragraph">Android、iOS、Web、サーバーで同じAPIキーを使い回さない方が安全です。</p>



<p class="wp-block-paragraph">例えば以下のように分けます。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>places-android-key
places-ios-key
places-server-key
</textarea></pre><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: #D4D4D4">places-android-key</span></span>
<span class="line"><span style="color: #D4D4D4">places-ios-key</span></span>
<span class="line"><span style="color: #D4D4D4">places-server-key</span></span>
<span class="line"></span></code></pre></div>



<p class="wp-block-paragraph">それぞれに適切なアプリケーション制限とAPI制限を設定します。</p>



<h3 class="wp-block-heading">2. API制限を必ず設定する</h3>



<p class="wp-block-paragraph">APIキーには、利用するAPIだけを許可するAPI制限を設定します。</p>



<p class="wp-block-paragraph">Placesで使うキーなら、Places関連のAPIだけを許可します。</p>



<p class="wp-block-paragraph">APIキーが漏れたときに、他のGoogle Cloud APIまで使われるリスクを下げるためです。</p>



<h3 class="wp-block-heading">3. Autocompleteはデバウンスする</h3>



<p class="wp-block-paragraph">Autocompleteはユーザーが文字を入力するたびにリクエストが発生しやすい機能です。</p>



<p class="wp-block-paragraph">そのため、入力のたびに即リクエストするのではなく、300〜500ms程度のデバウンスを入れるのがおすすめです。</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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>Timer? _debounce;

void onSearchTextChanged(String value) {
  _debounce?.cancel();
  _debounce = Timer(const Duration(milliseconds: 400), () {
    _search(value);
  });
}
</textarea></pre><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: #4EC9B0">Timer</span><span style="color: #D4D4D4">? _debounce;</span></span>
<span class="line"></span>
<span class="line"><span style="color: #569CD6">void</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">onSearchTextChanged</span><span style="color: #D4D4D4">(</span><span style="color: #4EC9B0">String</span><span style="color: #D4D4D4"> value) {</span></span>
<span class="line"><span style="color: #D4D4D4">  _debounce?.</span><span style="color: #DCDCAA">cancel</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  _debounce = </span><span style="color: #4EC9B0">Timer</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">Duration</span><span style="color: #D4D4D4">(milliseconds: </span><span style="color: #B5CEA8">400</span><span style="color: #D4D4D4">), () {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">_search</span><span style="color: #D4D4D4">(value);</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>



<h3 class="wp-block-heading">4. 空文字や短すぎる文字では検索しない</h3>



<p class="wp-block-paragraph">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" style="color:#D4D4D4;display:none" aria-label="Copy" class="code-block-pro-copy-button"><pre class="code-block-pro-copy-button-pre" aria-hidden="true"><textarea class="code-block-pro-copy-button-textarea" tabindex="-1" aria-hidden="true" readonly>if (query.trim().length &lt; 2) {
  return const [];
}
</textarea></pre><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">if</span><span style="color: #D4D4D4"> (query.</span><span style="color: #DCDCAA">trim</span><span style="color: #D4D4D4">().length &lt; </span><span style="color: #B5CEA8">2</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">const</span><span style="color: #D4D4D4"> [];</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span>
<span class="line"></span></code></pre></div>



<h3 class="wp-block-heading">5. セッショントークンを意識する</h3>



<p class="wp-block-paragraph">Places Autocompleteでは、入力候補の取得からユーザーの選択、詳細取得までを1つのセッションとして扱うために、セッショントークンを使えます。</p>



<p class="wp-block-paragraph">料金やリクエスト管理にも関わるため、AutocompleteとPlace Detailsを組み合わせる場合は、セッショントークンの扱いを確認しておくとよいです。</p>



<h3 class="wp-block-heading">6. 取得するフィールドを絞る</h3>



<p class="wp-block-paragraph">Place Detailsを取得する場合は、必要なフィールドだけを指定します。</p>



<p class="wp-block-paragraph">例えば、アプリで必要なのが名前・住所・緯度経度だけなら、それ以外の情報を無理に取得しないようにします。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">個人開発アプリならどの方式がよいか</h2>



<p class="wp-block-paragraph">個人開発アプリでFlutterからGoogle Places APIを使うなら、個人的には以下の順番で検討するのがよいと思います。</p>



<h3 class="wp-block-heading">まずはFlutterプラグインを検証する</h3>



<p class="wp-block-paragraph">最初からMethodChannelを自作すると、Android/iOSそれぞれの実装が必要になります。</p>



<p class="wp-block-paragraph">そのため、まずは <code>flutter_google_places_sdk</code> のようなネイティブSDKラッパー系プラグインで、目的の機能が実現できるか確認するのが現実的です。</p>



<h3 class="wp-block-heading">プラグインで足りなければMethodChannelを検討する</h3>



<p class="wp-block-paragraph">プラグインの対応状況やAPIの自由度に不満が出てきたら、MethodChannelで自前実装するのがよいです。</p>



<p class="wp-block-paragraph">特に、以下のような場合は自作の価値があります。</p>



<ul class="wp-block-list">
<li>Android/iOSで細かく挙動を合わせたい</li>



<li>最新のPlaces SDKにすぐ追従したい</li>



<li>必要なフィールドや検索条件を細かく制御したい</li>



<li>アプリの中核機能として長期的に運用したい</li>
</ul>



<h3 class="wp-block-heading">サーバー側機能が増えたらBackend Proxyを検討する</h3>



<p class="wp-block-paragraph">AIによる旅程生成や、サーバー側でのスポット推薦、検索履歴の分析などを行う場合は、Backend Proxy方式も候補になります。</p>



<p class="wp-block-paragraph">ただし、単純な目的地検索だけなら、最初からサーバーを挟む必要はないケースも多いと思います。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



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



<p class="wp-block-paragraph">FlutterでGoogle Places APIを使うとき、DartからREST APIを直接呼び出すだけなら実装は簡単です。</p>



<p class="wp-block-paragraph">しかし、その場合はAndroid/iOSアプリ制限付きAPIキーとの相性が悪く、403になることがあります。</p>



<p class="wp-block-paragraph">モバイルアプリで安全にPlacesを使いたい場合は、以下のどれかを選ぶのが現実的です。</p>



<ul class="wp-block-list">
<li>ネイティブSDKを利用するFlutterプラグインを使う</li>



<li>MethodChannelでPlaces SDK for Android / iOSを呼び出す</li>



<li>自前Backendを経由してPlaces APIを呼び出す</li>
</ul>



<p class="wp-block-paragraph">個人開発アプリでは、まずFlutterプラグインで小さく検証し、必要に応じてMethodChannel化するのがバランスの良い選択だと思います。</p>



<p class="wp-block-paragraph">「旅じたく」のような旅行準備アプリでは、目的地検索、行きたい場所、地図表示、天気連携など、Places APIを活用できる場面は多くあります。</p>



<p class="wp-block-paragraph">ただし、Google Places APIは便利な反面、APIキー制限や課金設計を誤るとトラブルになりやすい部分でもあります。</p>



<p class="wp-block-paragraph">FlutterでPlaces検索を実装する場合は、最初に <strong>どの呼び出し方式なら、どのAPIキー制限が使えるのか</strong> を整理しておくのがおすすめです。</p>



<hr class="wp-block-separator has-alpha-channel-opacity"/>



<h2 class="wp-block-heading">旅じたくの紹介</h2>



<p class="wp-block-paragraph">最後に少しだけ、今回の実装検討の背景になっているアプリを紹介します。<strong>旅じたく</strong> は、旅行前の準備をまとめて整理できるアプリです。旅行ごとに、持ち物、予定、予約メモ、予算、行きたい場所、関連リンクなどを管理できます。</p>



<p class="wp-block-paragraph">旅行の準備は、メモアプリ、カレンダー、地図、予約メールなどに情報が散らばりがちです。旅じたくでは、それらを旅行単位でまとめて管理できるようにすることを目指しています。</p>



<p class="wp-block-paragraph">今後は、Google Places APIを活用した目的地検索や場所候補の補完によって、旅行作成時の入力体験も改善していく予定です。</p>



<p class="wp-block-paragraph">旅行前の準備を少しでもラクにしたい方は、ぜひ使ってみてください。</p>


<a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6770222625?ct=tabijitaku_lp&#038;mt=8" title="旅じたくアプリ - App Store" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img fetchpriority="high" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/61806b46d335cd0616f022cd1a772eeb.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">旅じたくアプリ &#8211; App Store</div><div class="blogcard-snippet external-blogcard-snippet">NINJA SYSTEMの「旅じたく」をApp Storeでダウンロードしてください。スクリーンショット、評価とレビュー、ユーザのヒント、「旅じたく」に似たゲームを見ることなどができます。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img decoding="async" src="https://www.google.com/s2/favicons?domain=https://apps.apple.com/jp/app/%E6%97%85%E3%81%98%E3%81%9F%E3%81%8F/id6770222625" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">apps.apple.com</div></div></div></div></a>

<a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.tripmemo&#038;utm_source=ninja_site&#038;utm_medium=lp&#038;utm_campaign=tabijitaku_lp&#038;hl=ja&#038;gl=JP" title="旅じたく - 旅行準備・持ち物 - Google Play のアプリ" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/a5231a8c768556a5b9ab58f8b5ee03db.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">旅じたく &#8211; 旅行準備・持ち物 &#8211; Google Play のアプリ</div><div class="blogcard-snippet external-blogcard-snippet">旅行前の持ち物・予定・予算・予約をまとめて整理</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://play.google.com/store/apps/details?id=jp.ninja.tripmemo&#038;hl=ja" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">play.google.com</div></div></div></div></a>


<h2 class="wp-block-heading">補足：おすすめの実装方針</h2>



<p class="wp-block-paragraph">筆者なら、旅行系アプリのMVPでは次の順番で進めます。</p>



<ol class="wp-block-list">
<li><code>flutter_google_places_sdk</code> でAutocompleteの動作検証</li>



<li>Android/iOSでアプリ制限付きAPIキーが正しく使えるか確認</li>



<li>目的地名とplaceIdだけ保存</li>



<li>必要になったらPlace Detailsで住所・緯度経度を取得</li>



<li>プラグインの制約が出たらMethodChannelへ移行</li>



<li>サーバー側処理が増えたらBackend Proxyを検討</li>
</ol>



<p class="wp-block-paragraph">最初から大きく作りすぎず、まずは目的地入力の体験改善に絞って導入するのが良いと思います。旅行アプリでは、目的地入力のしやすさがそのまま旅行作成のしやすさにつながります。小さな改善ですが、ユーザー体験への効果はかなり大きいはずです。</p>



<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/flutter-google-places-api-403-app-restriction/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>QRコード詐欺とは？スマホでできるフィッシング対策とURL確認のコツ</title>
		<link>https://techgrowup.net/qr-code-scam-quishing-url-check/</link>
					<comments>https://techgrowup.net/qr-code-scam-quishing-url-check/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sat, 23 May 2026 16:11:26 +0000</pubDate>
				<category><![CDATA[セキュリティ]]></category>
		<category><![CDATA[QRコード]]></category>
		<category><![CDATA[URL確認]]></category>
		<category><![CDATA[クイッシング]]></category>
		<category><![CDATA[スマホ]]></category>
		<category><![CDATA[フィッシング]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2913</guid>

					<description><![CDATA[QRコードは、飲食店のメニュー、決済、イベント受付、チラシ、荷物確認など、日常のさまざまな場面で使われるようになりました。スマホのカメラを向けるだけでWebサイトを開けるため、とても便利です。 一方で、QRコードを悪用し [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">QRコードは、飲食店のメニュー、決済、イベント受付、チラシ、荷物確認など、日常のさまざまな場面で使われるようになりました。スマホのカメラを向けるだけでWebサイトを開けるため、とても便利です。</p>



<p class="wp-block-paragraph">一方で、QRコードを悪用して偽サイトへ誘導する「QRコード詐欺」や「クイッシング」と呼ばれる手口にも注意が必要です。クイッシングは、QRコードとフィッシングを組み合わせた言葉で、QRコードから偽サイトへ誘導し、ID・パスワードやクレジットカード情報などを盗もうとする手口です。</p>



<p class="wp-block-paragraph">QRコードは見た目だけではリンク先が分かりません。この記事では、QRコード詐欺の仕組み、よくある手口、スマホでできる対策、URL確認のコツを解説します。</p>



<h2 class="wp-block-heading">QRコード詐欺・クイッシングとは</h2>



<p class="wp-block-paragraph">QRコード詐欺とは、QRコードを使って偽サイトや不正なページへ誘導するフィッシング詐欺の一種です。</p>



<p class="wp-block-paragraph">たとえば、銀行、クレジットカード会社、決済サービス、配送業者、ECサイトなどを装ったページに誘導され、ログイン情報やカード情報の入力を求められるケースがあります。見た目が本物に似ていても、URLのドメインが公式サイトと違う場合があります。</p>



<p class="wp-block-paragraph">IPAも、QRコードを悪用したフィッシング詐欺を「クイッシング」として紹介しており、QRコードから偽サイトに誘導され、ID・パスワードやクレジットカード情報を詐取される可能性があると説明しています。</p>



<p class="wp-block-paragraph">重要なのは、QRコードそのものが危険なのではなく、QRコードの先にあるURLやWebサイトが危険な場合があるという点です。QRコードは便利ですが、「読み取った後にどこへ移動するのか」を確認する習慣が大切です。</p>



<h2 class="wp-block-heading">QRコード詐欺が厄介な理由</h2>



<p class="wp-block-paragraph">QRコード詐欺が厄介なのは、読み取る前にリンク先を判断しづらいことです。通常のURLであれば、文字列を見て「公式サイトと違う」「ドメインが怪しい」と気づけることがあります。しかしQRコードは、画像として表示されるため、見た目だけではリンク先が分かりません。</p>



<p class="wp-block-paragraph">また、QRコードはメールやPDF、チラシ、ポスターなどに画像として埋め込まれます。メール本文に直接URLを書かず、QRコードとして表示することで、リンク検査をすり抜けようとする手口もあります。IPAも、QRコードは画像であるため、メール内URLを検査するフィルタリング機能をすり抜ける可能性があると説明しています。</p>



<p class="wp-block-paragraph">さらに、物理的な場所に貼られたQRコードが差し替えられる可能性もあります。駐車場、飲食店、公共スペース、イベント会場などで、正規のQRコードの上から偽のQRコードシールが貼られるケースも考えられます。スマホで気軽に読み取れるからこそ、ひと手間の確認が重要です。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-1024x572.jpg" alt="" class="wp-image-2915" srcset="https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-1024x572.jpg 1024w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-300x168.jpg 300w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-768x429.jpg 768w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-1536x858.jpg 1536w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2026/05/QRコード詐欺の対策.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">よくあるQRコード詐欺の手口</h2>



<p class="wp-block-paragraph">QRコード詐欺には、いくつかのパターンがあります。</p>



<p class="wp-block-paragraph">まず注意したいのは、駐車場や店舗、イベント会場などに貼られたQRコードです。支払いページや案内ページを装い、偽の決済サイトへ誘導される可能性があります。特に、QRコードがシールで上貼りされている場合や、周囲の掲示物と雰囲気が違う場合は注意が必要です。</p>



<p class="wp-block-paragraph">次に、宅配業者やECサイトを装った手口です。「荷物の確認」「再配達手続き」「注文内容の確認」などを口実にQRコードを読み取らせ、偽サイトへ誘導するケースがあります。そこから住所、電話番号、ログイン情報、カード情報などの入力を求められることがあります。</p>



<p class="wp-block-paragraph">また、メールやPDFにQRコードを掲載し、スマホで読み取らせる手口もあります。PCで受け取ったメールをスマホで読み取らせることで、会社や学校のセキュリティ対策を回避しようとするケースもあります。QRコードを読み取った先でログインや決済を求められた場合は、一度立ち止まることが大切です。</p>



<div style="border: 2px solid #dbeafe; border-radius: 16px; padding: 20px; background: #f8fbff; margin: 28px 0;">
  <p style="font-size: 1.1em; font-weight: 700; margin: 0 0 8px;">
    QRコードを開く前にURLを確認したい方へ
  </p>
  <p style="margin: 0 0 16px; line-height: 1.8;">
    「あんしんQRコードチェッカー」は、QRコードを読み取ったあとにURLを確認してから開けるアプリです。怪しいURLをすぐ開かず、ワンクッション置いて確認できます。
  </p>
  <div style="display: flex; flex-wrap: wrap; gap: 12px;">
    <a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6769943765?l=ja" style="display: inline-block; padding: 12px 18px; border-radius: 999px; background: #0f172a; color: #fff; text-decoration: none; font-weight: 700;">
      App Storeで見る
    <span class="fa fa-external-link external-icon anchor-icon"></span></a>
    <a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.qrguard" style="display: inline-block; padding: 12px 18px; border-radius: 999px; background: #2563eb; color: #fff; text-decoration: none; font-weight: 700;">
      Google Playで見る
    <span class="fa fa-external-link external-icon anchor-icon"></span></a>
  </div>
</div>



<h2 class="wp-block-heading">怪しいQRコードを見分けるチェックポイント</h2>



<p class="wp-block-paragraph">QRコードを読み取ったら、まずURLを確認しましょう。特に銀行、クレジットカード、決済サービス、ECサイト、配送業者などを名乗るページでは、公式サイトのドメインと一致しているかを見ることが重要です。</p>



<p class="wp-block-paragraph">たとえば、公式サイトに見えても、ドメインの一部が不自然だったり、余計な単語が入っていたり、見慣れないサブドメインになっていたりする場合があります。短縮URLも、実際のリンク先が分かりづらいため注意が必要です。</p>



<p class="wp-block-paragraph">また、読み取り後すぐに個人情報、ID・パスワード、クレジットカード情報の入力を求められる場合は慎重に判断しましょう。FTCも、QRコードの先が本物そっくりの偽サイトである可能性や、入力した情報が盗まれる可能性について注意喚起しています。</p>



<p class="wp-block-paragraph">物理的なQRコードの場合は、シールで上から貼られていないか、掲示物に違和感がないかも確認しましょう。不安がある場合は、その場で店舗や管理者に確認するのが安全です。</p>



<h2 class="wp-block-heading">スマホでできるフィッシング対策</h2>



<p class="wp-block-paragraph">スマホでできる基本対策は、「QRコードを読み取ったらすぐ開かない」ことです。標準カメラでもURLが表示されることはありますが、勢いでタップしてしまうと、そのままブラウザで開いてしまいます。</p>



<p class="wp-block-paragraph">銀行、決済サービス、ECサイト、配送業者などの重要なサービスは、QRコードやメール内リンクからではなく、公式アプリやブックマークからアクセスする方が安全です。特にログインや決済を求められた場合は、読み取ったURLをそのまま信用せず、公式ルートから入り直すことをおすすめします。</p>



<p class="wp-block-paragraph">また、スマホのOSやブラウザを最新の状態に保つことも大切です。古いOSやアプリを使い続けると、既知の脆弱性を悪用されるリスクが高まります。</p>



<p class="wp-block-paragraph">QRコードは便利な仕組みですが、リンク先を確認する習慣がないと、偽サイトに誘導されても気づきにくい場合があります。URLを見てから開く、必要ならコピーして確認する、怪しい場合は開かない。この基本動作を意識するだけでもリスクを下げられます。</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="572" src="https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-1024x572.jpg" alt="" class="wp-image-2918" srcset="https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-1024x572.jpg 1024w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-300x168.jpg 300w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-768x429.jpg 768w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-1536x858.jpg 1536w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2026/05/ミスリードURLとクイッシングの対策.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading">「あんしんQRコードチェッカー」でURLを確認してから開く</h2>



<p class="wp-block-paragraph">QRコードを読み取ったあと、すぐにURLを開くのではなく、一度リンク先を確認したい場合は、QRGuardも選択肢のひとつです。</p>



<p class="wp-block-paragraph">あんしんQRコードチェッカーは、QRコードを読み取ったあとにURLを表示し、開く前に確認できるアプリです。読み取ったURLをコピーしたり、履歴から見返したりすることもできます。QRコードを読み取るたびに、ワンクッション置いてURLを見る習慣を作りたい場合に役立ちます。</p>



<p class="wp-block-paragraph">ただし、あんしんQRコードチェッカーはすべての危険URLを自動判定するセキュリティソフトではありません。最終的には、ユーザー自身がURLやページ内容を確認する必要があります。</p>



<p class="wp-block-paragraph">QRコードは便利ですが、リンク先が見えにくいという特徴があります。読み取ったらすぐ開くのではなく、まずURLを確認する。このひと手間が、スマホでできる基本的なフィッシング対策になります。</p>



<div style="border: 2px solid #dbeafe; border-radius: 16px; padding: 20px; background: #f8fbff; margin: 28px 0;">
  <p style="font-size: 1.1em; font-weight: 700; margin: 0 0 8px;">
    QRコードを開く前にURLを確認したい方へ
  </p>
  <p style="margin: 0 0 16px; line-height: 1.8;">
    「あんしんQRコードチェッカー」は、QRコードを読み取ったあとにURLを確認してから開けるアプリです。怪しいURLをすぐ開かず、ワンクッション置いて確認できます。
  </p>
  <div style="display: flex; flex-wrap: wrap; gap: 12px;">
    <a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6769943765?l=ja" style="display: inline-block; padding: 12px 18px; border-radius: 999px; background: #0f172a; color: #fff; text-decoration: none; font-weight: 700;">
      App Storeで見る
    <span class="fa fa-external-link external-icon anchor-icon"></span></a>
    <a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.qrguard" style="display: inline-block; padding: 12px 18px; border-radius: 999px; background: #2563eb; color: #fff; text-decoration: none; font-weight: 700;">
      Google Playで見る
    <span class="fa fa-external-link external-icon anchor-icon"></span></a>
  </div>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/qr-code-scam-quishing-url-check/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>QRコードを開く前にリンク先を確認できるアプリ「あんしんQRチェッカー」を作りました</title>
		<link>https://techgrowup.net/qr-code-url-check-app/</link>
					<comments>https://techgrowup.net/qr-code-url-check-app/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Thu, 21 May 2026 14:25:52 +0000</pubDate>
				<category><![CDATA[エンジニアコラム]]></category>
		<category><![CDATA[QRコード]]></category>
		<category><![CDATA[URL]]></category>
		<category><![CDATA[バーコード]]></category>
		<category><![CDATA[フィッシング]]></category>
		<category><![CDATA[プレビュー]]></category>
		<category><![CDATA[リンク]]></category>
		<category><![CDATA[リーダー]]></category>
		<category><![CDATA[安全]]></category>
		<category><![CDATA[履歴]]></category>
		<category><![CDATA[怪しい]]></category>
		<category><![CDATA[短縮URL]]></category>
		<category><![CDATA[確認]]></category>
		<category><![CDATA[詐欺]]></category>
		<category><![CDATA[読み取り]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2908</guid>

					<description><![CDATA[QRコードはとても便利ですが、読み取ったあとにそのままリンクを開くのが少し不安な場面があります。 たとえば、以下のようなケースです。 最近はQRコードを読み取る機会が増えていますが、QRコード自体は見た目だけでは中身が分 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">QRコードはとても便利ですが、読み取ったあとにそのままリンクを開くのが少し不安な場面があります。</p>



<p class="wp-block-paragraph">たとえば、以下のようなケースです。</p>



<ul class="wp-block-list">
<li>チラシやポスターに載っているQRコード</li>



<li>メールや資料に貼られているQRコード</li>



<li>見慣れない短縮URLにつながるQRコード</li>



<li>開く前にリンク先のドメインを確認したいQRコード</li>



<li>QRコードの中身がURLなのかテキストなのか確認したい場合</li>
</ul>



<p class="wp-block-paragraph">最近はQRコードを読み取る機会が増えていますが、QRコード自体は見た目だけでは中身が分かりません。</p>



<p class="wp-block-paragraph">そこで、QRコードやバーコードを読み取ったあと、開く前に内容を確認できるアプリとして <strong>「あんしんQRチェッカー」</strong> を作りました。</p>



<h2 class="wp-block-heading">あんしんQRチェッカーとは</h2>



<p class="wp-block-paragraph"><strong>あんしんQRチェッカー</strong> は、QRコードやバーコードを読み取り、開く前に内容をプレビューできるシンプルな読み取りアプリです。</p>



<p class="wp-block-paragraph">読み取った内容がURLの場合は、リンク先のドメインを表示します。</p>



<p class="wp-block-paragraph">そのため、すぐにブラウザで開くのではなく、内容を確認してから開くかどうかを判断できます。</p>


<a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6769943765?l=ja" title="あんしんQRチェッカーアプリ - App Store" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/767d374cc8d3bf2656303656b22ad0e5.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">あんしんQRチェッカーアプリ &#8211; App Store</div><div class="blogcard-snippet external-blogcard-snippet">NINJA SYSTEMの「あんしんQRチェッカー」をApp Storeでダウンロードしてください。スクリーンショット、評価とレビュー、ユーザのヒント、「あんしんQRチェッカー」に似たゲームを見ることなどができます。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://apps.apple.com/jp/app/%E3%81%82%E3%82%93%E3%81%97%E3%82%93qr%E3%83%81%E3%82%A7%E3%83%83%E3%82%AB%E3%83%BC/id6769943765" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">apps.apple.com</div></div></div></div></a>

<a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.qrguard&#038;hl=ja&#038;gl=JP" title="あんしんQRチェッカー - Google Play のアプリ" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/82b5df2c69eca8115138631939cf3f61.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">あんしんQRチェッカー &#8211; Google Play のアプリ</div><div class="blogcard-snippet external-blogcard-snippet">QR・バーコードを開く前に確認できるリーダー。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://play.google.com/store/apps/details?id=jp.ninja.qrguard&#038;hl=ja" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">play.google.com</div></div></div></div></a>


<h2 class="wp-block-heading">主な機能</h2>



<p class="wp-block-paragraph">あんしんQRチェッカーでは、主に以下の機能を利用できます。</p>



<ul class="wp-block-list">
<li>QRコードの読み取り</li>



<li>バーコードの読み取り</li>



<li>画像からのQRコード読み取り</li>



<li>URLやテキストを開く前にプレビュー</li>



<li>リンク先ドメインの表示</li>



<li>短縮URLなど、注意して確認したい形式のお知らせ</li>



<li>読み取り履歴の保存と削除</li>



<li>アプリ内の言語切り替え</li>
</ul>



<p class="wp-block-paragraph">QRコードリーダーとしての基本機能に加えて、「開く前に確認する」ことを重視しています。</p>



<h2 class="wp-block-heading">QRコードは便利だが、中身が見えない</h2>



<p class="wp-block-paragraph">QRコードは、URLやテキストなどの情報を簡単に読み取れる便利な仕組みです。</p>



<p class="wp-block-paragraph">一方で、QRコードは見た目だけでは中身が分かりません。</p>



<p class="wp-block-paragraph">読み取ったあとにそのままブラウザを開くタイプのQRリーダーもありますが、個人的には「一度内容を確認してから開きたい」と感じる場面がありました。</p>



<p class="wp-block-paragraph">特に、短縮URLや見慣れないドメインの場合は、いきなり開くよりも先に確認できた方が安心です。</p>



<h2 class="wp-block-heading">開く前にURLやドメインを確認できる</h2>



<p class="wp-block-paragraph">あんしんQRチェッカーでは、QRコードを読み取ったあと、まず確認画面を表示します。</p>



<p class="wp-block-paragraph">URLの場合は、リンク先のドメインも表示されます。</p>



<p class="wp-block-paragraph">たとえば、QRコードを読み取った結果がWebサイトのURLだった場合でも、すぐにブラウザを開くのではなく、以下のように確認できます。</p>



<ul class="wp-block-list">
<li>読み取ったURL</li>



<li>リンク先のドメイン</li>



<li>内容の種類</li>



<li>開く / コピーするなどの操作</li>
</ul>



<p class="wp-block-paragraph">これにより、「このQRコードを開いてもよさそうか」を自分で確認してから操作できます。</p>



<h2 class="wp-block-heading">画像内のQRコードも読み取れる</h2>



<p class="wp-block-paragraph">カメラで直接読み取るだけでなく、画像からのQRコード読み取りにも対応しています。</p>



<p class="wp-block-paragraph">たとえば、スクリーンショットや保存済み画像に含まれるQRコードを読み取りたい場合に利用できます。</p>



<p class="wp-block-paragraph">スマートフォン上で受け取った画像や資料内のQRコードを確認したいときにも便利です。</p>



<h2 class="wp-block-heading">読み取り履歴をあとから確認できる</h2>



<p class="wp-block-paragraph">読み取った内容は、履歴としてあとから確認できます。</p>



<p class="wp-block-paragraph">一度読み取ったURLやテキストを再確認したい場合に便利です。</p>



<p class="wp-block-paragraph">また、不要になった履歴は削除できます。</p>



<p class="wp-block-paragraph">履歴は端末内に保存されるため、必要に応じて自分で管理できます。</p>



<h2 class="wp-block-heading">プライバシーに配慮した設計</h2>



<p class="wp-block-paragraph">あんしんQRチェッカーでは、プライバシーにも配慮しています。</p>



<ul class="wp-block-list">
<li>カメラ映像は端末内のみで処理</li>



<li>カメラ映像を保存・送信しない</li>



<li>読み取ったデータを外部サーバーに送信しない</li>



<li>履歴は端末内に保存</li>



<li>履歴はいつでも削除可能</li>
</ul>



<p class="wp-block-paragraph">QRコードの読み取りは日常的に使う機能なので、なるべくシンプルで分かりやすく、安心して使える設計を意識しました。</p>



<h2 class="wp-block-heading">こんな方におすすめ</h2>



<p class="wp-block-paragraph">あんしんQRチェッカーは、以下のような方におすすめです。</p>



<ul class="wp-block-list">
<li>QRコードを開く前に内容を確認したい方</li>



<li>知らないリンクや短縮URLを慎重に扱いたい方</li>



<li>リンク先のドメインを確認してから開きたい方</li>



<li>QRコードだけでなくバーコードも読み取りたい方</li>



<li>画像内のQRコードを読み取りたい方</li>



<li>読み取り履歴を端末内で管理したい方</li>



<li>シンプルで見やすいQRリーダーを使いたい方</li>
</ul>



<h2 class="wp-block-heading">利用時の注意点</h2>



<p class="wp-block-paragraph">本アプリは、リンク先の内容や安全性を保証するものではありません。</p>



<p class="wp-block-paragraph">あくまで、QRコードやバーコードの内容を開く前に確認しやすくするためのアプリです。</p>



<p class="wp-block-paragraph">表示された内容を確認したうえで、ご自身の判断で利用してください。</p>



<p class="wp-block-paragraph">また、QRコードやバーコードの読み取りにはカメラの使用許可が必要です。</p>



<h2 class="wp-block-heading">ダウンロード</h2>



<p class="wp-block-paragraph">あんしんQRチェッカーは、iOS / Android の両方で利用できます。</p>


<a rel="noopener" target="_blank" href="https://apps.apple.com/jp/app/id6769943765?l=ja" title="あんしんQRチェッカーアプリ - App Store" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/767d374cc8d3bf2656303656b22ad0e5.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">あんしんQRチェッカーアプリ &#8211; App Store</div><div class="blogcard-snippet external-blogcard-snippet">NINJA SYSTEMの「あんしんQRチェッカー」をApp Storeでダウンロードしてください。スクリーンショット、評価とレビュー、ユーザのヒント、「あんしんQRチェッカー」に似たゲームを見ることなどができます。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://apps.apple.com/jp/app/%E3%81%82%E3%82%93%E3%81%97%E3%82%93qr%E3%83%81%E3%82%A7%E3%83%83%E3%82%AB%E3%83%BC/id6769943765" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">apps.apple.com</div></div></div></div></a>

<a rel="noopener" target="_blank" href="https://play.google.com/store/apps/details?id=jp.ninja.qrguard&#038;hl=ja&#038;gl=JP" title="あんしんQRチェッカー - Google Play のアプリ" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://techgrowup.net/wp-content/uploads/cocoon-resources/blog-card-cache/82b5df2c69eca8115138631939cf3f61.jpg" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">あんしんQRチェッカー &#8211; Google Play のアプリ</div><div class="blogcard-snippet external-blogcard-snippet">QR・バーコードを開く前に確認できるリーダー。</div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://play.google.com/store/apps/details?id=jp.ninja.qrguard&#038;hl=ja" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">play.google.com</div></div></div></div></a>


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



<p class="wp-block-paragraph">QRコードは便利ですが、読み取った先を開く前に内容を確認したい場面もあります。</p>



<p class="wp-block-paragraph"><strong>あんしんQRチェッカー</strong> は、QRコードやバーコードを読み取ったあと、URLやテキストを確認してから開けるシンプルなアプリです。</p>



<p class="wp-block-paragraph">普段からQRコードをよく使う方や、知らないQRコードをそのまま開くのが不安な方は、ぜひ使ってみてください。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/qr-code-url-check-app/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>実際に行ってきました！大阪万博2025の日記</title>
		<link>https://techgrowup.net/expo-2025-osaka-0526-review/</link>
					<comments>https://techgrowup.net/expo-2025-osaka-0526-review/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sat, 31 May 2025 06:32:49 +0000</pubDate>
				<category><![CDATA[最新ITニュース]]></category>
		<category><![CDATA[パビリオン]]></category>
		<category><![CDATA[万博グルメ]]></category>
		<category><![CDATA[万博体験談]]></category>
		<category><![CDATA[三菱パビリオン]]></category>
		<category><![CDATA[大屋根リング]]></category>
		<category><![CDATA[大阪万博2025]]></category>
		<category><![CDATA[混雑状況]]></category>
		<category><![CDATA[空飛ぶ車パビリオン]]></category>
		<category><![CDATA[関西万博]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2852</guid>

					<description><![CDATA[日記 5月25日に観光を兼ねて大阪に前泊しました。翌日の万博に備え、梅田エリアのホテルに宿泊しました。5月26日は万博の入場を11時に予約していたので、朝10時頃に梅田駅からOsaka Metro中央線のコスモスクエア駅 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">日記</h2>



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



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



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-1024x576.jpg" alt="" class="wp-image-2854" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014242372-1.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-1024x576.jpg" alt="" class="wp-image-2855" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_014956004.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>
</div>



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



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-1024x576.jpg" alt="" class="wp-image-2856" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_024148995.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



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





<a rel="noopener" target="_blank" href="https://www.expovisitors.expo2025.or.jp/map?_gl=1f3lu2o_gcl_au*MTM0OTAxODYxMC4xNzQ1NjQ0NjM3" title="https://www.expovisitors.expo2025.or.jp/map?_gl=1f3lu2o_gcl_au*MTM0OTAxODYxMC4xNzQ1NjQ0NjM3" class="blogcard-wrap external-blogcard-wrap a-wrap cf"><div class="blogcard external-blogcard eb-left cf"><div class="blogcard-label external-blogcard-label"><span class="fa"></span></div><figure class="blogcard-thumbnail external-blogcard-thumbnail"><img loading="lazy" decoding="async" src="https://s.wordpress.com/mshots/v1/https%3A%2F%2Fwww.expovisitors.expo2025.or.jp%2Fmap%3F_gl%3D1f3lu2o_gcl_au%2AMTM0OTAxODYxMC4xNzQ1NjQ0NjM3?w=320&#038;h=180" alt="" class="blogcard-thumb-image external-blogcard-thumb-image" width="320" height="180" /></figure><div class="blogcard-content external-blogcard-content"><div class="blogcard-title external-blogcard-title">https://www.expovisitors.expo2025.or.jp/map?_gl=1f3lu2o_gcl_au*MTM0OTAxODYxMC4xNzQ1NjQ0NjM3</div><div class="blogcard-snippet external-blogcard-snippet"></div></div><div class="blogcard-footer external-blogcard-footer cf"><div class="blogcard-site external-blogcard-site"><div class="blogcard-favicon external-blogcard-favicon"><img loading="lazy" decoding="async" src="https://www.google.com/s2/favicons?domain=https://www.expovisitors.expo2025.or.jp/map?_gl=1f3lu2o_gcl_au*MTM0OTAxODYxMC4xNzQ1NjQ0NjM3" alt="" class="blogcard-favicon-image external-blogcard-favicon-image" width="16" height="16" /></div><div class="blogcard-domain external-blogcard-domain">www.expovisitors.expo2025.or.jp</div></div></div></div></a>




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



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-1024x576.jpg" alt="" class="wp-image-2858" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030009743-1.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-1024x576.jpg" alt="" class="wp-image-2859" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030936276.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948-576x1024.jpg" alt="" class="wp-image-2860" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948-576x1024.jpg 576w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948-169x300.jpg 169w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948-768x1365.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948-864x1536.jpg 864w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_025613948.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673-576x1024.jpg" alt="" class="wp-image-2861" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673-576x1024.jpg 576w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673-169x300.jpg 169w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673-768x1365.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673-864x1536.jpg 864w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_030336673.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270-576x1024.jpg" alt="" class="wp-image-2862" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270-576x1024.jpg 576w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270-169x300.jpg 169w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270-768x1365.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270-864x1536.jpg 864w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_032710270.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>
</div>



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



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-1024x576.jpg" alt="" class="wp-image-2863" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034326177.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-1024x576.jpg" alt="" class="wp-image-2864" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_034343111.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



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



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-1024x576.jpg" alt="" class="wp-image-2865" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040622474.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-1024x576.jpg" alt="" class="wp-image-2867" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_040556472.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



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



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



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-1024x576.jpg" alt="" class="wp-image-2868" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_055518891.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-1024x576.jpg" alt="" class="wp-image-2869" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060310292.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-1024x576.jpg" alt="" class="wp-image-2870" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_060744009.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-1024x576.jpg" alt="" class="wp-image-2871" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_063452462.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



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



<div class="wp-block-group is-layout-constrained wp-block-group-is-layout-constrained">
<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-1024x576.jpg" alt="" class="wp-image-2872" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072846645.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="576" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-1024x576.jpg" alt="" class="wp-image-2873" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-1024x576.jpg 1024w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-300x169.jpg 300w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-768x432.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-1536x864.jpg 1536w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-120x68.jpg 120w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-160x90.jpg 160w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-320x180.jpg 320w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528-376x212.jpg 376w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_072920528.jpg 1920w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>
</div>



<p class="wp-block-paragraph">帰りの飛行機の時間が迫っていたため、近くのトルコ館とモザンビーク館を訪問してから会場を後にしました。</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-7387b849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154-576x1024.jpg" alt="" class="wp-image-2874" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154-576x1024.jpg 576w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154-169x300.jpg 169w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154-768x1365.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154-864x1536.jpg 864w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080409154.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="576" height="1024" src="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465-576x1024.jpg" alt="" class="wp-image-2875" srcset="https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465-576x1024.jpg 576w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465-169x300.jpg 169w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465-768x1365.jpg 768w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465-864x1536.jpg 864w, https://techgrowup.net/wp-content/uploads/2025/05/PXL_20250526_080450465.jpg 1080w" sizes="(max-width: 576px) 100vw, 576px" /></figure>
</div>
</div>



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



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/expo-2025-osaka-0526-review/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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 class="wp-block-paragraph">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 class="wp-block-paragraph">React のルールオブフックスに違反し、レンダー順序が崩れる可能性があります。必ずトップレベルで呼び、条件分岐は内部で処理を早期リターンするなどで対応しましょう.</p>



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



<p class="wp-block-paragraph">ネットワーク通信は多くの画面で発生するため、抽象化すると保守負荷が激減します。</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 class="wp-block-paragraph">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 class="wp-block-paragraph">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 class="wp-block-paragraph">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 class="wp-block-paragraph"><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 class="wp-block-paragraph">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 class="wp-block-paragraph">並列レンダーではコンポーネントが「途中で止まって再開」される可能性があります。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 class="wp-block-paragraph"><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 class="wp-block-paragraph">重い計算が <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 class="wp-block-paragraph">大規模チームでは「どんな 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 class="wp-block-paragraph">この記事では 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>useContext超大全──React Context APIでグローバル状態をスマートに共有する</title>
		<link>https://techgrowup.net/react-usecontext/</link>
					<comments>https://techgrowup.net/react-usecontext/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Thu, 01 May 2025 23:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Context API]]></category>
		<category><![CDATA[propsドリリング]]></category>
		<category><![CDATA[Provider]]></category>
		<category><![CDATA[useContext]]></category>
		<category><![CDATA[グローバルステート]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2841</guid>

					<description><![CDATA[はじめに React アプリが成長するにつれて、コンポーネント間で共通の値を渡す「props ドリリング」が増え、保守性が下がる課題に直面します。Context API と useContext フックは、この問題を標準 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">はじめに</h1>



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



<h2 class="wp-block-heading">Context API の全体像</h2>



<ul class="wp-block-list">
<li><code>React.createContext</code> で「箱」を作成</li>



<li><strong>Provider</strong> が値を注入</li>



<li><strong>useContext</strong> で最寄りの Provider から値を取得</li>



<li>Provider をネストすれば “局所グローバル” を作れる</li>
</ul>



<p class="wp-block-paragraph">図式化すると次のようになります。</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;Context.Provider value={A}&gt;
  └─ &lt;ChildA /&gt;  ← useContext ⇒ A
      └─ &lt;Context.Provider value={B}&gt;
            └─ &lt;ChildB /&gt;  ← useContext ⇒ 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: #808080">&lt;</span><span style="color: #4EC9B0">Context.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #4FC1FF">A</span><span style="color: #569CD6">}</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">ChildA</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">  ← useContext ⇒ A</span></span>
<span class="line"><span style="color: #D4D4D4">      └─ </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Context.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #4FC1FF">B</span><span style="color: #569CD6">}</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">ChildB</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #D4D4D4">  ← useContext ⇒ B</span></span></code></pre></div>



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



<h2 class="wp-block-heading">props ドリリング問題とContextの必要性</h2>



<h3 class="wp-block-heading">propsドリリングとは？</h3>



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



<h3 class="wp-block-heading">Contextで解決</h3>



<p class="wp-block-paragraph">Providerをルート付近に置き、任意の深さで直接<code>useContext</code>。不要な中継Propsが消え、各コンポーネントが必要な値だけを購読できます。</p>



<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 App() {
  const user = { id: 1, name: 'Taro' };
  return &lt;Page user={user} /&gt;;
}
function Page({ user }) {             /* ← 中継だけ */
  return &lt;Sidebar user={user} /&gt;;
}
function Sidebar({ user }) {          /* ← 中継だけ */
  return &lt;UserProfile user={user} /&gt;;
}
function UserProfile({ user }) {
  return &lt;p&gt;{user.name}&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">App</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: #9CDCFE">id:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">name:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;Taro&#39;</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: #4EC9B0">Page</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">user</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>
<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">Page</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">user</span><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: #4EC9B0">Sidebar</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">user</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>
<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">Sidebar</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">user</span><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: #4EC9B0">UserProfile</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">user</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>
<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">user</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">p</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">user</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</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 style="color: #D4D4D4">;          </span><span style="color: #6A9955">/* ← 本当に必要なのはここだけ */</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">Context を導入すると、中継 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 UserContext = createContext(null);
function App() {
  const user = { id: 1, name: 'Taro' };
  return (
    &lt;UserContext.Provider value={user}&gt;
      &lt;Page /&gt;
    &lt;/UserContext.Provider&gt;
  );
}
function UserProfile() {
  const user = useContext(UserContext);
  return &lt;p&gt;{user.name}&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">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">UserContext</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">);</span></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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">user</span><span style="color: #D4D4D4"> = { </span><span style="color: #9CDCFE">id:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">name:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;Taro&#39;</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">UserContext.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</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: #808080">&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">Page</span><span style="color: #D4D4D4"> </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">UserContext.Provider</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>
<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>
<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: #DCDCAA">useContext</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">UserContext</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">p</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">user</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">Context の作成と Provider 実装</h2>



<h3 class="wp-block-heading">createContext の引数</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="const ThemeContext = React.createContext('light'); // デフォルト値" 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">ThemeContext</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;light&#39;</span><span style="color: #D4D4D4">); </span><span style="color: #6A9955">// デフォルト値</span></span></code></pre></div>



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



<h3 class="wp-block-heading">Provider に渡す value</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="&lt;ThemeContext.Provider value=&quot;dark&quot;&gt;
  &lt;Toolbar /&gt;
&lt;/ThemeContext.Provider&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">ThemeContext.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;dark&quot;</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">Toolbar</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">ThemeContext.Provider</span><span style="color: #808080">&gt;</span></span></code></pre></div>



<p class="wp-block-paragraph">ポイント</p>



<ul class="wp-block-list">
<li>Provider が再レンダーされると <strong>value の参照比較</strong>で下位ツリー全部が再レンダー</li>



<li><code>value</code> がオブジェクトなら <code>useMemo</code> で安定化必須</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 memoValue = useMemo(() =&gt; ({ theme, toggle }), [theme]);" 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">memoValue</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">theme</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">toggle</span><span style="color: #D4D4D4"> }), [</span><span style="color: #9CDCFE">theme</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<h2 class="wp-block-heading">useContext の基本挙動</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="const value = useContext(ThemeContext);" 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">value</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useContext</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">ThemeContext</span><span style="color: #D4D4D4">);</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>最も近い Provider</strong> の value を返す</li>



<li>Provider が見つからない場合は createContext 時のデフォルト値</li>



<li>複数回呼んでも React がキャッシュするためコストは低い</li>
</ul>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><strong>注意</strong>: <code>useContext</code> は必ず React のレンダーフェーズ内で呼ぶ。コールバックや条件分岐で早期 return する位置に入れるとルール違反。</p>
</blockquote>



<h2 class="wp-block-heading">スコープ設計：いつ Provider を分割すべきか</h2>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>ケース</th><th>1 Provider</th><th>複数 Provider</th></tr></thead><tbody><tr><td>テーマ、ロケールなど不変に近い設定</td><td>◯</td><td>△</td></tr><tr><td>頻繁に変わる値（カーソル座標等）</td><td>△</td><td>◯</td></tr><tr><td>複数ドメインデータ（Auth と Cart）</td><td>△</td><td>◯</td></tr></tbody></table></div></figure>



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



<h2 class="wp-block-heading">サンプル：Todo アプリでの Context 分離</h2>



<h3 class="wp-block-heading">1. アプリ共通設定</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="const SettingsContext = createContext({
  showCompleted: true,
  toggleShow: () =&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">SettingsContext</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">showCompleted:</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">toggleShow</span><span style="color: #9CDCFE">:</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></code></pre></div>



<h3 class="wp-block-heading">2. ログインユーザー</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="const AuthContext = createContext({
  user: null,
  login: () =&gt; {},
  logout: () =&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">AuthContext</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">({</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">user:</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">login</span><span style="color: #9CDCFE">:</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">logout</span><span style="color: #9CDCFE">:</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></code></pre></div>



<h3 class="wp-block-heading">3. Todo リスト</h3>



<p class="wp-block-paragraph">todo はサイズが大きく頻繁に変わるため、<strong>Context より useReducer + Context</strong> の複合パターンが適切です（第二部で詳細解説）。</p>



<h2 class="wp-block-heading">パフォーマンス最適化と再レンダー制御</h2>



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



<h3 class="wp-block-heading">1. <code>useMemo</code> で value オブジェクトを安定化</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="const SettingsProvider = ({ children }) =&gt; {
  const [showCompleted, setShowCompleted] = useState(true);
  const ctx = useMemo(
    () =&gt; ({ showCompleted, toggle: () =&gt; setShowCompleted(v =&gt; !v) }),
    [showCompleted]    // プリミティブのみ依存
  );
  return &lt;SettingsContext.Provider value={ctx}&gt;{children}&lt;/SettingsContext.Provider&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: #DCDCAA">SettingsProvider</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">children</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">showCompleted</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setShowCompleted</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">ctx</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> ({ </span><span style="color: #9CDCFE">showCompleted</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">toggle</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">setShowCompleted</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>
<span class="line"><span style="color: #D4D4D4">    [</span><span style="color: #9CDCFE">showCompleted</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: #4EC9B0">SettingsContext.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">ctx</span><span style="color: #569CD6">}</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: #4EC9B0">SettingsContext.Provider</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 class="wp-block-paragraph">オブジェクト内の参照が変わらなければ再レンダーは下位に波及しません。</p>



<h3 class="wp-block-heading">2. Context を分割する</h3>



<p class="wp-block-paragraph"><strong>更新頻度</strong> が異なる値を 1 つの Provider に詰め込むと、滅多に変わらない設定が毎回レンダーのトリガーになります。</p>



<ul class="wp-block-list">
<li><code>AuthContext</code> と <code>ThemeContext</code> を個別 Provider にする</li>



<li>パフォーマンスクリティカルな配列や Map は専用 Context へ切り出す</li>
</ul>



<h3 class="wp-block-heading">3. Context Selector パターン</h3>



<p class="wp-block-paragraph">ライブラリ <code>use-context-selector</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 { createContext, useContextSelector } from 'use-context-selector';

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

function CounterLabel() {
  const count = useContextSelector(CountContext, v =&gt; v.count);
  return &lt;span&gt;{count}&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: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">createContext</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">useContextSelector</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;use-context-selector&#39;</span><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">CountContext</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">count:</span><span style="color: #D4D4D4"> </span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">, </span><span style="color: #DCDCAA">inc</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</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">CounterLabel</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: #DCDCAA">useContextSelector</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">CountContext</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">count</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">span</span><span style="color: #808080">&gt;</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">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>



<p class="wp-block-paragraph">Provider の値がオブジェクト再生成されても <code>count</code> が変わらない限り <code>CounterLabel</code> は再レンダーされません。</p>



<h3 class="wp-block-heading">4. <code>memo</code> + <code>useContext</code> は要注意</h3>



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



<h2 class="wp-block-heading">useReducer と Context を組み合わせたグローバルストア</h2>



<p class="wp-block-paragraph">小規模アプリなら Redux などを導入せずに、<strong><code>useReducer</code> + Context</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="const TodoContext = createContext(null);

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

export function TodoProvider({ children }) {
  const [todos, dispatch] = useReducer(todoReducer, []);
  const ctx = useMemo(() =&gt; ({ todos, dispatch }), [todos]);
  return &lt;TodoContext.Provider value={ctx}&gt;{children}&lt;/TodoContext.Provider&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">TodoContext</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">createContext</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: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">todoReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">switch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;add&#39;</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">action</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">payload</span><span style="color: #D4D4D4">];</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;toggle&#39;</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: #DCDCAA">map</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">t</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">        </span><span style="color: #9CDCFE">t</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4"> === </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">id</span><span style="color: #D4D4D4"> ? { ...</span><span style="color: #9CDCFE">t</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">done:</span><span style="color: #D4D4D4"> !</span><span style="color: #9CDCFE">t</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">done</span><span style="color: #D4D4D4"> } : </span><span style="color: #9CDCFE">t</span></span>
<span class="line"><span style="color: #D4D4D4">      );</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">default</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>
<span class="line"><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: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">TodoProvider</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">todos</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">todoReducer</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">ctx</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">todos</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">dispatch</span><span style="color: #D4D4D4"> }), [</span><span style="color: #9CDCFE">todos</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: #4EC9B0">TodoContext.Provider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">ctx</span><span style="color: #569CD6">}</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: #4EC9B0">TodoContext.Provider</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 class="wp-block-paragraph">呼び出し側は <code>dispatch({ type: 'add', payload })</code> で状態を更新し、<code>todos</code> を購読できます。Redux に比べてボイラープレートが少ないのが利点ですが、<strong>ミドルウェア機構</strong> や <strong>開発ツール</strong> が欲しくなった時点で Redux Toolkit へ移行する判断基準となります。</p>



<h2 class="wp-block-heading">型安全カスタムフックの作成（TypeScript）</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="export const SettingsContext =
  createContext&lt;SettingsCtx | undefined&gt;(undefined);

export function useSettings(): SettingsCtx {
  const ctx = useContext(SettingsContext);
  if (!ctx) {
    throw new Error('useSettings must be inside &lt;SettingsProvider&gt;');
  }
  return ctx;
}" 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">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">SettingsContext</span><span style="color: #D4D4D4"> =</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">createContext</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">SettingsCtx</span><span style="color: #D4D4D4"> | </span><span style="color: #4EC9B0">undefined</span><span style="color: #D4D4D4">&gt;(</span><span style="color: #569CD6">undefined</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">useSettings</span><span style="color: #D4D4D4">(): </span><span style="color: #4EC9B0">SettingsCtx</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">ctx</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useContext</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">SettingsContext</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">ctx</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><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: #CE9178">&#39;useSettings must be inside &lt;SettingsProvider&gt;&#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">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">ctx</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><code>undefined</code> をコンテキスト型に含め、Provider 外使用をコンパイル時に検出</li>



<li>呼び出し側は <code>const { theme } = useSettings();</code> とシンプル</li>
</ul>



<h2 class="wp-block-heading">React 18 コンカレント特有の落とし穴</h2>



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



<ol class="wp-block-list">
<li><strong>Transition</strong> 内で Provider を更新し、旧ツリーが素早く置き換わるようにする</li>



<li><strong>非同期値（サーバフェッチ結果など）</strong> は <code>useSyncExternalStore</code> で同期を取る</li>
</ol>



<h2 class="wp-block-heading">Suspense for Data Fetching と Context</h2>



<p class="wp-block-paragraph">React 18 の <strong>サスペンス対応データフェッチライブラリ</strong>（TanStack Query, SWR v2 等）は、Context でキャッシュを共有する設計が主流です。</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;QueryClientProvider client={client}&gt;  // Provider
  &lt;App /&gt;
&lt;/QueryClientProvider&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">QueryClientProvider</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">client</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">client</span><span style="color: #569CD6">}</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">  // Provider</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #808080">&lt;</span><span style="color: #4EC9B0">App</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">QueryClientProvider</span><span style="color: #808080">&gt;</span></span></code></pre></div>



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



<h2 class="wp-block-heading">テストと Storybook パターン</h2>



<h3 class="wp-block-heading">単体テスト</h3>



<ul class="wp-block-list">
<li>Provider をラップしたテストユーティリティを作成</li>



<li><code>renderHook</code> でカスタムフックのみをテスト</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 wrapper = ({ children }) =&gt; &lt;AuthProvider&gt;{children}&lt;/AuthProvider&gt;;
const { result } = renderHook(() =&gt; useAuth(), { wrapper });" 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: #DCDCAA">wrapper</span><span style="color: #D4D4D4"> = ({ </span><span style="color: #9CDCFE">children</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: #4EC9B0">AuthProvider</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: #4EC9B0">AuthProvider</span><span style="color: #808080">&gt;</span><span style="color: #D4D4D4">;</span></span>
<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">renderHook</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useAuth</span><span style="color: #D4D4D4">(), { </span><span style="color: #9CDCFE">wrapper</span><span style="color: #D4D4D4"> });</span></span></code></pre></div>



<h3 class="wp-block-heading">Storybook</h3>



<ul class="wp-block-list">
<li><code>decorators</code> で Provider を追加し、Context に基づく UI を再現</li>



<li>複数バリエーションを Template で切り替え、locale や theme を擬似表示</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><code>Cannot read property 'xyz' of null</code></td><td>Provider がツリーに含まれていない</td><td>カスタムフックでundefinedチェック</td></tr><tr><td>遅延評価したい値が毎回変わる</td><td>Provider の value を毎レンダー生成</td><td><code>useMemo</code> で固定 or 値を分割Providerへ</td></tr><tr><td>子が不必要に再レンダー</td><td>value がオブジェクトリテラル</td><td><code>useMemo</code> + <code>useCallback</code> で参照安定化</td></tr></tbody></table></div></figure>



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



<p class="wp-block-paragraph">Context の基礎とスコープ設計、パフォーマンス最適化と useReducer 連携・型安全化・並列レンダーの留意点まで解説しました。ここまでを理解すれば、中小規模の React アプリなら Redux 等を導入せずとも十分スケーラブルな状態共有が可能です。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-usecontext/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>useMemo徹底ガイド──Reactアプリの再計算コストを劇的に減らす最適化テクニック</title>
		<link>https://techgrowup.net/react-usememo/</link>
					<comments>https://techgrowup.net/react-usememo/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Wed, 30 Apr 2025 23:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[useCallback]]></category>
		<category><![CDATA[useMemo]]></category>
		<category><![CDATA[パフォーマンス]]></category>
		<category><![CDATA[メモ化]]></category>
		<category><![CDATA[依存配列]]></category>
		<category><![CDATA[再レンダー最適化]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2838</guid>

					<description><![CDATA[useMemoとは何か useMemo は指定した計算式をメモ化（キャッシュ）し、依存配列が変わらない限り再実行をスキップするReactフックです。たとえば数千件のリストを高コストなフィルタリングやソートで描画する場合、 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">useMemoとは何か</h2>



<p class="wp-block-paragraph"><code>useMemo</code> は指定した計算式をメモ化（キャッシュ）し、<strong>依存配列が変わらない限り再実行をスキップ</strong>するReactフックです。たとえば数千件のリストを高コストなフィルタリングやソートで描画する場合、入力が変わらなければ同じ結果を再計算するのはムダです。<code>useMemo</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 { useMemo } from 'react';

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

  return filtered.map(item =&gt; &lt;li key={item.id}&gt;{item.name}&lt;/li&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">useMemo</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">ExpensiveList</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">items</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">filtered</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">items</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">filter</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">item</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">item</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">includes</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: #9CDCFE">items</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">query</span><span style="color: #D4D4D4">]); </span><span style="color: #6A9955">// itemsかqueryが変わったときだけ再計算</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">filtered</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">item</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">item</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">item</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">name</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>
<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="const memoizedValue = useMemo(() =&gt; compute(expensive), [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">memoizedValue</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">compute</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">expensive</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>第1引数</strong>：メモ化したい関数（副作用を含まない純粋関数が望ましい）。</li>



<li><strong>第2引数</strong>：依存配列。ここに列挙した値が変わった場合のみ計算し直す。</li>
</ul>



<p class="wp-block-paragraph">依存配列を空配列 <code>[]</code> にするとマウント時に一回だけ実行。<code>undefined</code> を渡すと毎レンダー実行＝メモ化無効なので注意。</p>



<h2 class="wp-block-heading">useMemoが効く典型シナリオ</h2>



<h3 class="wp-block-heading">1. データ変換・重い計算</h3>



<ul class="wp-block-list">
<li>数学的シミュレーションの結果</li>



<li>JSONデータのディープコピーや正規化</li>



<li>Markdown → HTML 変換など CPUコストが高い処理</li>
</ul>



<h3 class="wp-block-heading">2. フィルタ・ソート・検索</h3>



<p class="wp-block-paragraph">リストの要素数が数百〜数千を超える場合、<code>Array.prototype.filter</code> や <code>sort</code> を毎レンダー走らせるとフレーム落ちの原因になります。</p>



<h3 class="wp-block-heading">3. JSX生成コスト削減</h3>



<p class="wp-block-paragraph">巨大リストを map で JSX に展開するとき、その戻り値をメモ化すると描画コストを下げられます（React 18の並列レンダーでも効果的）。</p>



<h2 class="wp-block-heading">useCallbackとの違い</h2>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>観点</th><th>useMemo</th><th>useCallback</th></tr></thead><tbody><tr><td>目的</td><td><strong>値</strong>をメモ化</td><td><strong>関数参照</strong>をメモ化</td></tr><tr><td>戻り値</td><td>計算結果そのもの</td><td>メモ化済みの関数</td></tr><tr><td>主要用途</td><td>高コストの計算結果を保持</td><td>子コンポーネントへ渡すハンドラの参照固定</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">内部実装では <code>useCallback(fn, deps)</code> は <code>useMemo(() =&gt; fn, deps)</code> と同等です。しかし意味づけが異なるため、値なら <code>useMemo</code>、関数なら <code>useCallback</code> を使うと読みやすくなります。</p>



<h2 class="wp-block-heading">実践シナリオ別コード例</h2>



<h3 class="wp-block-heading">例1：検索フィルタリング</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 SearchableTable({ rows }) {
  const [keyword, setKeyword] = useState('');
  const visibleRows = useMemo(() =&gt; {
    const lower = keyword.toLowerCase();
    return rows.filter(r =&gt; r.name.toLowerCase().includes(lower));
  }, [rows, keyword]);
  return (
    &lt;&gt;
      &lt;input value={keyword} onChange={e =&gt; setKeyword(e.target.value)} /&gt;
      &lt;ul&gt;{visibleRows.map(r =&gt; &lt;li key={r.id}&gt;{r.name}&lt;/li&gt;)}&lt;/ul&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: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">SearchableTable</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">rows</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">keyword</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setKeyword</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">visibleRows</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">lower</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">keyword</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">toLowerCase</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">rows</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">filter</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: #9CDCFE">name</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">toLowerCase</span><span style="color: #D4D4D4">().</span><span style="color: #DCDCAA">includes</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">lower</span><span style="color: #D4D4D4">));</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">rows</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">keyword</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">input</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">keyword</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onChange</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</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: #DCDCAA">setKeyword</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 style="color: #9CDCFE">value</span><span style="color: #D4D4D4">)</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </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">ul</span><span style="color: #808080">&gt;</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">visibleRows</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">name</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 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: #808080">&lt;/&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>rows が巨大でも <code>keyword</code> が変わらなければ再計算なし</li>



<li>入力ごとにフィルタされても実用レベルのパフォーマンス</li>
</ul>



<h3 class="wp-block-heading">例2：高コスト画像処理</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 Preview({ file }) {
  const thumbnail = useMemo(() =&gt; {
    return file ? createThumbnail(file) : null; // Canvas処理など重い関数
  }, [file]);
  return thumbnail ? &lt;img src={thumbnail} /&gt; : null;
}" 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">Preview</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">file</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">thumbnail</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">file</span><span style="color: #D4D4D4"> ? </span><span style="color: #DCDCAA">createThumbnail</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">file</span><span style="color: #D4D4D4">) : </span><span style="color: #569CD6">null</span><span style="color: #D4D4D4">; </span><span style="color: #6A9955">// Canvas処理など重い関数</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">file</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">thumbnail</span><span style="color: #D4D4D4"> ? </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">img</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">src</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">thumbnail</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: #569CD6">null</span><span style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">例3：ソート済みキャッシュ</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="const sorted = useMemo(
  () =&gt; [...items].sort((a, b) =&gt; a.date - b.date),
  [items]
);" 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">sorted</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useMemo</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">  () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> [...</span><span style="color: #9CDCFE">items</span><span style="color: #D4D4D4">].</span><span style="color: #DCDCAA">sort</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: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">a</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4"> - </span><span style="color: #9CDCFE">b</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">date</span><span style="color: #D4D4D4">),</span></span>
<span class="line"><span style="color: #D4D4D4">  [</span><span style="color: #9CDCFE">items</span><span style="color: #D4D4D4">]</span></span>
<span class="line"><span style="color: #D4D4D4">);</span></span></code></pre></div>



<p class="wp-block-paragraph">イミュータブルデータならシャローコピーしてソートし、<code>items</code> が同一参照の間は再計算されません。</p>



<h2 class="wp-block-heading">依存配列設計のコツ</h2>



<ol class="wp-block-list">
<li><strong>リストやオブジェクトは参照が変わる条件を把握</strong>
<ul class="wp-block-list">
<li>Redux/Context でステートが再生成されると参照が毎回変わる ⇒ 先に<code>useSelector(shallowEqual)</code>などで固定化</li>
</ul>
</li>



<li><strong>不要な依存を追加しない</strong>
<ul class="wp-block-list">
<li>計算結果に影響しない値を含めると再実行が増える</li>
</ul>
</li>



<li><strong>入れ忘れはバグの温床</strong>
<ul class="wp-block-list">
<li>ESLintの<code>react-hooks/exhaustive-deps</code>ルールを有効化し、自動検出する</li>
</ul>
</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="// NG: setState関数は安定参照のため依存不要
useMemo(() =&gt; compute(state, setState), [state, setState]); 
// OK
useMemo(() =&gt; compute(state), [state]);" 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">// NG: setState関数は安定参照のため依存不要</span></span>
<span class="line"><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">compute</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">setState</span><span style="color: #D4D4D4">), [</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">setState</span><span style="color: #D4D4D4">]); </span></span>
<span class="line"><span style="color: #6A9955">// OK</span></span>
<span class="line"><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">compute</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">), [</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<h2 class="wp-block-heading">パフォーマンス測定と検証</h2>



<h3 class="wp-block-heading">Profiler API</h3>



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



<li>録画して操作 ⇒ 再計算が走る場所を特定</li>



<li><code>useMemo</code>導入後にレンダー時間が短縮されているか確認</li>
</ol>



<h3 class="wp-block-heading">console.time計測</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="const result = useMemo(() =&gt; {
  console.time('expensive');
  const v = heavy();
  console.timeEnd('expensive');
  return v;
}, [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">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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">console</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">time</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;expensive&#39;</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">v</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">heavy</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">timeEnd</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;expensive&#39;</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">v</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></code></pre></div>



<p class="wp-block-paragraph">メモ化の前後で時間を比較し、効果を数字で把握します。</p>



<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>メモ化を外す or 依存を安定化</td></tr><tr><td>stale data（古い値を参照）</td><td>依存配列に必要な変数がない</td><td>ESLintルールで検出＋修正</td></tr><tr><td>メモリリーク</td><td>大きな配列・Mapをキャッシュして解放しない</td><td>必要に応じて <code>useMemo</code> を解除</td></tr></tbody></table></div></figure>



<h2 class="wp-block-heading">useMemoを使わない方がいい場合</h2>



<ul class="wp-block-list">
<li><strong>計算コスト &lt; メモ化管理コスト</strong></li>



<li><strong>UIがほぼ静的</strong>：再レンダー回数が少ない</li>



<li><strong>依存が頻繁に変わる</strong>：キャッシュがほぼ活きない</li>
</ul>



<p class="wp-block-paragraph">最適化は「必要になったら測定して導入」が鉄則です。</p>



<h2 class="wp-block-heading"><code>useMemo</code> と Suspense / Concurrent Features</h2>



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



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



<p class="wp-block-paragraph"><code>useMemo</code> は</p>



<ol class="wp-block-list">
<li><strong>高コスト計算の結果をキャッシュ</strong>し</li>



<li><strong>依存配列が変わるまで再利用</strong>し</li>



<li><strong>不要な再レンダーを抑制</strong></li>
</ol>



<p class="wp-block-paragraph">というシンプルながら強力な最適化手段です。ただし「とりあえず入れる」は逆効果。まずはプロファイリングでボトルネックを特定し、効果が見込める箇所にのみ導入するのがベストプラクティスです。正しい依存配列と計算粒度を意識し、快適なReact UIを届けましょう。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-usememo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>useReducer完全入門──複雑な状態管理をスマートに解決するReactフックの使い方</title>
		<link>https://techgrowup.net/react-usereducer/</link>
					<comments>https://techgrowup.net/react-usereducer/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Wed, 30 Apr 2025 22:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[dispatch]]></category>
		<category><![CDATA[Reducer]]></category>
		<category><![CDATA[useReducer]]></category>
		<category><![CDATA[コンポーネント設計]]></category>
		<category><![CDATA[状態管理]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2835</guid>

					<description><![CDATA[なぜuseReducerが必要か useStateは直感的で便利ですが、 といった場面では読みづらくなります。こうしたケースでuseReducerを使うと、状態更新ロジックを1カ所に集約でき、コンポーネントがスッキリしま [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">なぜuseReducerが必要か</h2>



<p class="wp-block-paragraph"><code>useState</code>は直感的で便利ですが、</p>



<ul class="wp-block-list">
<li><strong>複数の状態が相互に影響する</strong></li>



<li><strong>更新ロジックが長い if/else で肥大化する</strong></li>



<li><strong>次の状態が前の状態に依存する更新が多い</strong></li>
</ul>



<p class="wp-block-paragraph">といった場面では読みづらくなります。こうしたケースで<code>useReducer</code>を使うと、<strong>状態更新ロジックを1カ所に集約</strong>でき、コンポーネントがスッキリします。</p>



<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="const [state, dispatch] = useReducer(reducer, initialState, init?)" 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">state</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">reducer</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">initialState</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">init</span><span style="color: #D4D4D4">?)</span></span></code></pre></div>



<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><code>reducer</code></td><td><code>(state, action) ⇒ newState</code></td><td>更新ロジックを持つ純粋関数</td></tr><tr><td><code>initialState</code></td><td>任意</td><td>初期値</td></tr><tr><td><code>init</code></td><td><em>(optional)</em> <code>(arg) ⇒ initializedState</code></td><td>遅延初期化用</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph">戻り値</p>



<ul class="wp-block-list">
<li><code>state</code>：現在の状態</li>



<li><code>dispatch</code>：アクションを送る関数</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="import { useReducer } from 'react';

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

export default function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });
  return (
    &lt;&gt;
      &lt;p&gt;Count: {state.count}&lt;/p&gt;
      &lt;button onClick={() =&gt; dispatch({ type: 'increment' })}&gt;＋&lt;/button&gt;
      &lt;button onClick={() =&gt; dispatch({ type: 'decrement' })}&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">useReducer</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">reducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">switch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;increment&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">count:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">state</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;decrement&#39;</span><span style="color: #D4D4D4">: </span><span style="color: #C586C0">return</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">count:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">state</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">default</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>
<span class="line"><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: #C586C0">export</span><span style="color: #D4D4D4"> </span><span style="color: #C586C0">default</span><span style="color: #D4D4D4"> </span><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">state</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">reducer</span><span style="color: #D4D4D4">, { </span><span style="color: #9CDCFE">count:</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">Count: </span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">.</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">dispatch</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">type:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;increment&#39;</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;</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">dispatch</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">type:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;decrement&#39;</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></code></pre></div>



<p class="wp-block-paragraph"><strong>ポイント解説</strong></p>



<ul class="wp-block-list">
<li><code>reducer</code> は <strong>純粋関数</strong>。副作用を入れない</li>



<li><code>dispatch</code> へは <strong>オブジェクト</strong>を渡す（type必須）</li>



<li>UI 側は state を読むだけ＝ロジックが分離され可読性↑</li>
</ul>



<h2 class="wp-block-heading">複数フィールドのフォーム例</h2>



<p class="wp-block-paragraph"><code>useState</code>でそれぞれ管理すると更新がバラけますが、<code>useReducer</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 formReducer(state, action) {
  switch (action.type) {
    case 'change':
      return { ...state, [action.field]: action.value };
    case 'reset':
      return action.initial;
    default:
      return state;
  }
}

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

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

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

  return (
    &lt;form&gt;
      {Object.entries(form).map(([key, val]) =&gt; (
        &lt;input key={key} name={key} value={val} onChange={handleChange} /&gt;
      ))}
      &lt;button onClick={() =&gt; dispatch({ type: 'reset', initial: initialForm })}&gt;
        クリア
      &lt;/button&gt;
    &lt;/form&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">formReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #C586C0">switch</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">type</span><span style="color: #D4D4D4">) {</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;change&#39;</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">[action.field]:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">action</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: #C586C0">case</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;reset&#39;</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">action</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: #C586C0">default</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>
<span class="line"><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">initialForm</span><span style="color: #D4D4D4"> = { </span><span style="color: #9CDCFE">name:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;&#39;</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">password:</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: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">SignupForm</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">form</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">formReducer</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">initialForm</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">handleChange</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">e</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #DCDCAA">dispatch</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">type:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;change&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">field:</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 style="color: #9CDCFE">name</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">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 style="color: #9CDCFE">value</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>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">form</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">Object</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">entries</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">form</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(([</span><span style="color: #9CDCFE">key</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">val</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: #808080">&lt;</span><span style="color: #569CD6">input</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">key</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">name</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">key</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">value</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">val</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onChange</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">handleChange</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span></span>
<span class="line"><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">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">dispatch</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">type:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;reset&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">initial:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">initialForm</span><span style="color: #D4D4D4"> })</span><span style="color: #569CD6">}</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 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;/</span><span style="color: #569CD6">form</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>



<h3 class="wp-block-heading">深掘り</h3>



<ul class="wp-block-list">
<li><code>...state</code> で既存をコピー＝<strong>不変性</strong>を守る</li>



<li><code>reset</code>アクションでワンアクション全項目初期化</li>



<li>大量フィールドでも reducer が変わらないので拡張に強い</li>
</ul>



<h2 class="wp-block-heading">遅延初期化でパフォーマンス改善</h2>



<p class="wp-block-paragraph">重い計算で初期値を作る場合、第三引数 <code>init</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 init(countFromLocal) {
  return { count: countFromLocal };
}
const [state, dispatch] = useReducer(reducer, initialArg, init);" 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">init</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">countFromLocal</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">count:</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">countFromLocal</span><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">state</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">reducer</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">initialArg</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">init</span><span style="color: #D4D4D4">);</span></span></code></pre></div>



<h2 class="wp-block-heading">useState vs useReducer 使い分け指針</h2>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>シチュエーション</th><th>推奨フック</th></tr></thead><tbody><tr><td>1〜2個の単純な値</td><td><code>useState</code></td></tr><tr><td>更新ロジックが直感的</td><td><code>useState</code></td></tr><tr><td>多数のフィールド・複雑な分岐</td><td><code>useReducer</code></td></tr><tr><td>次の状態が前の状態に強く依存</td><td><code>useReducer</code></td></tr><tr><td>Redux 風のアーキテクチャを局所的に実現</td><td><code>useReducer</code></td></tr></tbody></table></div></figure>



<h2 class="wp-block-heading">コンポーネント分割との相性</h2>



<p class="wp-block-paragraph">Reducer を別ファイルに切り出し、複数コンポーネントから共通利用するとReduxライクな構成が得られます。</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="// todoReducer.js
export function todoReducer(state, action) { /* 省略 */ }

// TodoList.jsx
import { todoReducer } from './todoReducer';
const [todos, dispatch] = useReducer(todoReducer, []);" 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">// todoReducer.js</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">todoReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">state</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">action</span><span style="color: #D4D4D4">) { </span><span style="color: #6A9955">/* 省略 */</span><span style="color: #D4D4D4"> }</span></span>
<span class="line"></span>
<span class="line"><span style="color: #6A9955">// TodoList.jsx</span></span>
<span class="line"><span style="color: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">todoReducer</span><span style="color: #D4D4D4"> } </span><span style="color: #C586C0">from</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;./todoReducer&#39;</span><span style="color: #D4D4D4">;</span></span>
<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">dispatch</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useReducer</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">todoReducer</span><span style="color: #D4D4D4">, []);</span></span></code></pre></div>



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



<p class="wp-block-paragraph"><code>useReducer</code>は<strong>状態更新ロジックを一元管理</strong>し、可読性と拡張性を両立させる強力なフックです。</p>



<ol class="wp-block-list">
<li>純粋関数 <code>reducer</code> を定義</li>



<li><code>dispatch</code> で明示的にアクションを送る</li>



<li>ステートが複雑化したら <code>useReducer</code> へ移行</li>
</ol>



<p class="wp-block-paragraph">ロジックが整理されることでバグ発生率が減り、大規模アプリでもスケールしやすいコードベースを実現できます。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-usereducer/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>useRef徹底ガイド──DOMアクセスからミューテーション管理まで</title>
		<link>https://techgrowup.net/react-useref/</link>
					<comments>https://techgrowup.net/react-useref/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Wed, 30 Apr 2025 20:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[DOM参照]]></category>
		<category><![CDATA[forwardRef]]></category>
		<category><![CDATA[ref]]></category>
		<category><![CDATA[useImperativeHandle]]></category>
		<category><![CDATA[useRef]]></category>
		<category><![CDATA[コールバックref]]></category>
		<category><![CDATA[ミューテーション]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2832</guid>

					<description><![CDATA[useRefとは？ ReactのuseRefフックは、レンダー間で変化しても再レンダーを引き起こさない「箱」を提供します。返されるオブジェクトは{ current: 値 }という形で、コンポーネントのライフサイクル全体を [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h2 class="wp-block-heading">useRefとは？</h2>



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



<ol class="wp-block-list">
<li><strong>DOM要素への参照</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="import { useRef, useEffect } from 'react';

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

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

  return &lt;input ref={inputRef} placeholder=&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: #C586C0">import</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">useRef</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">TextInputFocus</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">inputRef</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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: #9CDCFE">inputRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">focus</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: #808080">&lt;</span><span style="color: #569CD6">input</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">inputRef</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">placeholder</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">仕組みと特徴</h2>



<ul class="wp-block-list">
<li><code>useRef(initialValue)</code> はレンダーのたびに <strong>同じオブジェクト</strong> を返す</li>



<li><code>ref.current</code> の変更は React の再レンダーをトリガーしない</li>



<li>オブジェクト参照自体は固定だが <code>current</code> プロパティは自由に書き換え可能</li>
</ul>



<p class="wp-block-paragraph">この振る舞いにより、<strong>状態のように保持しつつUIに影響を与えないデータ</strong> を格納できます。</p>



<h2 class="wp-block-heading">DOMリファレンスの実践例</h2>



<h3 class="wp-block-heading">フォームのフォーカス管理</h3>



<p class="wp-block-paragraph">複数ステップのフォームで次の入力要素へフォーカスを移すとき、<code>useRef</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 MultiStepForm() {
  const step1Ref = useRef(null);
  const step2Ref = useRef(null);
  const [step, setStep] = useState(1);

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

  return (
    &lt;&gt;
      {step === 1 &amp;&amp; &lt;input ref={step1Ref} /&gt;}
      {step === 2 &amp;&amp; &lt;input ref={step2Ref} /&gt;}
      &lt;button onClick={() =&gt; setStep(s =&gt; s + 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: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">MultiStepForm</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">step1Ref</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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">step2Ref</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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">step</span><span style="color: #D4D4D4">, </span><span style="color: #4FC1FF">setStep</span><span style="color: #D4D4D4">] = </span><span style="color: #DCDCAA">useState</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">1</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: #C586C0">if</span><span style="color: #D4D4D4"> (</span><span style="color: #9CDCFE">step</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">step1Ref</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">focus</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">step</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4">) </span><span style="color: #9CDCFE">step2Ref</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">focus</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">step</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>
<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: #569CD6">{</span><span style="color: #9CDCFE">step</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">1</span><span style="color: #D4D4D4"> &amp;&amp; </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">input</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">step1Ref</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</span><span style="color: #569CD6">}</span></span>
<span class="line"><span style="color: #D4D4D4">      </span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">step</span><span style="color: #D4D4D4"> === </span><span style="color: #B5CEA8">2</span><span style="color: #D4D4D4"> &amp;&amp; </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">input</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">step2Ref</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #808080">/&gt;</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">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">setStep</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: #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></code></pre></div>



<h3 class="wp-block-heading">スクロール制御</h3>



<p class="wp-block-paragraph">チャットアプリで新しいメッセージ追加時に最下部までスクロールする例。</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 ChatWindow({ messages }) {
  const bottomRef = useRef(null);

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

  return (
    &lt;div className=&quot;chat-area&quot;&gt;
      {messages.map(m =&gt; &lt;p key={m.id}&gt;{m.text}&lt;/p&gt;)}
      &lt;div ref={bottomRef} /&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">ChatWindow</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">messages</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">bottomRef</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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: #9CDCFE">bottomRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">?.</span><span style="color: #DCDCAA">scrollIntoView</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">behavior:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;smooth&#39;</span><span style="color: #D4D4D4"> });</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">messages</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>
<span class="line"><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;chat-area&quot;</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">messages</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">map</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">m</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">p</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">m</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">m</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">text</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 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">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">bottomRef</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </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">div</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>



<h3 class="wp-block-heading">レンダー回数を数える</h3>



<p class="wp-block-paragraph">状態にすると無限ループするような値は <code>useRef</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 RenderCount() {
  const countRef = useRef(0);
  useEffect(() =&gt; { countRef.current += 1 });
  return &lt;p&gt;レンダー回数: {countRef.current}&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">RenderCount</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">countRef</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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: #DCDCAA">useEffect</span><span style="color: #D4D4D4">(() </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">countRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</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 style="color: #C586C0">return</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: #569CD6">{</span><span style="color: #9CDCFE">countRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h3 class="wp-block-heading">setIntervalのIDを保持</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 Timer() {
  const intervalRef = useRef(null);
  const [count, setCount] = useState(0);

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

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

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

  return &lt;&gt;
    &lt;p&gt;{count} 秒経過&lt;/p&gt;
    &lt;button onClick={start}&gt;開始&lt;/button&gt;
    &lt;button onClick={stop}&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: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Timer</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">intervalRef</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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">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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">start</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">intervalRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</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: #9CDCFE">intervalRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</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">setCount</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">c</span><span style="color: #D4D4D4"> + </span><span style="color: #B5CEA8">1</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>
<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">stop</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">clearInterval</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">intervalRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">);</span></span>
<span class="line"><span style="color: #D4D4D4">    </span><span style="color: #9CDCFE">intervalRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</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>
<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 style="color: #9CDCFE">stop</span><span style="color: #D4D4D4">, []); </span><span style="color: #6A9955">// アンマウント時に停止</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: #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: #569CD6">{</span><span style="color: #9CDCFE">count</span><span style="color: #569CD6">}</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: #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">start</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;</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">stop</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



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



<p class="wp-block-paragraph">親コンポーネントから子のDOMへ直接アクセスする場合は <code>forwardRef</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="const FancyInput = React.forwardRef((props, ref) =&gt; {
  return &lt;input className=&quot;fancy&quot; ref={ref} {...props} /&gt;;
});

function Parent() {
  const inputRef = useRef(null);
  return &lt;&gt;
    &lt;FancyInput ref={inputRef} /&gt;
    &lt;button onClick={() =&gt; inputRef.current.focus()}&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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">FancyInput</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">forwardRef</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">ref</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">return</span><span style="color: #D4D4D4"> </span><span style="color: #808080">&lt;</span><span style="color: #569CD6">input</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">className</span><span style="color: #D4D4D4">=</span><span style="color: #CE9178">&quot;fancy&quot;</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: #D4D4D4"> </span><span style="color: #569CD6">{</span><span style="color: #D4D4D4">...</span><span style="color: #9CDCFE">props</span><span style="color: #569CD6">}</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>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">Parent</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">inputRef</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useRef</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: #C586C0">return</span><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: #4EC9B0">FancyInput</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">inputRef</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </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: #9CDCFE">inputRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">focus</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">useImperativeHandleで公開APIを制御</h2>



<p class="wp-block-paragraph">子コンポーネント側で <code>ref</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="const Canvas = React.forwardRef((props, ref) =&gt; {
  const canvasRef = useRef();
  useImperativeHandle(ref, () =&gt; ({
    clear: () =&gt; canvasRef.current.getContext('2d').clearRect(0,0,300,150)
  }));
  return &lt;canvas ref={canvasRef} width={300} height={150} /&gt;;
});

function DrawingApp() {
  const canvasRef = useRef();
  return &lt;&gt;
    &lt;Canvas ref={canvasRef} /&gt;
    &lt;button onClick={() =&gt; canvasRef.current.clear()}&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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">Canvas</span><span style="color: #D4D4D4"> = </span><span style="color: #9CDCFE">React</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">forwardRef</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">props</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">ref</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">canvasRef</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">useImperativeHandle</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">ref</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">clear</span><span style="color: #9CDCFE">:</span><span style="color: #D4D4D4"> () </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">canvasRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">getContext</span><span style="color: #D4D4D4">(</span><span style="color: #CE9178">&#39;2d&#39;</span><span style="color: #D4D4D4">).</span><span style="color: #DCDCAA">clearRect</span><span style="color: #D4D4D4">(</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">,</span><span style="color: #B5CEA8">0</span><span style="color: #D4D4D4">,</span><span style="color: #B5CEA8">300</span><span style="color: #D4D4D4">,</span><span style="color: #B5CEA8">150</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">canvas</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">canvasRef</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">width</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #B5CEA8">300</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">height</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #B5CEA8">150</span><span style="color: #569CD6">}</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>
<span class="line"><span style="color: #569CD6">function</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">DrawingApp</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">canvasRef</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: #C586C0">return</span><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: #4EC9B0">Canvas</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">canvasRef</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </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: #9CDCFE">canvasRef</span><span style="color: #D4D4D4">.</span><span style="color: #9CDCFE">current</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">clear</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<h2 class="wp-block-heading">コールバックrefとの比較</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>string ref</td><td><code>ref="myRef"</code></td><td>レガシー、廃止予定</td></tr><tr><td>callback ref</td><td><code>ref={node =&gt; { ... }}</code></td><td>動的に複数refや条件分岐を管理</td></tr><tr><td>useRef + ref属性</td><td><code>const r=useRef(); … ref={r}</code></td><td>一般的。オブジェクトに一度だけセット</td></tr></tbody></table></div></figure>



<h2 class="wp-block-heading">パフォーマンスに与える影響</h2>



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



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



<ol class="wp-block-list">
<li><strong>refがnullのままアクセス</strong>
<ul class="wp-block-list">
<li>初回レンダー直後はまだDOMがセットされておらずnull。<code>useEffect</code> の中で読む。</li>
</ul>
</li>



<li><strong>currentを書き換えても画面が更新されない</strong>
<ul class="wp-block-list">
<li><code>useRef</code>は状態管理ではない。UIに反映が必要なら <code>useState</code> を使う。</li>
</ul>
</li>



<li><strong>依存配列にref.currentを入れてしまう</strong>
<ul class="wp-block-list">
<li><code>ref.current</code> の値が変わっても依存配列は検知しない。監視には別状態が必要。</li>
</ul>
</li>
</ol>



<h2 class="wp-block-heading">総括</h2>



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



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-useref/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ReactのuseCallback徹底ガイド──依存配列・再レンダリング制御・パフォーマンス最適化の基本と応用</title>
		<link>https://techgrowup.net/react-usecallback/</link>
					<comments>https://techgrowup.net/react-usecallback/?noamp=mobile#respond</comments>
		
		<dc:creator><![CDATA[techgrowup]]></dc:creator>
		<pubDate>Sun, 27 Apr 2025 23:00:00 +0000</pubDate>
				<category><![CDATA[React]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Hooks]]></category>
		<category><![CDATA[useCallback]]></category>
		<category><![CDATA[パフォーマンス]]></category>
		<category><![CDATA[依存配列]]></category>
		<category><![CDATA[再レンダリング]]></category>
		<category><![CDATA[再利用]]></category>
		<category><![CDATA[最適化]]></category>
		<guid isPermaLink="false">https://techgrowup.net/?p=2827</guid>

					<description><![CDATA[はじめに Reactの関数コンポーネントで副作用や再レンダリング制御を扱う際、useCallback フックはしばしば「面倒くさい」「よくわからない」と敬遠されがちです。しかし、大規模アプリのパフォーマンスチューニングや [&#8230;]]]></description>
										<content:encoded><![CDATA[
<h1 class="wp-block-heading">はじめに</h1>



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



<h2 class="wp-block-heading">useCallbackとは？</h2>



<p class="wp-block-paragraph"><code>useCallback</code> は「関数をメモ化（キャッシュ）する」ための React フックです。コンポーネントが再レンダーされるたびに新しい関数インスタンスを生成すると、子コンポーネントに渡したコールバックが”変化”とみなされ、不要な再レンダーにつながります。これを防ぐのが <code>useCallback</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="const memoizedHandler = useCallback(() =&gt; {
  console.log('クリックされました');
}, [依存値1, 依存値2]);" 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">memoizedHandler</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>
<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: #9CDCFE">依存値1</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">依存値2</span><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>



<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 useCallback&lt;T extends (...args: any[]) =&gt; any&gt;(
  callback: T,
  deps: DependencyList
): T;" 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">useCallback</span><span style="color: #D4D4D4">&lt;</span><span style="color: #4EC9B0">T</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">extends</span><span style="color: #D4D4D4"> (...</span><span style="color: #9CDCFE">args</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">any</span><span style="color: #D4D4D4">[]) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #4EC9B0">any</span><span style="color: #D4D4D4">&gt;(</span></span>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #9CDCFE">callback</span><span style="color: #D4D4D4">: </span><span style="color: #4EC9B0">T</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 style="color: #4EC9B0">DependencyList</span></span>
<span class="line"><span style="color: #D4D4D4">): </span><span style="color: #4EC9B0">T</span><span style="color: #D4D4D4">;</span></span></code></pre></div>



<ul class="wp-block-list">
<li><strong>T</strong>：任意の関数型</li>



<li><strong>DependencyList</strong>：<code>any[]</code> 型の配列</li>



<li><strong>戻り値</strong>：渡した <code>callback</code> と同じ型の“メモ化された関数”</li>
</ul>



<p class="wp-block-paragraph">内部では React が Hook ごとに前回の <code>callback</code> 参照を保持し、<code>deps</code> が完全一致する限り同じ関数を返します。</p>



<h2 class="wp-block-heading">依存配列の正しい書き方</h2>



<p class="wp-block-paragraph">依存配列を適切に指定しないと、次のような問題が起きます。</p>



<figure class="wp-block-table"><div class="scrollable-table"><table class="has-fixed-layout"><thead><tr><th>誤り</th><th>症状</th></tr></thead><tbody><tr><td>依存配列を空にする (<code>[]</code>)</td><td>最新の外部変数を参照できない （stale closure）</td></tr><tr><td>依存を省略 (<code>undefined</code>)</td><td>毎回新しい関数が作られ、効果なし</td></tr><tr><td>必要な依存を含めない</td><td>古い値を使い続ける</td></tr></tbody></table></div></figure>



<p class="wp-block-paragraph"><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 Counter() {
  const [count, setCount] = useState(0);

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

  return &lt;button onClick={handler}&gt;ログ出力&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: #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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">handler</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>
<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">count</span><span style="color: #D4D4D4">); </span><span style="color: #6A9955">// 依存にcountを含めないと常に0が表示</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: #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">handler</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 style="color: #D4D4D4">;</span></span>
<span class="line"><span style="color: #D4D4D4">}</span></span></code></pre></div>



<p class="wp-block-paragraph">上記では <code>count</code> が変わっても <code>handler</code> 内の <code>count</code> は初回のまま。依存配列には常に<strong>最新のcount</strong>を正しく指定しましょう。</p>



<h2 class="wp-block-heading">useCallbackとuseMemoの違い</h2>



<ul class="wp-block-list">
<li><strong>useCallback</strong>：関数をメモ化する</li>



<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 memoizedValue = useMemo(() =&gt; computeExpensive(a, b), [a, b]);
const memoizedHandler = useCallback(() =&gt; doSomething(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">memoizedValue</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">computeExpensive</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>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">memoizedHandler</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: #DCDCAA">doSomething</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>



<p class="wp-block-paragraph"><code>useCallback(fn, deps)</code> は <code>useMemo(() =&gt; fn, deps)</code> のエイリアスと考えることもできますが、可読性の観点から関数キャッシュには <code>useCallback</code> を使うのが一般的です。</p>



<ol class="wp-block-list">
<li>コンポーネント初回レンダー</li>



<li><code>useCallback</code> が新しい関数を生成し返却</li>



<li>次回レンダー
<ul class="wp-block-list">
<li><code>deps</code> が同じ → 前回の関数を再利用</li>



<li><code>deps</code> が変わった → 新しい関数を生成</li>
</ul>
</li>



<li>子コンポーネントへ渡す際、関数参照の変化を検出してレンダー制御できる</li>
</ol>



<p class="wp-block-paragraph">この仕組みを利用し、<code>React.memo</code> と組み合わせることで子コンポーネントの再レンダーを抑制できます。</p>



<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="const Child = React.memo(({ onAction }) =&gt; {
  console.log('Child render');
  return &lt;button onClick={onAction}&gt;アクション&lt;/button&gt;;
});

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

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

  return (
    &lt;&gt;
      &lt;p&gt;Count: {count}&lt;/p&gt;
      &lt;button onClick={() =&gt; setCount(c =&gt; c + 1)}&gt;増やす&lt;/button&gt;
      &lt;Child onAction={handleAction} /&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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">Child</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: #9CDCFE">onAction</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;Child render&#39;</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">onAction</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 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">Parent</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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">handleAction</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>
<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;Action!&#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>
<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">Count: </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">c</span><span style="color: #D4D4D4"> </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">c</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;</span><span style="color: #4EC9B0">Child</span><span style="color: #D4D4D4"> </span><span style="color: #9CDCFE">onAction</span><span style="color: #D4D4D4">=</span><span style="color: #569CD6">{</span><span style="color: #9CDCFE">handleAction</span><span style="color: #569CD6">}</span><span style="color: #D4D4D4"> </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></code></pre></div>



<ul class="wp-block-list">
<li><code>Parent</code> が再レンダーされても <code>handleAction</code> は同じ参照</li>



<li><code>Child</code> は Props が変わらないとレンダーされないため高速</li>
</ul>



<h2 class="wp-block-heading">使わないほうがいいケース</h2>



<ul class="wp-block-list">
<li><strong>軽量コンポーネント／関数</strong><br>関数の生成コストよりReactのレンダーコストのほうが低い場合</li>



<li><strong>短命な値</strong><br>毎回異なる依存配列を生成すると逆効果</li>



<li><strong>過度のメモ化</strong><br>コードが複雑になり可読性や保守性が低下</li>
</ul>



<p class="wp-block-paragraph"><strong>判断基準</strong>：<br>パフォーマンス問題が「本当に関数参照の変化」に起因しているかプロファイラで確認しましょう。</p>



<h2 class="wp-block-heading">複数フックとの連携パターン</h2>



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



<p class="wp-block-paragraph">エフェクト内で関数を参照する場合、依存配列に含めないと stale closure が起きます。</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 Search({ query }) {
  const [results, setResults] = useState([]);
  const fetchResults = useCallback(async () =&gt; {
    const res = await fetch(`/api?q=${query}`);
    setResults(await res.json());
  }, [query]);

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

  return /* 結果表示 */;
}" 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">Search</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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">fetchResults</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(</span><span style="color: #569CD6">async</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">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: #CE9178">`/api?q=</span><span style="color: #569CD6">${</span><span style="color: #9CDCFE">query</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">setResults</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: #9CDCFE">query</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: #DCDCAA">fetchResults</span><span style="color: #D4D4D4">();</span></span>
<span class="line"><span style="color: #D4D4D4">  }, [</span><span style="color: #9CDCFE">fetchResults</span><span style="color: #D4D4D4">]); </span><span style="color: #6A9955">// fetchResults を正しく依存に含める</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: #6A9955">/* 結果表示 */</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>



<p class="wp-block-paragraph">関数による計算結果をさらにメモ化したい場合</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 expensiveCompute = useCallback((x) =&gt; heavyCalc(x), []);
const memoizedResult = useMemo(() =&gt; expensiveCompute(input), [input, expensiveCompute]);" 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">expensiveCompute</span><span style="color: #D4D4D4"> = </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">((</span><span style="color: #9CDCFE">x</span><span style="color: #D4D4D4">) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">heavyCalc</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">x</span><span style="color: #D4D4D4">), []);</span></span>
<span class="line"><span style="color: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">memoizedResult</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">expensiveCompute</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">input</span><span style="color: #D4D4D4">), [</span><span style="color: #9CDCFE">input</span><span style="color: #D4D4D4">, </span><span style="color: #9CDCFE">expensiveCompute</span><span style="color: #D4D4D4">]);</span></span></code></pre></div>



<p class="wp-block-paragraph"><code>useCallback</code> でメモ化された関数をテストする際は、<code>renderHook</code> と <code>act</code> を使うと楽です。</p>



<h2 class="wp-block-heading">コールバック関数のテスト</h2>



<p class="wp-block-paragraph"><code>useCallback</code> でメモ化された関数をテストする際は、<code>renderHook</code> と <code>act</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 { renderHook, act } from '@testing-library/react-hooks';

test('fetchResults が更新時に変化する', async () =&gt; {
  const { result, rerender } = renderHook(
    ({ query }) =&gt; useCallback(/*…*/, [query]),
    { initialProps: { query: 'a' } }
  );
  const first = result.current;
  rerender({ query: 'b' });
  expect(result.current).not.toBe(first);
});" 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-hooks&#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;fetchResults が更新時に変化する&#39;</span><span style="color: #D4D4D4">, </span><span style="color: #569CD6">async</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: #4FC1FF">rerender</span><span style="color: #D4D4D4"> } = </span><span style="color: #DCDCAA">renderHook</span><span style="color: #D4D4D4">(</span></span>
<span class="line"><span style="color: #D4D4D4">    ({ </span><span style="color: #9CDCFE">query</span><span style="color: #D4D4D4"> }) </span><span style="color: #569CD6">=&gt;</span><span style="color: #D4D4D4"> </span><span style="color: #DCDCAA">useCallback</span><span style="color: #D4D4D4">(</span><span style="color: #6A9955">/*…*/</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: #9CDCFE">initialProps:</span><span style="color: #D4D4D4"> { </span><span style="color: #9CDCFE">query:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;a&#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: #569CD6">const</span><span style="color: #D4D4D4"> </span><span style="color: #4FC1FF">first</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>
<span class="line"><span style="color: #D4D4D4">  </span><span style="color: #DCDCAA">rerender</span><span style="color: #D4D4D4">({ </span><span style="color: #9CDCFE">query:</span><span style="color: #D4D4D4"> </span><span style="color: #CE9178">&#39;b&#39;</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">not</span><span style="color: #D4D4D4">.</span><span style="color: #DCDCAA">toBe</span><span style="color: #D4D4D4">(</span><span style="color: #9CDCFE">first</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>



<ul class="wp-block-list">
<li><strong>Profiler API</strong> で実際のレンダー回数を計測</li>



<li><strong>React DevTools</strong> のProfilerタブでコールバック関数生成の影響を見る</li>



<li><code>useCallback</code> 無し / 有りでどれだけ再レンダーが減るかを数値で比較</li>
</ul>



<h2 class="wp-block-heading">よくある誤解とQ&amp;A</h2>



<ol class="wp-block-list">
<li><strong>「depsが空なら一度だけ実行される」</strong><br>→ <code>useCallback</code> では「関数参照が一度だけ生成される」。 <code>useEffect([], …)</code> とは別。</li>



<li><strong>「関数を渡すだけで最適化される」</strong><br>→ 子コンポーネントも <code>React.memo</code> などで参照比較しなければ効果なし。</li>



<li><strong>「常に使えば良い」</strong><br>→ 過度のメモ化は逆効果。必要な箇所だけに絞りましょう。</li>
</ol>



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



<p class="wp-block-paragraph"><code>useCallback</code> は関数インスタンスの再生成を防ぎ、再レンダーを制御する強力なフックです。依存配列の正確な管理、<code>React.memo</code> との併用、過度なメモ化の回避などポイントを押さえれば、アプリ全体のパフォーマンスが向上します。まずはパフォーマンスプロファイリングを行い、本当に必要な箇所だけに導入してみてください。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techgrowup.net/react-usecallback/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-06-13 03:18:17 by W3 Total Cache
-->