

<?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>React &#8211; Max的程式語言筆記</title>
	<atom:link href="https://stackoverflow.max-everyday.com/tag/react/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Thu, 18 Jun 2026 02:01:01 +0000</lastBuildDate>
	<language>zh-TW</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://stackoverflow.max-everyday.com/wp-content/uploads/2017/02/max-stackoverflow-256.png</url>
	<title>React &#8211; Max的程式語言筆記</title>
	<link>https://stackoverflow.max-everyday.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Vite 打包的大坑：為什麼測試環境 staging 默默變成了 production ？</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 02:00:50 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8568</guid>

					<description><![CDATA[當我們在開發前端專案時，常常會遇到明明在本地開發...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-1024x572.jpg?v=1781748044" alt="" class="wp-image-8570" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-1024x572.jpg?v=1781748044 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-600x335.jpg?v=1781748044 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean-768x429.jpg?v=1781748044 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/vite-staging-production-build-check_clean.jpg?v=1781748044 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">當我們在開發前端專案時，常常會遇到明明在本地開發測試都好好的，但一打包部署到測試環境（ staging ），某些功能就莫名其妙失蹤的靈異事件！</p>



<p class="wp-block-paragraph">這次苦主遇到的狀況是：使用 pnpm build:staging 指令打包後，左側導覽列的「管理員選單（ isAdmin ）」居然直接蒸發了！</p>



<p class="wp-block-paragraph">經過一番鍵盤柯南式的徹查，終於抓到幕後黑手，原來是我們自以為很懂的 Vite 在搞鬼。</p>



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



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f50d.png" alt="🔍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 案發現場：為什麼選單不見了？</h2>



<p class="wp-block-paragraph">在原本的程式碼中，開發者用了 import.meta.env.PROD 來做判斷。心裡想著：「開發環境（ dev ）是 false ，測試環境（ staging ）應該也是 false ，只有真正的正式環境（ production ）才會是 true 吧？」</p>



<p class="wp-block-paragraph">結果 Vite 表示：「你想得美！」</p>



<p class="wp-block-paragraph">我們來看看 Vite 在不同指令下的真實真面目：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>執行的指令</strong></td><td><strong>MODE 變數</strong></td><td><strong>PROD 變數（ 關鍵在這裡 ）</strong></td><td><strong>導致的後果</strong></td></tr></thead><tbody><tr><td>vite (dev)</td><td>development</td><td>false</td><td>順利繞過檢查，選單正常顯示。</td></tr><tr><td>vite build &#8211;mode staging</td><td>staging</td><td><strong>true</strong> <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6a8.png" alt="🚨" class="wp-smiley" style="height: 1em; max-height: 1em;" /></td><td>誤判為正式環境，跑去呼叫真實 API 檢查，結果因為 IP 不對直接被拒絕，選單慘遭隱形。</td></tr><tr><td>vite build</td><td>production</td><td>true</td><td>正常的正式環境流程。</td></tr></tbody></table></figure>



<p class="wp-block-paragraph"><strong>破案關鍵</strong>：在 Vite 的世界裡，只要你執行了 vite build 這個打包指令，不管後面帶的 &#8211;mode 是什麼，它都會偷偷把 NODE_ENV 設定為 production 。因此， import.meta.env.PROD 在測試環境下也會被無情地判定為 true ！</p>



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



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6e0.png" alt="🛠" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 拯救選單：動手修正它</h2>



<p class="wp-block-paragraph">既然知道了 Vite 的脾氣，修正的方法就很簡單了。我們不要再相信傲嬌的 PROD 變數，直接去檢查 MODE 的名字是不是叫做 production 就好了！</p>



<h3 class="wp-block-heading">程式碼改動</h3>



<p class="wp-block-paragraph">請把原本的防禦邏輯修改如下：</p>



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



<pre class="wp-block-code"><code>// &#x274c; 原本的寫法（ 會誤判測試環境 ）
if (!import.meta.env.PROD) { ... }

//  修正後的寫法（ 精準識別是不是真的正式環境 ）
if (import.meta.env.MODE !== 'production') { ... }
</code></pre>



<p class="wp-block-paragraph">同時，別忘了同步更新專案的 README.md 說明文件，讓未來的自己或其他同事不會再踩進同一個坑裡。</p>



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



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f389.png" alt="🎉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 修正後的快樂結局</h2>



<p class="wp-block-paragraph">改用 import.meta.env.MODE 來把關之後，判斷邏輯就變得非常聽話了：</p>



<ul class="wp-block-list">
<li>pnpm run dev <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 development <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 順利放行，直接顯示選單！</li>



<li>pnpm run build:staging <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 staging <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 順利放行，直接顯示選單！</li>



<li>pnpm run build <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 模式為 production <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 嚴格把關，呼叫 API 檢查！</li>
</ul>



<p class="wp-block-paragraph">現在重新執行 pnpm build:staging 打包，那個令人朝思暮想的管理員選單終於乖乖下凡，回歸到它該出現的地方囉！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/vite-staging-production-build-check/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React Query 在處理即時更新快取（refetchOnWindowFocus + invalidate logs）</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/react-tanstack-query/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/react-tanstack-query/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 06:26:42 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8547</guid>

					<description><![CDATA[請 AI 在寫程式, 順便學一下 React 怎...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">請 AI 在寫程式, 順便學一下 React 怎麼更新快取, 這個異動之前, 需要使用者手動 reload page 才能取得正確資訊.</p>



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



<pre class="wp-block-code"><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">        await queryClient.invalidateQueries({ queryKey: &#91;'/api/suggestions/'], exact: false });</mark>
<mark style="background-color:rgba(0, 0, 0, 0);color:#1f7300" class="has-inline-color">        // Use predicate because the key is a single string '/api/suggestions/{id}/logs'
        // and partial array prefix matching would not reach it.
        await queryClient.invalidateQueries({
            predicate: (query) =>
                typeof query.queryKey&#91;0] === 'string' &amp;&amp;
                (query.queryKey&#91;0] as string).endsWith('/logs'),
        });</mark></code></pre>



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



<pre class="wp-block-code"><code><mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-vivid-red-color">    // The detail page may already be open (same SPA), so refetchOnWindowFocus won't fire.
    await queryClient.invalidateQueries({ queryKey: &#91;'/api/suggestions/'], exact: false });</mark><mark style="background-color:rgba(0, 0, 0, 0);color:#1f7300" class="has-inline-color">
    await queryClient.invalidateQueries({
        predicate: (query) =>
            typeof query.queryKey&#91;0] === 'string' &amp;&amp;
            (query.queryKey&#91;0] as string).endsWith('/logs'),
    });</mark></code></pre>



<p class="wp-block-paragraph">上面的紅色刪除 code, 綠色是新增的 code.</p>



<p class="wp-block-paragraph">這段程式碼之所以能達成<strong>即時更新快取</strong>，關鍵在於它修正了 <strong>TanStack Query (React Query)</strong> 的快取清除機制，從原本失效的「前綴匹配」改為精準的「條件篩選」。</p>



<p class="wp-block-paragraph">以下為您詳細拆解它的運作原理，以及快取內容是如何重新取得的：</p>



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



<h2 class="wp-block-heading">1. 為什麼這樣子做，可以即時更新快取？</h2>



<p class="wp-block-paragraph">在原先的程式碼中，開發者試圖使用以下方式來讓快取失效：</p>



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



<pre class="wp-block-code"><code>// 舊寫法：無效
await queryClient.invalidateQueries({ queryKey: &#91;'/api/suggestions/'], exact: false })
</code></pre>



<h3 class="wp-block-heading">為什麼舊寫法會失敗？</h3>



<p class="wp-block-paragraph">React Query 在處理 <code>queryKey</code> 陣列時，預設的模糊匹配（<code>exact: false</code>）是比對<strong>陣列中的元素元素（Array Elements）</strong>。</p>



<p class="wp-block-paragraph">如果你的 API 快取鍵值（Query Key）在設計時是一個<strong>單一字串</strong>（例如：<code>'/api/suggestions/{id}/logs'</code>），React Query 會認為這個陣列裡只有一個元素。它在比對時，會檢查 <code>'/api/suggestions/{id}/logs'</code> 是否「完全等於」或「為陣列前綴」，但它<strong>不會</strong>去把字串拆開做子字串匹配。因此，舊寫法完全找不到對應的快取，自然無法更新。</p>



<h3 class="wp-block-heading">為什麼新寫法（Predicate）可以成功？</h3>



<p class="wp-block-paragraph">新程式碼改用 <code>predicate</code> 自訂篩選函式：</p>



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



<pre class="wp-block-code"><code>// 新寫法：成功
await queryClient.invalidateQueries({
  predicate: (query) =&gt;
    typeof query.queryKey&#91;0] === 'string' &amp;&amp;
    (query.queryKey&#91;0] as string).endsWith('/logs'),
})
</code></pre>



<ul class="wp-block-list">
<li><strong>主動巡檢</strong>：<code>predicate</code> 就像是一個過濾器，它會遍歷目前瀏覽器記憶體中<strong>所有</strong>現存的 React Query 快取。</li>



<li><strong>精準識別</strong>：不論 <code>{id}</code> 是多少（例如 <code>/api/suggestions/123/logs</code> 或 <code>/api/suggestions/456/logs</code>），只要快取鍵值的第 0 個元素是字串，且結尾是 <code>'/logs'</code>，就會被強制貼上「<strong>已過期（Stale）</strong>」的標籤。</li>
</ul>



<p class="wp-block-paragraph">透過這種方式，所有相關的審核紀錄快取（Suggestion Logs）就會在代理人變更的當下<strong>立刻失效</strong>，迫使畫面準備更新。</p>



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



<h2 class="wp-block-heading">2. 快取內容又是如何去取得（重新發送請求）？</h2>



<p class="wp-block-paragraph">當 <code>invalidateQueries</code> 成功將這些快取標記為「已過期（Stale）」後，React Query 會根據<strong>當前頁面的狀態</strong>，自動透過以下邏輯去重新取得（Refetch）最新的資料：</p>



<h3 class="wp-block-heading">狀況 A：該資料正顯示在當前畫面上（作用中的 Query）</h3>



<p class="wp-block-paragraph">如果使用者正停留在該審核紀錄的頁面上（該 Query 處於 <code>active</code> 狀態），一旦 <code>invalidateQueries</code> 被執行，React Query 會<strong>立刻在背景自動觸發對應的 API 請求</strong>（例如呼叫後端後台 <code>GET /api/suggestions/{id}/logs</code>），取得最新含有新代理人資訊（<code>approvalProgress.reviewer_delegate_id</code>）的資料，並在收到回應後即時渲染、更新畫面。</p>



<h3 class="wp-block-heading">狀況 B：該資料目前沒顯示在畫面上（非作用中的快取）</h3>



<p class="wp-block-paragraph">如果使用者目前在其他分頁，這些被標記為過期的快取會先靜靜地待在記憶體中。直到使用者切換回該詳細頁面、畫面重新掛載（Mount）該元件時，React Query 偵測到「快取已過期」，就會在初始化時立刻發送 API 請求撈取最新資料，確保使用者看到的絕對不會是舊代理人的髒資料（Dirty Data）。</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/react-tanstack-query/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>React（以及現代網頁開發）的語境中，SPA 指的是 Single Page Application（單頁應用程式）</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/react-spa/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/react-spa/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Tue, 16 Jun 2026 05:20:13 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8543</guid>

					<description><![CDATA[什麼是 SPA？ 傳統的網站（Multi-Pag...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="559" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/react-spa_clean-1024x559.jpg?v=1781587172" alt="" class="wp-image-8544" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/react-spa_clean-1024x559.jpg?v=1781587172 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/react-spa_clean-600x327.jpg?v=1781587172 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/react-spa_clean-768x419.jpg?v=1781587172 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/react-spa_clean.jpg?v=1781587172 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h3 class="wp-block-heading">什麼是 SPA？</h3>



<p class="wp-block-paragraph">傳統的網站（Multi-Page Application, MPA）在你點擊連結、切換頁面時，瀏覽器會向伺服器重新請求一個全新的 HTML 檔案，畫面會整個閃一下、重新載入。</p>



<p class="wp-block-paragraph">而 React 打造的 <strong>SPA</strong> 正好相反：</p>



<ul class="wp-block-list">
<li><strong>只有一個 HTML 檔案：</strong> 瀏覽器只在第一次進入網站時，下載一個基本上是空白的 <code>index.html</code> 和一大包 JavaScript（React 程式碼）。</li>



<li><strong>前端路由（Client-side Routing）：</strong> 當你在網站內切換頁面（例如從首頁到設定頁）時，<strong>瀏覽器並不會向伺服器重新請求新網頁</strong>。React Router 等套件會攔截點擊事件，直接在前端用 JavaScript 把舊的組件（Component）拔掉，換上新的組件。</li>



<li><strong>體驗流暢：</strong> 因為不需要重新整理網頁，頁面切換就像桌面軟體或 App 一樣秒開、極度流暢。</li>
</ul>



<h3 class="wp-block-heading">解讀你遇到的問題</h3>



<p class="wp-block-paragraph">你提到的這句話非常精準地點出了 SPA 的特性：</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>&#8220;The refetchOnWindowFocus doesn&#8217;t fire when navigating within the same SPA.&#8221;</em></p>
</blockquote>



<p class="wp-block-paragraph">如果你使用的是 <strong>TanStack Query (React Query)</strong> 或類似的 Data Fetching 套件，<code>refetchOnWindowFocus</code> 的觸發條件是「瀏覽器視窗重新獲得焦點」（例如：你切換到別的視窗看 LINE，再切回瀏覽器；或者切換分頁再切回來）。</p>



<p class="wp-block-paragraph">因為在同一個 SPA 裡面切換頁面（Navigating）：</p>



<ol start="1" class="wp-block-list">
<li><strong>視窗從頭到尾都沒有失去焦點</strong>，瀏覽器只是在執行 React 的組件切換。</li>



<li>對瀏覽器來說，使用者根本沒有「離開視窗再回來」。</li>



<li>因此，<code>refetchOnWindowFocus</code> 絕對<strong>不會</strong>因為你在 SPA 內轉址而觸發。</li>
</ol>



<h3 class="wp-block-heading">你的解決方案完全正確</h3>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p class="wp-block-paragraph"><em>&#8220;The real fix is: invalidate getMe and all suggestion logs from AccessRolesPage after removing the reviewer delegate&#8221;</em></p>
</blockquote>



<p class="wp-block-paragraph">這正是處理 SPA 狀態同步的標準標準做法（Best Practice）：</p>



<p class="wp-block-paragraph">在 SPA 中，因為頁面不會重新載入，舊頁面的快取數據可能還留在記憶體中。當你執行了某個動作（例如：移除審查員代理人 reviewer delegate），後端的資料已經變了，但前端畫面可能還看得到舊資料。</p>



<p class="wp-block-paragraph">透過主動呼叫 <strong><code>queryClient.invalidateQueries</code></strong>：</p>



<ul class="wp-block-list">
<li>你明確告訴 React Query：「<code>getMe</code> 和 <code>suggestion logs</code> 這幾筆資料已經過期（stale）了！」</li>



<li>React Query 就會立刻在背景重新向 API 發送請求（Refetch），確保使用者切換到 <code>AccessRolesPage</code> 時，看到的是最新的正確資料。</li>
</ul>



<p class="wp-block-paragraph">這種「<strong>動作完成後，主動使相關快取失效</strong>」的機制，是維持 SPA 資料同步最乾淨、最不通靈的解法！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/react-spa/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>認識 React useMemo</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/react-usememo/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/react-usememo/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Sat, 06 Jun 2026 04:06:54 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[React]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8526</guid>

					<description><![CDATA[在 React 之中，useMemo 是一個非常...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在 React 之中，<code>useMemo</code> 是一個非常實用且能優化效能的 Hook。簡單來說，它的核心功能就是「快取」，也就是幫你把複雜運算的結果存起來，避免重複執行沒必要的計算。</p>



<p class="wp-block-paragraph">要理解它為什麼能提升效率，我們需要先來看看 React 的運作機制。</p>



<h2 class="wp-block-heading">為什麼需要 useMemo？</h2>



<p class="wp-block-paragraph">在網頁運作時，只要元件的狀態（State）或屬性（Props）發生改變，React 就會重新渲染該元件。重新渲染意味著元件內部的所有程式碼，都會從上到下重新執行一次。</p>



<p class="wp-block-paragraph">想像一下，如果你的元件內有一個需要處理上萬條資料的排序函式，每次畫面一有風吹草動，這個排序函式就要重新跑一次，這會消耗極大的瀏覽器運算資源，導致畫面出現卡頓。</p>



<p class="wp-block-paragraph"><code>useMemo</code> 就是為了解決這個問題而誕生的。</p>



<h2 class="wp-block-heading">useMemo 的運作原理</h2>



<p class="wp-block-paragraph"><code>useMemo</code> 的基本語法如下：</p>



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



<pre class="wp-block-code"><code>const memoizedValue = useMemo(() =&gt; computeExpensiveValue(a, b), &#91;a, b]);
</code></pre>



<p class="wp-block-paragraph">它接收兩個參數：</p>



<ol start="1" class="wp-block-list">
<li>一個帶有回傳值的函式，裡面放置你需要進行的複雜運算。</li>



<li>一個陣列，稱為「相依陣列」（Dependency Array）。</li>
</ol>



<p class="wp-block-paragraph">它的工作流程非常聰明：</p>



<h3 class="wp-block-heading">1. 記憶結果</h3>



<p class="wp-block-paragraph">當元件第一次渲染時，<code>useMemo</code> 會執行內部的函式，並把算好的結果存到記憶體中。</p>



<h3 class="wp-block-heading">2. 比對相依陣列</h3>



<p class="wp-block-paragraph">當元件重新渲染時，<code>useMemo</code> 會去檢查相依陣列裡面的變數（例如上面的 <code>a</code> 和 <code>b</code>）有沒有發生改變。</p>



<h3 class="wp-block-heading">3. 決定是否重新計算</h3>



<ul class="wp-block-list">
<li>如果 <code>a</code> 和 <code>b</code> 的值跟上一次一模一樣，<code>useMemo</code> 就會直接從記憶體拿取上一次算好的結果，完全跳過內部的運算函式。</li>



<li>只有當 <code>a</code> 或 <code>b</code> 的值改變了，它才會重新執行運算，並更新記憶體裡的暫存答案。</li>
</ul>



<h2 class="wp-block-heading">為什麼這樣可以提升效率？</h2>



<p class="wp-block-paragraph">我們用一個生活中的例子來比喻：</p>



<p class="wp-block-paragraph">假設老師每天都會考你一題非常複雜的數學題。</p>



<ul class="wp-block-list">
<li><strong>沒有使用 useMemo 的情況</strong>：你每天早上拿到題目，就算題目跟昨天完全一樣，你還是老老實實地在計算紙上從第一步算到最後一步，花了 20 分鐘才寫出答案。</li>



<li><strong>使用了 useMemo 的情況</strong>：你發現題目沒變，於是直接翻開昨天的筆記本，把寫在上面的答案抄上去，只花了 2 秒鐘。只有當老師換了新題目，你才會重新動筆計算。</li>
</ul>



<p class="wp-block-paragraph">在程式裡也是一樣。透過快取機制，<code>useMemo</code> 幫瀏覽器省去了大量重複的 CPU 計算時間，讓網頁反應變得更加流暢。</p>



<h2 class="wp-block-heading">什麼時候該用 useMemo？</h2>



<p class="wp-block-paragraph">雖然 <code>useMemo</code> 很好用，但這並不代表所有地方都應該加上它。因為「把資料存進記憶體」以及「每次比對相依陣列」本身也需要消耗微小的效能。</p>



<p class="wp-block-paragraph">以下是適合使用的情境：</p>



<ol start="1" class="wp-block-list">
<li><strong>處理大量資料的運算</strong>：例如高達數千筆資料的過濾（Filter）、排序（Sort）或複雜轉換。</li>



<li><strong>避免不必要的子元件渲染</strong>：當你把一個物件或陣列當作 Props 傳給子元件時，因為 JavaScript 每次重新渲染都會產生新的物件記憶體位址，會導致子元件誤以為資料變了而跟著重新渲染。這時候用 <code>useMemo</code> 把物件固定住，就能阻止子元件浪費效能。</li>
</ol>



<p class="wp-block-paragraph">如果只是簡單的加減乘除或少數資料的處理，直接讓 React 重新計算即可，不需要刻意使用 <code>useMemo</code>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/react-usememo/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
