

<?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/category/react/feed/" rel="self" type="application/rss+xml" />
	<link>https://stackoverflow.max-everyday.com</link>
	<description>我要當一個豬頭，快樂過每一天</description>
	<lastBuildDate>Thu, 18 Jun 2026 07:29:27 +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>vue 3 Element Plus 與 vite 的關係</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 07:29:27 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8581</guid>

					<description><![CDATA[在 Vue 3 的開發世界中，Element P...]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">在 Vue 3 的開發世界中，<strong>Element Plus</strong> 與 <strong>Vite</strong> 是一對<strong>天生一對的最佳拍檔</strong>。它們兩者在專案中扮演著完全不同、卻又緊密合作的角色。</p>



<p class="wp-block-paragraph">簡單來說：<strong>Vue 3 是核心大腦，Element Plus 是外在的精美皮囊（UI 元件），而 Vite 則是負責把這一切快速組裝、運送到瀏覽器上的「超級工廠（構建工具）」。</strong></p>



<p class="wp-block-paragraph">以下為你詳細拆解它們之間的深層關係，以及為什麼現在開發 Vue 3 專案時，大家總是把它們綁在一起：</p>



<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;" /> 1. 角色定位：元件庫 vs. 構建工具</h2>



<ul class="wp-block-list">
<li><strong>Element Plus (UI 框架)：</strong> 它是寫好的 Vue 3 組件庫（按鈕、表格、下拉選單等）。它關心的是「畫面好不好看」<strong>、</strong>「功能好不好用」。</li>



<li><strong>Vite (前端構建工具)：</strong> 它是新一代的前端開發伺服器與打包工具。它不關心你的按鈕是藍色還是綠色，它關心的是「程式碼載入速度夠不夠快」<strong>、</strong>「打包出來的檔案體積夠不夠小」。</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/26a1.png" alt="⚡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 2. 為什麼兩者結合會「快上加快」？</h2>



<p class="wp-block-paragraph">Vite 的核心特點是利用了瀏覽器原生的 <strong>ES Modules (ESM)</strong>，在開發環境下不需要像舊時代的 Webpack 一樣把幾萬行程式碼全部打包完才開機。而 Element Plus 也是完全基於 ESM 架構重構的。</p>



<p class="wp-block-paragraph">這帶來了兩個巨大的優勢：</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 瞬間啟動 (Lightning Fast Dev Server)</h3>



<p class="wp-block-paragraph">當你在 Vite 專案中引入 Element Plus 時，Vite 會利用 <strong>Esbuild</strong> 對 Element Plus 進行「預構建 (Dependency Pre-bundling)」。這意味著即使 Element Plus 包含幾百個組件，Vite 也能在 <strong>1 秒內</strong>啟動開發伺服器，完全不會因為組件庫太龐大而卡頓。</p>



<h3 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4a1.png" alt="💡" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 完美的自動按需引入 (Tree Shaking)</h3>



<p class="wp-block-paragraph">這是兩者合作最漂亮的亮點。在過去，如果你只用一個 Element Plus 的按鈕 <code>&lt;el-button&gt;</code>，你可能得把幾萬行的整包組件庫都下載到瀏覽器。</p>



<p class="wp-block-paragraph">但在 Vite 生態圈中，透過官方推薦的 Vite 插件（<code>unplugin-vue-components</code> 和 <code>unplugin-auto-import</code>），Vite 可以在編譯時<strong>自動偵測</strong>你在程式碼裡寫了什麼。</p>



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



<pre class="wp-block-code"><code>// vite.config.ts 範例配置
import { defineConfig } from 'vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'

export default defineConfig({
  plugins: &#91;
    // Vite 會自動幫你在背後偷偷引入 Element Plus 的組件與樣式
    AutoImport({ resolvers: &#91;ElementPlusResolver()] }),
    Components({ resolvers: &#91;ElementPlusResolver()] }),
  ],
})
</code></pre>



<p class="wp-block-paragraph">有了這個關係，你在寫 Vue 檔案時<strong>完全不需要手動 <code>import</code> 元件</strong>，直接寫 <code>&lt;el-table&gt;</code>，Vite 就會自動幫你打包對應的 JS 和 CSS，做到極致的效能優化（Tree-shaking）。</p>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3a8.png" alt="🎨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 3. 樣式與主題定制的橋樑</h2>



<p class="wp-block-paragraph">Element Plus 內建使用了大量的 <strong>CSS 變數 (CSS Variables)</strong>。而 Vite 對於 CSS、SCSS 的處理速度極快，且原生支援 CSS 變數。</p>



<ul class="wp-block-list">
<li><strong>開發時：</strong> 當你在 Vite 專案中微調 Element Plus 的主題顏色（例如把品牌色從藍色改成綠色），Vite 的 HMR（熱模組替換）能在<strong>幾毫秒內</strong>讓瀏覽器畫面直接變色，開發體驗非常絲滑。</li>



<li><strong>打包時：</strong> Vite 底層使用 Rollup，它會把 Element Plus 被你用到元件的 CSS 提取出來，與你自己的 CSS 合併並壓縮，這也是為什麼你在生產環境（Production build）時，預設能拿到乾淨、沒有 <code>unsafe-inline</code> 的獨立 CSS 檔案。</li>
</ul>



<h2 class="wp-block-heading"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /> 總結</h2>



<p class="wp-block-paragraph">Element Plus 與 Vite 的關係，就像是「優質賽車（Element Plus）」<strong>與</strong>「頂級賽道/後勤團隊（Vite）」。</p>



<p class="wp-block-paragraph">Element Plus 專心提供基於 Vue 3 的強大組件，而 Vite 則利用現代瀏覽器的特性，確保這些組件在開發時能秒速渲染、在部署時能被極致壓縮。這也是目前 Vue 3 生態系中最標準、最推薦的官方級主流配置。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/vue-3-element-plus-vite/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>網頁開發者必看！用白話秒懂 CSS Injection 與 CSP 防禦</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 06:39:22 +0000</pubDate>
				<category><![CDATA[css筆記]]></category>
		<category><![CDATA[React 筆記]]></category>
		<category><![CDATA[css]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8572</guid>

					<description><![CDATA[為什麼連寫個看起來無害的 CSS 都會被瀏覽器當...]]></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/css-injection-csp-url__clean-1024x572.jpg?v=1781764740" alt="" class="wp-image-8579" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-1024x572.jpg?v=1781764740 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-600x335.jpg?v=1781764740 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean-768x429.jpg?v=1781764740 768w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/css-injection-csp-url__clean.jpg?v=1781764740 1376w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">為什麼連寫個看起來無害的 CSS 都會被瀏覽器當成恐怖份子。</p>



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



<h2 class="wp-block-heading">瀏覽器的大內總管：CSP 到底在嚴格什麼？</h2>



<p class="wp-block-paragraph">各位前端與後端苦力們，有沒有遇過這種慘劇？</p>



<p class="wp-block-paragraph">當你興高采烈地在網站加上這行內容安全政策（CSP）：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self';</code></code></pre>



<p class="wp-block-paragraph">本以為從此高枕無憂，結果下一秒打開網頁，發現自己寫的行內腳本（inline-script）全部陣亡：</p>



<pre class="wp-block-code"><code><code>&lt;script&gt;console.log('連我都不能跑是怎樣');&lt;/script&gt;</code></code></pre>



<p class="wp-block-paragraph">好啦，大家都知道 JavaScript 權限大、壞人最愛用，所以瀏覽器預設不讓它亂跑很合理。為了安撫大內總管，我們還得乖乖去算雜湊值（Hash）或是塞個 nonce 安全憑證，像是這樣：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; script-src 'sha256-xxx...';</code></code></pre>



<p class="wp-block-paragraph">但是，最讓人傻眼的是，連單純想幫網頁化妝的行內樣式（inline-style）也被一起關進大牢了：</p>



<pre class="wp-block-code"><code><code>&lt;style&gt;h1 { color: yellow; }&lt;/style&gt;</code></code></pre>



<p class="wp-block-paragraph">CSS 到底做錯了什麼？它只是個孩子啊！</p>



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



<h2 class="wp-block-heading">警匪片現場：當 CSS 變成滲透工具</h2>



<p class="wp-block-paragraph">資安世界有三大神聖支柱：機密性、完整性、可用性。而隨便亂加的 CSS，其實正在默默破壞這一切。</p>



<h3 class="wp-block-heading">疑犯一號：DOM 元素私生子（破壞完整性）</h3>



<p class="wp-block-paragraph">你可能會想：「這網頁是我寫的，裡面的 CSS 當然也是我寫的啊！」</p>



<p class="wp-block-paragraph">事情可沒那麼簡單。在 JavaScript 的世界裡，壞人可以用各種神奇手法動態產生元素：</p>



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



<pre class="wp-block-code"><code>let style = document.createElement('style');
style.innerText = 'h1 { color: blue; }';
document.body.appendChild(style);
</code></pre>



<p class="wp-block-paragraph">這段程式碼一旦被惡意注入，它就能神不知鬼不覺地改掉你的網頁外觀。這就是為什麼瀏覽器乾脆一不做二不休，預設把所有沒登記過的行內樣式全部封鎖。</p>



<h3 class="wp-block-heading">疑犯二號：假藉美化名義的間諜（破壞機密性）</h3>



<p class="wp-block-paragraph">CSS 裡面有兩個看起來很無辜、但實際上超常被當作間諜的語法： <code>@import</code> 和 <code>url()</code> 。</p>



<p class="wp-block-paragraph">當你用了背景圖片：</p>



<pre class="wp-block-code"><code><code>background-image: url(https://example.com/spy.jpg);</code></code></pre>



<p class="wp-block-paragraph">瀏覽器看到這行，就會自動發出一個請求去下載圖片。這時候，壞人就可以利用這個機制搞鬼：</p>



<ul class="wp-block-list">
<li><strong>偷看你的行蹤（Beacon 技術）：</strong> 只要把圖片網址加上個人專屬的編碼，當你一打開網頁，壞人的伺服器收到圖片請求，就知道「喔！某某某在幾點幾分看了這個頁面！」</li>



<li><strong>Cookie 順風車：</strong> 如果網站沒設定好，這個請求甚至會順便把你的 Cookie 跨站送出去，直接把你的登入狀態雙手奉上。</li>
</ul>



<p class="wp-block-paragraph">所以，想要在有 CSP 的防護下安全地秀出圖片，你還必須跟總管報備圖片的白色名單：</p>



<pre class="wp-block-code"><code><code>Content-Security-Policy: default-src 'self'; style-src 'sha256-...'; img-src http://信任的圖片來源;</code></code></pre>



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



<h2 class="wp-block-heading">結論</h2>



<p class="wp-block-paragraph">在這個壞人招數層出不窮的時代，網頁開發者真的不能只活在「CSS 只是用來排版」的粉紅泡泡裡。多了解一點 CSP 的龜毛規則，雖然設定時會寫到抓狂，但至少能保住大家的飯碗，不讓網站變成駭客的免費遊樂園！</p>



<p class="wp-block-paragraph"></p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/css-injection-csp-url/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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 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>從 Vue 2 升級到 Vue 3 + Vite, v-model 語法修正</title>
		<link>https://stackoverflow.max-everyday.com/2026/06/vue-2-to-3-v-model/</link>
					<comments>https://stackoverflow.max-everyday.com/2026/06/vue-2-to-3-v-model/#respond</comments>
		
		<dc:creator><![CDATA[max-stackoverflow]]></dc:creator>
		<pubDate>Thu, 18 Jun 2026 01:16:10 +0000</pubDate>
				<category><![CDATA[React 筆記]]></category>
		<guid isPermaLink="false">https://stackoverflow.max-everyday.com/?p=8565</guid>

					<description><![CDATA[這次從 Vue 2 升級到 Vue 3 + Vi...]]></description>
										<content:encoded><![CDATA[
<figure class="wp-block-image size-full"><img decoding="async" width="1024" height="572" src="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/14548560955904015219.jpg?v=1781745361" alt="" class="wp-image-8566" srcset="https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/14548560955904015219.jpg?v=1781745361 1024w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/14548560955904015219-600x335.jpg?v=1781745361 600w, https://stackoverflow.max-everyday.com/wp-content/uploads/2026/06/14548560955904015219-768x429.jpg?v=1781745361 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p class="wp-block-paragraph">這次從 Vue 2 升級到 Vue 3 + Vite 的過程中，後台管理系統的按鈕集體罷工，點了完全沒反應，讓人一度懷疑是不是人生也卡住了。經過一番開箱檢查，終於抓到這幾個幕後黑手！</p>



<p class="wp-block-paragraph">以下是這次的災情與拯救指南，幫大家把 Vue 2 舊語法順利轉換成現代化的 Vue 3 風格。</p>



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



<h2 class="wp-block-heading">為什麼按鈕點了沒反應？三大全面罷工原因</h2>



<h3 class="wp-block-heading">1. 彈窗搞失蹤：Dialog 的雙向綁定變心了</h3>



<p class="wp-block-paragraph">在 Vue 2 的 Element UI 時代，控制彈窗顯示是用 <code>v-model:visible="dialogVisible"</code>。</p>



<p class="wp-block-paragraph">但是在 Vue 3 的 Element Plus 裡，它已經悄悄改名叫 <code>v-model="dialogVisible"</code> 了！</p>



<p class="wp-block-paragraph">因為語法對不上，雖然程式背後把狀態改成 <code>true</code>，但 Dialog 本尊根本收不到訊號，難怪怎麼點按鈕都像在點空氣。</p>



<h3 class="wp-block-heading">2. 警告噴不停：el-row 的 flex 屬性退役</h3>



<p class="wp-block-paragraph">以前寫彈性佈局會加上 <code>type="flex"</code>，現在 Element Plus 預設就已經是 Flex 佈局了，這個屬性不但多餘還會狂噴警告，直接拿掉世界就清淨了。</p>



<h3 class="wp-block-heading">3. 泡泡提示與確認視窗：找不到全域的 this</h3>



<p class="wp-block-paragraph">舊程式很喜歡用 <code>this.$message</code> 或 <code>this.$confirm</code>。雖然全域載入時可能還拿得到，但在現代化的 Vue 3 組合式 API（Composition API）世界裡，這種寫法不僅不夠直覺，還容易在搬家時鬧失蹤。</p>



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



<h2 class="wp-block-heading">一圖看懂：Vue 2 與 Vue 3 修正對照表</h2>



<p class="wp-block-paragraph">我們把這次搬家的修改重點整理成表格，按圖索驥就不會迷路：</p>



<figure class="wp-block-table"><table class="has-fixed-layout"><thead><tr><td><strong>檢查項目</strong></td><td><strong>Vue 2 舊寫法</strong></td><td><strong>Vue 3 現代化修正</strong></td></tr></thead><tbody><tr><td><strong>彈窗顯示</strong></td><td><code>v-model:visible="dialogVisible"</code></td><td><code>v-model="dialogVisible"</code></td></tr><tr><td><strong>訊息提示</strong></td><td><code>this.$message</code></td><td><code>ElMessage</code>（需從 <code>element-plus</code> 引入）</td></tr><tr><td><strong>確認視窗</strong></td><td><code>this.$confirm</code></td><td><code>ElMessageBox.confirm</code>（需從 <code>element-plus</code> 引入）</td></tr><tr><td><strong>網格佈局</strong></td><td><code>el-row type="flex"</code></td><td>直接寫 <code>el-row</code> 即可（預設即為 flex）</td></tr><tr><td><strong>程式風格</strong></td><td><code>export default { data(), methods: {} }</code></td><td>擁抱 <code>&lt;script setup&gt;</code> 組合式 API</td></tr></tbody></table></figure>



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



<h2 class="wp-block-heading">核心程式碼大改造</h2>



<p class="wp-block-paragraph">既然要改，就直接升級成最潮的 <code>&lt;script setup&gt;</code> 風格，程式碼看起來乾淨俐落！</p>



<h3 class="wp-block-heading">舊版 Option API 風格（Vue 2）</h3>



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



<pre class="wp-block-code"><code>export default {
  data() {
    return {
      dialogVisible: false
    }
  },
  methods: {
    handleDelete() {
      this.$confirm('確定要刪除嗎？').then(() =&gt; {
        this.$message.success('刪除成功');
      });
    }
  }
}
</code></pre>



<h3 class="wp-block-heading">新版 Script Setup 風格（Vue 3）</h3>



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



<pre class="wp-block-code"><code>import { ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';

const dialogVisible = ref(false);

const handleDelete = () =&gt; {
  ElMessageBox.confirm('確定要刪除嗎？', '提示', {
    type: 'warning'
  }).then(() =&gt; {
    ElMessage.success('刪除成功');
  }).catch(() =&gt; {
    // 取消刪除
  });
};
</code></pre>



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



<h2 class="wp-block-heading">總結</h2>



<p class="wp-block-paragraph">程式搬家總會遇到幾顆絆腳石，這次的按鈕罷工事件，說穿了就是 <strong>Dialog 的雙向綁定語法變更</strong> 導致的慘劇。只要把 <code>v-model:visible</code> 改回 <code>v-model</code>，並順手將舊式的 <code>this</code> 改為直接引入 <code>element-plus</code> 的組件，按鈕們就會乖乖聽話，重回工作崗位囉！</p>
]]></content:encoded>
					
					<wfw:commentRss>https://stackoverflow.max-everyday.com/2026/06/vue-2-to-3-v-model/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 loading="lazy" 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="auto, (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>
