<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>KD Tech Blog 技術部落格 Blog</title>
        <link>https://tech.kdchang.com/blog</link>
        <description>KD Tech Blog 技術部落格 Blog</description>
        <lastBuildDate>Mon, 24 Feb 2025 02:23:41 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>zh-Hant</language>
        <item>
            <title><![CDATA[Django、Flask、FastAPI 吞吐量比較入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial</guid>
            <pubDate>Mon, 24 Feb 2025 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在 Python Web 開發領域，Django、Flask 與 FastAPI 是三個非常熱門且廣泛使用的框架。它們各有特點與適用場景，但在性能表現，特別是吞吐量（throughput）方面，存在一定差異。吞吐量通常指每秒鐘能處理的請求數量，是衡量 Web 框架在高併發環境中效率的重要指標。</p>
<p>本篇筆記將簡要介紹這三個框架的基本架構與設計理念，並透過簡單測試與範例比較其吞吐量，幫助初學者理解如何依需求選擇適合的框架。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>Django</strong></p>
<ul>
<li class="">全功能、重量級框架，內建 ORM、管理後台、驗證系統等</li>
<li class="">同步同步處理，基於 WSGI，預設不支援非同步（Asynchronous）請求</li>
<li class="">適合需要完整解決方案的中大型專案</li>
<li class="">吞吐量相較較低，因同步阻塞限制高併發能力</li>
</ul>
</li>
<li class="">
<p><strong>Flask</strong></p>
<ul>
<li class="">輕量級框架，核心簡單，擴展彈性高</li>
<li class="">同樣基於同步 WSGI，預設不支援非同步</li>
<li class="">適合小型、原型開發及彈性需求較多的專案</li>
<li class="">吞吐量與 Django 相近，瓶頸多來自同步阻塞與部署環境</li>
</ul>
</li>
<li class="">
<p><strong>FastAPI</strong></p>
<ul>
<li class="">新興的輕量且高性能框架，採用 ASGI 標準，內建非同步支援</li>
<li class="">基於 Starlette 與 Pydantic，支援非同步 I/O，大幅提升吞吐量</li>
<li class="">適合高併發、API 開發需求強烈的專案</li>
<li class="">吞吐量明顯優於 Django、Flask，適合現代微服務架構</li>
</ul>
</li>
<li class="">
<p><strong>吞吐量測試環境與結果</strong></p>
<ul>
<li class="">使用同一台機器與相同測試工具（如 <code>wrk</code>、<code>ab</code>、<code>Locust</code>）對簡單 API 進行測試</li>
<li class="">Django、Flask 在同步阻塞環境吞吐量約數千至上萬請求/秒</li>
<li class="">FastAPI 在非同步環境可突破十萬請求/秒，具備更好擴展性</li>
</ul>
</li>
<li class="">
<p><strong>部署差異</strong></p>
<ul>
<li class="">Django、Flask 多搭配 Gunicorn（WSGI）同步服務器部署</li>
<li class="">FastAPI 搭配 Uvicorn、Hypercorn（ASGI）非同步服務器，性能最佳化</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="吞吐量比較實際範例">吞吐量比較實際範例<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E5%90%9E%E5%90%90%E9%87%8F%E6%AF%94%E8%BC%83%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="吞吐量比較實際範例的直接連結" title="吞吐量比較實際範例的直接連結" translate="no">​</a></h2>
<p>以下將以簡單「Hello World」API 為例，展示三個框架的基本實作，並提供吞吐量測試方法參考。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-django-範例">1. Django 範例<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#1-django-%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="1. Django 範例的直接連結" title="1. Django 範例的直接連結" translate="no">​</a></h3>
<p><strong>程式碼（<code>views.py</code>）：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> django</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">http </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> JsonResponse</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">hello</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> JsonResponse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hello World"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p><strong>URL 設定（<code>urls.py</code>）：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> django</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">urls </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> path</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">views </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> hello</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">urlpatterns </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'hello/'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> hello</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p><strong>啟動方式：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">python manage.py runserver</span><br></span></code></pre></div></div>
<p>（正式部署可用 Gunicorn）</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-flask-範例">2. Flask 範例<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#2-flask-%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="2. Flask 範例的直接連結" title="2. Flask 範例的直接連結" translate="no">​</a></h3>
<p><strong>程式碼（<code>app.py</code>）：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> flask </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> Flask</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> jsonify</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app </span><span class="token operator">=</span><span class="token plain"> Flask</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">__name__</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@app</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">route</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/hello'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">hello</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> jsonify</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">message</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"Hello World"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> __name__ </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"__main__"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">run</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p><strong>啟動方式：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">python app.py</span><br></span></code></pre></div></div>
<p>（正式部署可用 Gunicorn）</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-fastapi-範例">3. FastAPI 範例<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#3-fastapi-%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="3. FastAPI 範例的直接連結" title="3. FastAPI 範例的直接連結" translate="no">​</a></h3>
<p><strong>程式碼（<code>main.py</code>）：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> fastapi </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> FastAPI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app </span><span class="token operator">=</span><span class="token plain"> FastAPI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">@app</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token decorator annotation punctuation" style="color:rgb(248, 248, 242)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"/hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">hello</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"message"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hello World"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>啟動方式：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="吞吐量測試方法簡介">吞吐量測試方法簡介<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E5%90%9E%E5%90%90%E9%87%8F%E6%B8%AC%E8%A9%A6%E6%96%B9%E6%B3%95%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="吞吐量測試方法簡介的直接連結" title="吞吐量測試方法簡介的直接連結" translate="no">​</a></h2>
<p>使用 Linux 下的 <code>wrk</code> 工具對三個 API 進行測試：</p>
<p>若使用 Mac 作業系統可以安裝：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">brew install wrk</span><br></span></code></pre></div></div>
<p>下指令測試：</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">wrk -t4 -c100 -d30s http://localhost:8000/hello</span><br></span></code></pre></div></div>
<ul>
<li class=""><code>-t4</code>：4 個 thread</li>
<li class=""><code>-c100</code>：100 個持續連線</li>
<li class=""><code>-d30s</code>：測試持續 30 秒</li>
</ul>
<p>測試結果可觀察 Requests/sec 欄位，即為吞吐量。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="性能分析與比較">性能分析與比較<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90%E8%88%87%E6%AF%94%E8%BC%83" class="hash-link" aria-label="性能分析與比較的直接連結" title="性能分析與比較的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>Django</strong> 在簡單請求下可達數千請求/秒，但因同步處理與較重的框架開銷，無法輕易擴展至高併發環境。</li>
<li class=""><strong>Flask</strong> 同樣是同步，吞吐量略優於 Django，但主要瓶頸仍在同步阻塞與伺服器資源分配。</li>
<li class=""><strong>FastAPI</strong> 採用非同步設計，利用 Python 的 async/await 及高效事件迴圈，能在相同硬體資源下達到數倍甚至十倍以上吞吐量，尤其適合 I/O 密集型服務。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>選擇適合的 Python Web 框架時，需根據專案規模、功能需求及預期流量做權衡。Django 適合快速搭建大型且功能完整的應用，Flask 適合靈活開發與小型專案，而 FastAPI 則是現代高效能 API 開發的首選。</p>
<p>在吞吐量需求高、需要非同步處理的情境下，FastAPI 明顯優勢突出。未來隨著非同步技術普及，FastAPI 的應用範圍將持續擴大。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-django-flask-fastapi-throughput-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://locust.io/" target="_blank" rel="noopener noreferrer" class="">locust 官方文件</a></li>
</ol>]]></content:encoded>
            <category>Django</category>
            <category>Flask</category>
            <category>FastAPI</category>
            <category>locust</category>
            <category>throughput</category>
            <category>Load testing</category>
        </item>
        <item>
            <title><![CDATA[Roo Code 詳細介紹：AI VS Code 編輯器外掛入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial</guid>
            <pubDate>Thu, 20 Feb 2025 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>Roo Code（原名 Roo Cline）是一款嵌入於 Visual Studio Code 編輯器中的開源 AI 代理，能讀寫檔案、自動執行終端指令、操作瀏覽器，甚至整合多種 LLM 模型。無論是編寫程式、重構、除錯、架構設計，Roo Code 都能協助你提升開發效率，更像是一個智慧且自主的開發團隊成員。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>核心功能與定位</strong></p>
<ul>
<li class="">像開發團隊的 AI 助手，能讀寫檔案、執行命令、自動操作終端與瀏覽器</li>
<li class="">支援自然語言溝通，整合 OpenAI、Gemini、Anthropic、Deepseek 等多種提供者</li>
</ul>
</li>
<li class="">
<p><strong>多模式運作（Modes）</strong></p>
<ul>
<li class=""><strong>Code Mode</strong>：主要用於撰寫與 refactor 程式碼</li>
<li class=""><strong>Architect Mode</strong>：聚焦系統設計與高階規劃</li>
<li class=""><strong>Ask Mode</strong>：解答問題、技術查詢</li>
<li class=""><strong>Debug Mode</strong>：專門診斷並修復錯誤</li>
</ul>
</li>
<li class="">
<p><strong>工具整合</strong></p>
<ul>
<li class="">可撰寫檔案、執行 CLI 指令、操控瀏覽器</li>
<li class="">支援外部工具擴充（MCP：Model Context Protocol），可接入資料庫、API、指令集</li>
</ul>
</li>
<li class="">
<p><strong>客製化能力強</strong></p>
<ul>
<li class="">自訂指令與 prompt</li>
<li class="">建立自訂模式，定義專屬角色（如測試工程師、設計師）</li>
</ul>
</li>
<li class="">
<p><strong>專案記憶與 Context 管理</strong></p>
<ul>
<li class="">支援 context condensing 和索引技術，避免長案子失去關聯性</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與初步設置">安裝與初步設置<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E5%AE%89%E8%A3%9D%E8%88%87%E5%88%9D%E6%AD%A5%E8%A8%AD%E7%BD%AE" class="hash-link" aria-label="安裝與初步設置的直接連結" title="安裝與初步設置的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-安裝-roocode">1. 安裝 Roo Code<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#1-%E5%AE%89%E8%A3%9D-roocode" class="hash-link" aria-label="1. 安裝 Roo Code的直接連結" title="1. 安裝 Roo Code的直接連結" translate="no">​</a></h3>
<ul>
<li class="">打開 VS Code，前往 Extensions 搜尋「Roo Code」，點擊安裝</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-設定-ai-提供者">2. 設定 AI 提供者<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#2-%E8%A8%AD%E5%AE%9A-ai-%E6%8F%90%E4%BE%9B%E8%80%85" class="hash-link" aria-label="2. 設定 AI 提供者的直接連結" title="2. 設定 AI 提供者的直接連結" translate="no">​</a></h3>
<ul>
<li class="">支援 OpenAI-compliant API，例如 OpenAI 本身、OpenRouter、Anthropic、Gemini 等</li>
<li class="">透過裡面 Preferences 設定 API Key、Model ID、Mode 對應關係</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作範例實際使用流程">實作範例（實際使用流程）<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E5%AF%A6%E4%BD%9C%E7%AF%84%E4%BE%8B%E5%AF%A6%E9%9A%9B%E4%BD%BF%E7%94%A8%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="實作範例（實際使用流程）的直接連結" title="實作範例（實際使用流程）的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例-a建立新檔案">範例 A：建立新檔案<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E7%AF%84%E4%BE%8B-a%E5%BB%BA%E7%AB%8B%E6%96%B0%E6%AA%94%E6%A1%88" class="hash-link" aria-label="範例 A：建立新檔案的直接連結" title="範例 A：建立新檔案的直接連結" translate="no">​</a></h3>
<ol>
<li class="">切換到 <strong>Code Mode</strong></li>
<li class="">輸入：「Create a basic HTML template for a portfolio website」</li>
<li class="">Roo Code 生成 HTML 檔案並顯示 diff</li>
<li class="">審核後確定寫入實際工作區</li>
</ol>
<p>→ 成功驗證：可透過開啟檔案確認生成內容</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例-b執行命令cli--build--test">範例 B：執行命令（CLI / Build / Test）<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E7%AF%84%E4%BE%8B-b%E5%9F%B7%E8%A1%8C%E5%91%BD%E4%BB%A4cli--build--test" class="hash-link" aria-label="範例 B：執行命令（CLI / Build / Test）的直接連結" title="範例 B：執行命令（CLI / Build / Test）的直接連結" translate="no">​</a></h3>
<ol>
<li class="">讓 Roo 進入程式模式</li>
<li class="">輸入：「Run npm install」</li>
<li class="">Roo 使用終端指令完成套件安裝，且結果顯示在你的終端</li>
</ol>
<p>→ 驗證指令正確執行</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例-c使用-architect-mode-規劃功能">範例 C：使用 Architect Mode 規劃功能<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E7%AF%84%E4%BE%8B-c%E4%BD%BF%E7%94%A8-architect-mode-%E8%A6%8F%E5%8A%83%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="範例 C：使用 Architect Mode 規劃功能的直接連結" title="範例 C：使用 Architect Mode 規劃功能的直接連結" translate="no">​</a></h3>
<ol>
<li class="">切換至 <strong>Architect Mode</strong></li>
<li class="">提出：「Help me plan data access layer for a todo application」</li>
<li class="">Roo 回應並建立記錄文件（如 <code>.md</code> 記錄實作計畫），建立指引</li>
</ol>
<p>→ 這種模式能生成結構化內容並建立 Context 記錄</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="深入技巧與最佳實踐">深入技巧與最佳實踐<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E6%B7%B1%E5%85%A5%E6%8A%80%E5%B7%A7%E8%88%87%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90" class="hash-link" aria-label="深入技巧與最佳實踐的直接連結" title="深入技巧與最佳實踐的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>啟用 Indexing / Context Condensing</strong>：讓 Roo 在大型專案中維持上下文關聯性，有效處理長案不遺漏</li>
<li class=""><strong>自訂模式（Custom Modes）</strong>：為不同角色（如 QA、設計師）建立專屬對話與指令集</li>
<li class=""><strong>MCP 擴充</strong>：串接外部系統（如 AWS、Jira），擴展 Roo 能力</li>
<li class=""><strong>權限控制</strong>：設定自動執行命令範圍、上下文層級，以符合安全標準</li>
<li class=""><strong>模式搭配 LLM 模型</strong>：Schema 例：使用 Deepseek R1 做 code、Gigantic Gemini 做 architect、等</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="操作範例結合免費-llm-模型">操作範例：結合免費 LLM 模型<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E6%93%8D%E4%BD%9C%E7%AF%84%E4%BE%8B%E7%B5%90%E5%90%88%E5%85%8D%E8%B2%BB-llm-%E6%A8%A1%E5%9E%8B" class="hash-link" aria-label="操作範例：結合免費 LLM 模型的直接連結" title="操作範例：結合免費 LLM 模型的直接連結" translate="no">​</a></h2>
<p>假設你想要整合免費模型：</p>
<ol>
<li class="">
<p>透過 OpenRouter 啟用 DeepSeek R1、Gemini Flash 模型</p>
</li>
<li class="">
<p>將 model 分配到不同模式：如 Architect 模式使用 Gemini、Code 模式使用 DeepSeek</p>
</li>
<li class="">
<p>透過 Roo 對話測試指令：</p>
<ul>
<li class="">“Generate React component for ToDo item list.”</li>
<li class="">“Update backend schema to include due date.”</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>Roo Code 是一款功能強大且高度可客製的 AI 編程代理工具，適合想提升開發效率、探索 AI 開發流程的人使用。核心優勢包括：</p>
<ul>
<li class="">多種工作模式切換（<code>Code</code> / <code>Architect</code> / <code>Ask</code> / <code>Debug</code>）</li>
<li class="">可控且安全的檔案與終端操作</li>
<li class="">擴充能力強，可透過 MCP、Context Condensing、自訂模式延伸功能</li>
<li class="">支援多種 LLM 提供者，具備彈性與擴充性</li>
</ul>
<p>由於功能豐富，建議一開始可以先安裝外掛並先從簡單任務使用起：生成範例檔案、執行終端指令，接著再進行構架規劃與系統整合。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-roo-code-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://help.apiyi.com/roo-code-claude-api-integration-guide.html" target="_blank" rel="noopener noreferrer" class="">Roo Code 接入 Claude API 完全指南：无惧官网限制而快速用上</a></li>
<li class=""><a href="https://www.youtube.com/watch?v=c5pDWj-zLDQ" target="_blank" rel="noopener noreferrer" class="">【Vibe Coding 神器】VS Code 超強 AI 插件 Roo Code 實作教學：Ask 模式分析專案、協調器模式開發全新專案、Debug 模式進行除錯！</a></li>
<li class=""><a href="https://www.youtube.com/watch?v=Vb8Vgs5sZiA" target="_blank" rel="noopener noreferrer" class="">Vibe Coding 真的這麼神？上集 來自開發團隊的真實導入經驗 聊聊實踐與成效｜#VibeCoding #AI #軟體開發 #開發團隊｜ JUGG 聊敏捷#22</a></li>
<li class=""><a href="https://www.youtube.com/watch?v=eZWVzTd3mkU" target="_blank" rel="noopener noreferrer" class="">Vibe Coding 真的這麼神？下集 來自開發團隊的真實導入對談｜導入的坑、文化與建議｜#VibeCoding #AI #軟體開發 #開發團隊 #AI 導入｜ JUGG 聊敏捷#23</a></li>
<li class=""><a href="https://www.thingsaboutweb.dev/zh-TW/posts/the-70-percent-problem#%E8%87%AA%E4%B8%BB%E4%BD%86%E5%8F%97%E5%BC%95%E5%B0%258" target="_blank" rel="noopener noreferrer" class="">70% 問題：關於 AI 輔助開發的真實樣貌</a></li>
</ol>]]></content:encoded>
            <category>Roo Code</category>
            <category>VS Code</category>
            <category>編輯器</category>
        </item>
        <item>
            <title><![CDATA[12 Factor App 入門教學：打造現代雲端應用的十二守則教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial</guid>
            <pubDate>Sun, 09 Feb 2025 02:23:41 GMT</pubDate>
            <description><![CDATA[12 Factor App 是由 Heroku 團隊提出的一套雲端應用架構設計原則，旨在幫助開發者打造可擴展、可維護、易部署的現代化應用程式。這些準則不限語言或框架，廣泛適用於各種 SaaS 應用、API 服務、微服務架構等場景。]]></description>
            <content:encoded><![CDATA[<p><strong>12 Factor App</strong> 是由 Heroku 團隊提出的一套雲端應用架構設計原則，旨在幫助開發者打造可擴展、可維護、易部署的現代化應用程式。這些準則不限語言或框架，廣泛適用於各種 SaaS 應用、API 服務、微服務架構等場景。</p>
<p>以下是每一個 factor 的說明與實際範例：</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一-codebase代碼基底">一、 Codebase（代碼基底）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%B8%80-codebase%E4%BB%A3%E7%A2%BC%E5%9F%BA%E5%BA%95" class="hash-link" aria-label="一、 Codebase（代碼基底）的直接連結" title="一、 Codebase（代碼基底）的直接連結" translate="no">​</a></h2>
<p><strong>一個應用對應一個代碼庫，多個部署環境共用該代碼庫</strong></p>
<p>一個應用程式不應散落在多個 Git 倉庫中，即使部署至多個環境（開發、測試、正式），仍應共用同一代碼庫。</p>
<p><strong>範例：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">git@github.com:kdchang/todo-api.git  # 統一代碼庫</span><br></span></code></pre></div></div>
<p>若你在 GitHub 上有一個 <code>todo-api</code> 倉庫，開發、測試與生產環境（production、staging、dev）的部署都應來自這個倉庫的不同分支或 Tag。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二-dependencies明確聲明相依套件">二、 Dependencies（明確聲明相依套件）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%BA%8C-dependencies%E6%98%8E%E7%A2%BA%E8%81%B2%E6%98%8E%E7%9B%B8%E4%BE%9D%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="二、 Dependencies（明確聲明相依套件）的直接連結" title="二、 Dependencies（明確聲明相依套件）的直接連結" translate="no">​</a></h2>
<p><strong>使用明確的套件管理工具來聲明所有相依項目，避免依賴系統層級安裝</strong>，例如：使用如 requirements.txt, Pipfile, package.json</p>
<p><strong>範例（Node.js）</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># package.json 中聲明所有依賴</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">{</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  "dependencies": {</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    "express": "^4.18.2",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    "dotenv": "^16.0.3"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">}</span><br></span></code></pre></div></div>
<p>部署時只需透過 <code>npm install</code> 即可安裝所有套件。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三-config環境設定分離">三、 Config（環境設定分離）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%B8%89-config%E7%92%B0%E5%A2%83%E8%A8%AD%E5%AE%9A%E5%88%86%E9%9B%A2" class="hash-link" aria-label="三、 Config（環境設定分離）的直接連結" title="三、 Config（環境設定分離）的直接連結" translate="no">​</a></h2>
<p><strong>環境變數應儲存所有設定資訊，而非寫死在程式碼中</strong>，例如：<code>.env</code></p>
<p>這包含資料庫連線、API 金鑰、第三方服務設定等。</p>
<p><strong>範例：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># .env 檔（不要提交到 Git）</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">DATABASE_URL=postgres://user:pass@host:5432/dbname</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">JWT_SECRET=my_secret_token</span><br></span></code></pre></div></div>
<p>程式碼中透過 <code>process.env</code> 取用這些變數。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四-backing-services外部資源視為附屬服務">四、 Backing Services（外部資源視為附屬服務）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%9B%9B-backing-services%E5%A4%96%E9%83%A8%E8%B3%87%E6%BA%90%E8%A6%96%E7%82%BA%E9%99%84%E5%B1%AC%E6%9C%8D%E5%8B%99" class="hash-link" aria-label="四、 Backing Services（外部資源視為附屬服務）的直接連結" title="四、 Backing Services（外部資源視為附屬服務）的直接連結" translate="no">​</a></h2>
<p><strong>無論是本地資料庫、第三方 API、AWS S3 等，都視為可替換的附屬資源</strong></p>
<p>切換服務供應商不應需改動應用邏輯，只要變更設定即可。例如：DATABASE_URL</p>
<p><strong>範例：</strong></p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> s3 </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">AWS</span><span class="token class-name punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token class-name">S3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">accessKeyId</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">AWS_ACCESS_KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">secretAccessKey</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">AWS_SECRET_KEY</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">region</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">AWS_REGION</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五-build-release-run建置發布執行分離">五、 Build, Release, Run（建置、發布、執行分離）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%BA%94-build-release-run%E5%BB%BA%E7%BD%AE%E7%99%BC%E5%B8%83%E5%9F%B7%E8%A1%8C%E5%88%86%E9%9B%A2" class="hash-link" aria-label="五、 Build, Release, Run（建置、發布、執行分離）的直接連結" title="五、 Build, Release, Run（建置、發布、執行分離）的直接連結" translate="no">​</a></h2>
<p><strong>明確區分建置（build）、發布（release）與執行（run）三個階段</strong></p>
<ul>
<li class="">Build：編譯程式、安裝依賴（例如：container）</li>
<li class="">Release：結合建置結果與設定，生成可部署版本。將 build 結果與設定綁定</li>
<li class="">Run：實際執行應用（以 immutable 的方式）</li>
</ul>
<p><strong>範例（Heroku）：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">git push heroku main  # 觸發 build 與 release</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">heroku run npm start  # 執行</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六-stateless-processes無狀態的執行單元">六、 Stateless Processes（無狀態的執行單元）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%85%AD-stateless-processes%E7%84%A1%E7%8B%80%E6%85%8B%E7%9A%84%E5%9F%B7%E8%A1%8C%E5%96%AE%E5%85%83" class="hash-link" aria-label="六、 Stateless Processes（無狀態的執行單元）的直接連結" title="六、 Stateless Processes（無狀態的執行單元）的直接連結" translate="no">​</a></h2>
<p><strong>應用程式應以一個或多個無狀態進程執行，狀態需存於外部服務</strong></p>
<p>避免將使用者 session 存在記憶體中，應使用 Redis、資料庫等外部服務。可以隨時 scale out</p>
<p><strong>範例（Express）：</strong></p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// Session 儲存至 Redis，而非記憶體</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">use</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">store</span><span class="token operator">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">RedisStore</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">client</span><span class="token operator">:</span><span class="token plain"> redisClient </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">secret</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">SESSION_SECRET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">resave</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">saveUninitialized</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七-port-binding綁定至-port-提供服務">七、 Port Binding（綁定至 Port 提供服務）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%B8%83-port-binding%E7%B6%81%E5%AE%9A%E8%87%B3-port-%E6%8F%90%E4%BE%9B%E6%9C%8D%E5%8B%99" class="hash-link" aria-label="七、 Port Binding（綁定至 Port 提供服務）的直接連結" title="七、 Port Binding（綁定至 Port 提供服務）的直接連結" translate="no">​</a></h2>
<p><strong>應用應自行綁定 port 來對外提供 HTTP 服務，而非依賴外部 Web Server</strong>，例如：Gunicorn</p>
<p>這使得應用本身即是一個完整的服務，容易容器化部署。</p>
<p><strong>範例（Node.js）：</strong></p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">listen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">PORT</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token number">3000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Server started"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="八-concurrency使用程序模型提升並行能力">八、 Concurrency（使用程序模型提升並行能力）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%85%AB-concurrency%E4%BD%BF%E7%94%A8%E7%A8%8B%E5%BA%8F%E6%A8%A1%E5%9E%8B%E6%8F%90%E5%8D%87%E4%B8%A6%E8%A1%8C%E8%83%BD%E5%8A%9B" class="hash-link" aria-label="八、 Concurrency（使用程序模型提升並行能力）的直接連結" title="八、 Concurrency（使用程序模型提升並行能力）的直接連結" translate="no">​</a></h2>
<p><strong>透過分工的進程來擴展應用功能，例如 Web、Worker、queue 等</strong></p>
<p>每個類型的處理單位可根據需求水平擴充。</p>
<p><strong>範例（使用 PM2）</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pm2 start app.js -i max       # 啟動多個 Web 處理進程</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">pm2 start worker.js --name worker  # 啟動背景任務處理器</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="九-disposability快速啟動與優雅關閉">九、 Disposability（快速啟動與優雅關閉）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E4%B9%9D-disposability%E5%BF%AB%E9%80%9F%E5%95%9F%E5%8B%95%E8%88%87%E5%84%AA%E9%9B%85%E9%97%9C%E9%96%89" class="hash-link" aria-label="九、 Disposability（快速啟動與優雅關閉）的直接連結" title="九、 Disposability（快速啟動與優雅關閉）的直接連結" translate="no">​</a></h2>
<p><strong>應用應能快速啟動與安全關閉，適應雲端平台的彈性調度</strong></p>
<p>優雅關閉能確保未完成的請求被妥善處理完畢，當使用容器應能快速重啟。</p>
<p><strong>範例（Node.js）：</strong></p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"SIGTERM"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">close</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Server closed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">exit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="十-devprod-parity開發與生產環境的一致性">十、 Dev/Prod Parity（開發與生產環境的一致性）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%8D%81-devprod-parity%E9%96%8B%E7%99%BC%E8%88%87%E7%94%9F%E7%94%A2%E7%92%B0%E5%A2%83%E7%9A%84%E4%B8%80%E8%87%B4%E6%80%A7" class="hash-link" aria-label="十、 Dev/Prod Parity（開發與生產環境的一致性）的直接連結" title="十、 Dev/Prod Parity（開發與生產環境的一致性）的直接連結" translate="no">​</a></h2>
<p><strong>開發、測試、生產環境盡可能相似，降低部署錯誤風險</strong></p>
<p>推薦使用 Docker 來統一環境。盡量減少「只有 production 才會發生」的 bug。</p>
<p><strong>範例：</strong></p>
<div class="language-Dockerfile language-dockerfile codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-dockerfile codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">FROM node:18</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">WORKDIR /app</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">COPY . .</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">RUN npm install</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">CMD ["npm", "start"]</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="十一-logs將-log-作為事件串流">十一、 Logs（將 log 作為事件串流）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%8D%81%E4%B8%80-logs%E5%B0%87-log-%E4%BD%9C%E7%82%BA%E4%BA%8B%E4%BB%B6%E4%B8%B2%E6%B5%81" class="hash-link" aria-label="十一、 Logs（將 log 作為事件串流）的直接連結" title="十一、 Logs（將 log 作為事件串流）的直接連結" translate="no">​</a></h2>
<p><strong>應用不應自行管理 log 文件，而是將 log 輸出到 stdout/stderr，再由平台集中收集與分析</strong></p>
<p><strong>範例：</strong></p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"User login success"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">userId</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">123</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"Database connection failed"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>在 Heroku、GCP、Kubernetes 等平台會自動收集這些 log，讓 log 管理交給專門工具收集（如 ELK, CloudWatch）</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="十二-admin-processes一時性管理指令">十二、 Admin Processes（一時性管理指令）<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E5%8D%81%E4%BA%8C-admin-processes%E4%B8%80%E6%99%82%E6%80%A7%E7%AE%A1%E7%90%86%E6%8C%87%E4%BB%A4" class="hash-link" aria-label="十二、 Admin Processes（一時性管理指令）的直接連結" title="十二、 Admin Processes（一時性管理指令）的直接連結" translate="no">​</a></h2>
<p><strong>資料庫 migration、資料修復等管理任務應能以一次性指令執行</strong></p>
<p>這些指令應與主應用共用相同的環境設定與程式碼。管理性任務（如資料遷移）應獨立於應用程式主進程（例如：<code>python manage.py migrate</code>）</p>
<p><strong>範例：</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Sequelize migration 指令</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">npx sequelize-cli db:migrate</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-12-factor-cloud-app-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>12 Factor App 並不是一套硬性規定，而是建立雲端應用的實務指南。當你的應用朝微服務、CI/CD、雲端部署發展時，這十二項原則能幫助你打造更穩定、可擴充的系統架構。</p>]]></content:encoded>
            <category>12 Factor App</category>
            <category>Heroku</category>
            <category>SaaS</category>
            <category>API</category>
            <category>cloud</category>
        </item>
        <item>
            <title><![CDATA[EventSource API in JavaScript 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial</guid>
            <pubDate>Mon, 03 Feb 2025 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在現代的網頁應用程式中，實時性資料更新是一個常見需求，例如即時通知、股價更新、聊天室訊息、伺服器狀態監控等。傳統上，開發者可能會透過輪詢（Polling）或 WebSocket 來實現。然而，若只是單向由伺服器推送訊息到瀏覽器端，其實有更簡單且高效的選擇：<strong>EventSource API</strong>。</p>
<p>EventSource 基於 <strong>Server-Sent Events（SSE）</strong>，由伺服器主動推送文字資料到客戶端，並且使用 <strong>HTTP 協議的持久連線</strong>，開發上比 WebSocket 更簡單，適合事件流的場景。本文將帶我們快速入門 EventSource API，理解它的特性與應用方式。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>EventSource 與 SSE 的核心概念</strong></p>
<ul>
<li class="">EventSource 是瀏覽器提供的 JavaScript API，用於接收伺服器推送的 SSE（Server-Sent Events）。</li>
<li class="">採用 <strong>HTTP 長連線</strong>，不需要 WebSocket，也不需要額外協議。</li>
<li class="">支援自動重連（瀏覽器會自動在連線中斷時重新連接伺服器）。</li>
</ul>
</li>
<li class="">
<p><strong>EventSource 的適用場景</strong></p>
<ul>
<li class="">即時通知（系統提醒、訊息推送）</li>
<li class="">資料更新（股價、天氣、賽事比分）</li>
<li class="">記錄串流（伺服器日誌、事件追蹤）</li>
<li class="">聊天室訊息（單向推送）</li>
</ul>
</li>
<li class="">
<p><strong>EventSource 的特性</strong></p>
<ul>
<li class="">單向通訊：伺服器 → 客戶端</li>
<li class="">自動重連機制（可透過伺服器端 <code>retry:</code> 指令調整重試時間）</li>
<li class="">基於純文字的事件格式（MIME type 為 <code>text/event-stream</code>）</li>
<li class="">可透過自訂事件名稱分發不同事件</li>
</ul>
</li>
<li class="">
<p><strong>與其他技術比較</strong></p>
<ul>
<li class=""><strong>Polling</strong>：需要客戶端頻繁請求，耗費頻寬與伺服器資源。</li>
<li class=""><strong>WebSocket</strong>：雙向溝通更靈活，但需要額外處理協議與狀態管理。</li>
<li class=""><strong>EventSource（SSE）</strong>：單向推送即可，實作簡單、輕量化，適合多數即時通知場景。</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="eventsource-使用範例">EventSource 使用範例<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#eventsource-%E4%BD%BF%E7%94%A8%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="EventSource 使用範例的直接連結" title="EventSource 使用範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-瀏覽器端javascript">1. 瀏覽器端（JavaScript）<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#1-%E7%80%8F%E8%A6%BD%E5%99%A8%E7%AB%AFjavascript" class="hash-link" aria-label="1. 瀏覽器端（JavaScript）的直接連結" title="1. 瀏覽器端（JavaScript）的直接連結" translate="no">​</a></h3>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 建立 EventSource 連線</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> eventSource </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">EventSource</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/events'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 接收預設訊息（message 事件）</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">eventSource</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method-variable function-variable method function property-access" style="color:rgb(80, 250, 123)">onmessage</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">event</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'收到訊息:'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> event</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 接收自訂事件</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">eventSource</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">addEventListener</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'news'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">event</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'收到新聞事件:'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> event</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 監控錯誤與連線狀態</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">eventSource</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method-variable function-variable method function property-access" style="color:rgb(80, 250, 123)">onerror</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'EventSource 發生錯誤:'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-伺服器端nodejs-express-範例">2. 伺服器端（Node.js Express 範例）<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#2-%E4%BC%BA%E6%9C%8D%E5%99%A8%E7%AB%AFnodejs-express-%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="2. 伺服器端（Node.js Express 範例）的直接連結" title="2. 伺服器端（Node.js Express 範例）的直接連結" translate="no">​</a></h3>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> express </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">require</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'express'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> app </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">express</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/events'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 設定 SSE 必要的 Header</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Content-Type'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'text/event-stream'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Cache-Control'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'no-cache'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Connection'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'keep-alive'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 每隔 3 秒推送一個訊息</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> intervalId </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">setInterval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> data </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token plain"> </span><span class="token class-name">Date</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">toLocaleTimeString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">data: 現在時間 </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">data</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">\n\n</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 當客戶端中斷連線時清理資源</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  req</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'close'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">clearInterval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">intervalId</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">listen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'SSE 伺服器運行於 http://localhost:3000/events'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-sse-資料格式範例">3. SSE 資料格式範例<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#3-sse-%E8%B3%87%E6%96%99%E6%A0%BC%E5%BC%8F%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="3. SSE 資料格式範例的直接連結" title="3. SSE 資料格式範例的直接連結" translate="no">​</a></h3>
<p>伺服器回傳的資料需符合 <code>text/event-stream</code> 格式，每筆訊息以兩個換行結尾。</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">data: 這是一個預設訊息</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">event: news</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">data: 這是一個新聞更新</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">event: alert</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">data: 系統警告訊息</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">retry: 5000</span><br></span></code></pre></div></div>
<p>說明：</p>
<ul>
<li class=""><code>data:</code>：主要訊息內容，可以有多行。</li>
<li class=""><code>event:</code>：指定自訂事件名稱，客戶端可用 <code>addEventListener</code> 監聽。</li>
<li class=""><code>retry:</code>：定義自動重連的延遲時間（毫秒）。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意事項">注意事項<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85" class="hash-link" aria-label="注意事項的直接連結" title="注意事項的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><strong>瀏覽器支援性</strong>：大部分現代瀏覽器支援 EventSource（IE 除外）。</li>
<li class=""><strong>跨域問題</strong>：若伺服器與前端不同網域，需設定 CORS。</li>
<li class=""><strong>資料格式</strong>：僅支援 UTF-8 文字資料，若要傳送二進位資料需轉成 Base64 或 JSON。</li>
<li class=""><strong>連線數限制</strong>：部分瀏覽器對同一網域的 SSE 連線數有限制（通常 6 條）。</li>
<li class=""><strong>斷線重連</strong>：內建自動重連機制，但若伺服器返回錯誤狀態碼，可能需手動處理。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-event-source-api-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>EventSource API 提供了一個簡單又高效的方式，讓前端應用程式能夠輕鬆接收伺服器的即時推送。相較於 WebSocket，EventSource 不需要額外的協議處理，也避免了頻繁輪詢帶來的效能浪費。在僅需單向資料更新的場景下，它是一個理想解決方案。</p>
<p>當我們下次需要在前端實現即時通知、動態更新數據或流式資料顯示時，不妨先考慮 EventSource，它或許就是最輕量的選擇。</p>]]></content:encoded>
            <category>SSE</category>
            <category>EventSource</category>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>frontend</category>
            <category>frontend engineer</category>
            <category>CSS</category>
        </item>
        <item>
            <title><![CDATA[Server-Sent Events（SSE）入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial copy</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial copy</guid>
            <pubDate>Thu, 30 Jan 2025 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在現代 Web 應用中，即時資料傳輸已經成為重要需求，例如股票行情更新、聊天室訊息、即時通知等。傳統的 HTTP 請求模式是 <strong>客戶端發起請求，伺服器回應一次</strong>，這種「單向請求-回應」模式無法有效支持持續更新。</p>
<p>WebSocket 是一個廣為人知的解決方案，提供全雙工通道，但對於單向更新（伺服器推送到客戶端）而言，WebSocket 可能略顯複雜。這時，<strong>Server-Sent Events（SSE）</strong> 就是一個簡單、輕量級的替代方案。SSE 提供 <strong>伺服器到客戶端的單向持續資料推送</strong>，使用原生 HTML5 技術即可實現，不需額外協議或第三方套件。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sse-的核心概念">SSE 的核心概念<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#sse-%E7%9A%84%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="SSE 的核心概念的直接連結" title="SSE 的核心概念的直接連結" translate="no">​</a></h2>
<p>Server-Sent Events 是基於 <strong>HTTP 協議</strong>，透過 <strong>text/event-stream</strong> 的 MIME 類型，保持一個持久連接，讓伺服器可以不斷地向客戶端傳送資料。SSE 的主要特性包括：</p>
<ol>
<li class="">
<p><strong>單向通訊</strong></p>
<ul>
<li class="">伺服器向客戶端推送資料，客戶端只能接收。若需要雙向通訊，仍需搭配 WebSocket 或 AJAX 請求。</li>
</ul>
</li>
<li class="">
<p><strong>自動重連</strong></p>
<ul>
<li class="">客戶端若因網路斷線或其他原因中斷，瀏覽器會自動重連，可透過 <code>retry</code> 指定重連間隔。</li>
</ul>
</li>
<li class="">
<p><strong>文字格式傳輸</strong></p>
<ul>
<li class="">SSE 傳輸的資料以純文字格式為主，每個事件用特定的格式標記（如 <code>event:</code>、<code>data:</code>、<code>id:</code> 等）。</li>
</ul>
</li>
<li class="">
<p><strong>支援事件命名</strong></p>
<ul>
<li class="">可以自定義事件名稱，在前端監聽不同事件，提高可讀性與維護性。</li>
</ul>
</li>
<li class="">
<p><strong>輕量且易實作</strong></p>
<ul>
<li class="">客戶端只需使用 JavaScript 的 <code>EventSource</code> API，即可快速建立 SSE 連接。</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>單向連接</strong>：伺服器 → 客戶端</p>
</li>
<li class="">
<p><strong>持久連接</strong>：不需每次重新建立 HTTP 請求</p>
</li>
<li class="">
<p><strong>事件格式</strong>：<code>event</code>, <code>data</code>, <code>id</code>, <code>retry</code></p>
</li>
<li class="">
<p><strong>自動重連</strong>：瀏覽器自動處理連線中斷</p>
</li>
<li class="">
<p><strong>瀏覽器支援</strong>：大部分現代瀏覽器原生支援（IE 除外）</p>
</li>
<li class="">
<p><strong>適用場景</strong>：</p>
<ul>
<li class="">即時通知系統</li>
<li class="">資料更新流（股票、天氣、社群消息）</li>
<li class="">運行狀態監控儀表板</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sse-的資料格式">SSE 的資料格式<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#sse-%E7%9A%84%E8%B3%87%E6%96%99%E6%A0%BC%E5%BC%8F" class="hash-link" aria-label="SSE 的資料格式的直接連結" title="SSE 的資料格式的直接連結" translate="no">​</a></h2>
<p>SSE 的資料格式遵循 <strong>行為約定</strong>：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">id: 1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">event: message</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">retry: 3000</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">data: Hello, this is a message from server</span><br></span></code></pre></div></div>
<p>說明：</p>
<ul>
<li class=""><strong>id</strong>（選填）：事件 ID，客戶端斷線後重連，會告訴伺服器最後接收的 ID</li>
<li class=""><strong>event</strong>（選填）：事件名稱，自訂事件類型</li>
<li class=""><strong>retry</strong>（選填）：重連間隔，單位為毫秒</li>
<li class=""><strong>data</strong>（必填）：事件資料內容，可有多行</li>
</ul>
<p><strong>注意</strong>：每個事件以空行結束，代表事件結束。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sse-實作範例">SSE 實作範例<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#sse-%E5%AF%A6%E4%BD%9C%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="SSE 實作範例的直接連結" title="SSE 實作範例的直接連結" translate="no">​</a></h2>
<p>以下示範如何使用 <strong>Node.js + Express</strong> 建立 SSE 服務，並在前端接收事件：</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-後端-nodejs--express">1. 後端 (Node.js + Express)<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#1-%E5%BE%8C%E7%AB%AF-nodejs--express" class="hash-link" aria-label="1. 後端 (Node.js + Express)的直接連結" title="1. 後端 (Node.js + Express)的直接連結" translate="no">​</a></h3>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> express </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">require</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'express'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> app </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">express</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/events'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">req</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 設定 SSE 標頭</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Content-Type'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'text/event-stream'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Cache-Control'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'no-cache'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setHeader</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Connection'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'keep-alive'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">let</span><span class="token plain"> counter </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 每秒推送一次訊息</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> interval </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">setInterval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    counter</span><span class="token operator">++</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">id: </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">counter</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">\n</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">event: message\n</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">write</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">data: {"count": </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">counter</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">, "time": "</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token template-string interpolation"> </span><span class="token template-string interpolation class-name">Date</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation method function property-access" style="color:rgb(80, 250, 123)">toISOString</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">"}\n\n</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">1000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 當客戶端斷線時清理 interval</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  req</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">on</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'close'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">clearInterval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">interval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">listen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">3000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'SSE server running on http://localhost:3000'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-前端-html--javascript">2. 前端 (HTML + JavaScript)<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#2-%E5%89%8D%E7%AB%AF-html--javascript" class="hash-link" aria-label="2. 前端 (HTML + JavaScript)的直接連結" title="2. 前端 (HTML + JavaScript)的直接連結" translate="no">​</a></h3>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token doctype punctuation" style="color:rgb(248, 248, 242)">&lt;!</span><span class="token doctype doctype-tag">DOCTYPE</span><span class="token doctype"> </span><span class="token doctype name">html</span><span class="token doctype punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">html</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">head</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">title</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">SSE Example</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">title</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">head</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">body</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">Server-Sent Events Demo</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">messages</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">script</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token script language-javascript"> eventSource </span><span class="token script language-javascript operator">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">new</span><span class="token script language-javascript"> </span><span class="token script language-javascript class-name">EventSource</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript string" style="color:rgb(255, 121, 198)">'http://localhost:3000/events'</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      eventSource</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method-variable function-variable method function property-access" style="color:rgb(80, 250, 123)">onmessage</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript parameter">event</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token script language-javascript"> msgDiv </span><span class="token script language-javascript operator">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method function property-access" style="color:rgb(80, 250, 123)">getElementById</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript string" style="color:rgb(255, 121, 198)">'messages'</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token script language-javascript"> data </span><span class="token script language-javascript operator">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript known-class-name class-name">JSON</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method function property-access" style="color:rgb(80, 250, 123)">parse</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript">event</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript property-access">data</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        msgDiv</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript property-access">innerHTML</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator">+=</span><span class="token script language-javascript"> </span><span class="token script language-javascript template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token script language-javascript template-string embedded-code html">&lt;p&gt;Count: ${data.count}, Time: ${data.time}&lt;/p&gt;</span><span class="token script language-javascript template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      eventSource</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method function property-access" style="color:rgb(80, 250, 123)">addEventListener</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript string" style="color:rgb(255, 121, 198)">'message'</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript parameter">event</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        </span><span class="token script language-javascript comment" style="color:rgb(98, 114, 164)">// 也可透過自訂事件名稱接收</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        </span><span class="token script language-javascript console class-name">console</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript string" style="color:rgb(255, 121, 198)">'Received event:'</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token script language-javascript"> event</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript property-access">data</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      eventSource</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method-variable function-variable method function property-access" style="color:rgb(80, 250, 123)">onerror</span><span class="token script language-javascript"> </span><span class="token script language-javascript operator">=</span><span class="token script language-javascript"> </span><span class="token script language-javascript keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript parameter">err</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript"> </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">        </span><span class="token script language-javascript console class-name">console</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token script language-javascript method function property-access" style="color:rgb(80, 250, 123)">error</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token script language-javascript string" style="color:rgb(255, 121, 198)">'EventSource failed:'</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token script language-javascript"> err</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">      </span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token script language-javascript"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token script language-javascript">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">script</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">body</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">html</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></span></code></pre></div></div>
<p><strong>解釋</strong>：</p>
<ul>
<li class=""><code>EventSource</code> 會自動建立持久連接</li>
<li class="">每次後端 <code>res.write</code> 新事件時，前端 <code>onmessage</code> 會被觸發</li>
<li class="">客戶端不需發起額外請求，伺服器可以持續推送</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="sse-與-websocket-的比較">SSE 與 WebSocket 的比較<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#sse-%E8%88%87-websocket-%E7%9A%84%E6%AF%94%E8%BC%83" class="hash-link" aria-label="SSE 與 WebSocket 的比較的直接連結" title="SSE 與 WebSocket 的比較的直接連結" translate="no">​</a></h2>
<table><thead><tr><th>特性</th><th>SSE</th><th>WebSocket</th></tr></thead><tbody><tr><td>通訊方向</td><td>伺服器 → 客戶端（單向）</td><td>雙向（伺服器 ↔ 客戶端）</td></tr><tr><td>協議</td><td>HTTP</td><td>TCP / WebSocket protocol</td></tr><tr><td>連接維護</td><td>自動重連</td><td>需手動處理斷線</td></tr><tr><td>輕量實作</td><td>非常簡單</td><td>相對複雜</td></tr><tr><td>適用場景</td><td>事件推送、資料流</td><td>即時遊戲、聊天室、雙向互動</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實務應用建議">實務應用建議<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#%E5%AF%A6%E5%8B%99%E6%87%89%E7%94%A8%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="實務應用建議的直接連結" title="實務應用建議的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>適用場景</strong>：單向更新、事件通知、儀表板數據刷新</p>
</li>
<li class="">
<p><strong>不適用場景</strong>：需要大量雙向互動、低延遲遊戲或高頻交易</p>
</li>
<li class="">
<p><strong>瀏覽器支援</strong>：大部分現代瀏覽器支援，但 IE 需 polyfill</p>
</li>
<li class="">
<p><strong>部署注意</strong>：</p>
<ul>
<li class="">長連接可能對 Nginx / Proxy 設定有影響，需允許長時間 HTTP keep-alive</li>
<li class="">伺服器端要注意資源釋放，避免記憶體泄漏</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結論">結論<a href="https://tech.kdchang.com/blog/learning-notes-git-intro-tutorial%20copy#%E7%B5%90%E8%AB%96" class="hash-link" aria-label="結論的直接連結" title="結論的直接連結" translate="no">​</a></h2>
<p>Server-Sent Events 是一種 <strong>簡單、輕量、易於實作的即時資料推送方案</strong>，特別適合需要 <strong>單向事件更新</strong> 的 Web 應用。對於需要雙向通訊的場景，WebSocket 或其他方案可能更合適。掌握 SSE 可以讓我們在開發即時應用、監控儀表板或通知系統時快速上手，並結合現代瀏覽器的原生支援，提供穩定的即時體驗。</p>]]></content:encoded>
            <category>SSE</category>
            <category>stream</category>
            <category>Server-Sent Events</category>
        </item>
        <item>
            <title><![CDATA[前端 i18n 入門教學與注意事項整理筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial copy</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial copy</guid>
            <pubDate>Tue, 14 Jan 2025 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在現今的全球化應用中，網站或產品若希望觸及更多用戶，提供多語系支援幾乎是必須的功能。這就是所謂的國際化（Internationalization，簡稱 i18n），意即在程式設計階段預先做好結構設計，使系統能根據不同語言與地區的需求，自動載入對應的文案、格式與顯示方式。</p>
<p>本篇筆記將說明前端 i18n 的核心觀念、開發時常見的注意事項，以及如何透過實際程式碼實作一個簡單的多語系功能，協助你快速掌握前端 i18n 的基本功。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要i18n-實作注意事項">重點摘要：i18n 實作注意事項<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81i18n-%E5%AF%A6%E4%BD%9C%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85" class="hash-link" aria-label="重點摘要：i18n 實作注意事項的直接連結" title="重點摘要：i18n 實作注意事項的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><strong>避免硬編碼文字</strong>：所有顯示文字應抽離為 key-value 翻譯檔，便於日後維護與翻譯。</li>
<li class=""><strong>使用成熟 i18n 套件</strong>：例如 React 的 <code>react-i18next</code>、Vue 的 <code>vue-i18n</code>。</li>
<li class=""><strong>結構化管理翻譯檔案</strong>：依功能模組分類翻譯內容，避免 key 混亂或重複。</li>
<li class=""><strong>支援變數插值與格式化</strong>：例如姓名、時間、數字等內容應透過參數傳遞給翻譯函數。</li>
<li class=""><strong>避免字串拼接組合句子</strong>：不同語言語序不同，拼接容易導致語意錯誤。</li>
<li class=""><strong>設計 UI 時預留文字空間</strong>：不同語言的字串長度可能差異很大。</li>
<li class=""><strong>處理 RTL 語言與排版</strong>：如阿拉伯語需設定 <code>direction: rtl</code>，必要時翻轉 UI。</li>
<li class=""><strong>提供語系切換機制與偵測</strong>：可從 <code>navigator.language</code>、URL、cookie 判斷語系。</li>
<li class=""><strong>設計 fallback 機制</strong>：若某語系未翻譯的 key，應自動 fallback 至預設語系。</li>
<li class=""><strong>翻譯流程建議自動化與工具化</strong>：搭配翻譯平台（如 Lokalise、Crowdin）管理翻譯流程與品質。</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實作範例使用-react--react-i18next-實現簡單的-i18n-功能">實作範例：使用 React + react-i18next 實現簡單的 i18n 功能<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E5%AF%A6%E4%BD%9C%E7%AF%84%E4%BE%8B%E4%BD%BF%E7%94%A8-react--react-i18next-%E5%AF%A6%E7%8F%BE%E7%B0%A1%E5%96%AE%E7%9A%84-i18n-%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="實作範例：使用 React + react-i18next 實現簡單的 i18n 功能的直接連結" title="實作範例：使用 React + react-i18next 實現簡單的 i18n 功能的直接連結" translate="no">​</a></h2>
<p>假設我們有一個需要支援中英文切換的 React 專案，以下將一步步實作基本功能。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟一安裝相關套件">步驟一：安裝相關套件<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E6%AD%A5%E9%A9%9F%E4%B8%80%E5%AE%89%E8%A3%9D%E7%9B%B8%E9%97%9C%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="步驟一：安裝相關套件的直接連結" title="步驟一：安裝相關套件的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">npm install i18next react-i18next i18next-browser-languagedetector</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟二建立翻譯檔案放在-srclocales">步驟二：建立翻譯檔案（放在 <code>src/locales/</code>）<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E6%AD%A5%E9%A9%9F%E4%BA%8C%E5%BB%BA%E7%AB%8B%E7%BF%BB%E8%AD%AF%E6%AA%94%E6%A1%88%E6%94%BE%E5%9C%A8-srclocales" class="hash-link" aria-label="步驟二建立翻譯檔案放在-srclocales的直接連結" title="步驟二建立翻譯檔案放在-srclocales的直接連結" translate="no">​</a></h3>
<p><strong>src/locales/en/translation.json</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"greeting"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Hello, {{name}}!"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"home"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"title"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Welcome to the homepage"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>src/locales/zh/translation.json</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"greeting"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"哈囉，{{name}}！"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"home"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"title"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"歡迎來到首頁"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟三初始化-i18n-設定srci18njs">步驟三：初始化 i18n 設定（src/i18n.js）<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E6%AD%A5%E9%A9%9F%E4%B8%89%E5%88%9D%E5%A7%8B%E5%8C%96-i18n-%E8%A8%AD%E5%AE%9Asrci18njs" class="hash-link" aria-label="步驟三：初始化 i18n 設定（src/i18n.js）的直接連結" title="步驟三：初始化 i18n 設定（src/i18n.js）的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports">i18n</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'i18next'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> initReactI18next </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-i18next'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">LanguageDetector</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'i18next-browser-languagedetector'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports">en</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./locales/en/translation.json'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports">zh</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./locales/zh/translation.json'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">i18n</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">use</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token maybe-class-name">LanguageDetector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">use</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">initReactI18next</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">init</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">resources</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">en</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">translation</span><span class="token operator">:</span><span class="token plain"> en </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">zh</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">translation</span><span class="token operator">:</span><span class="token plain"> zh </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">fallbackLng</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'en'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">interpolation</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">escapeValue</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">false</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> i18n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟四在應用入口引入-i18n-設定例如-indexjs">步驟四：在應用入口引入 i18n 設定（例如 index.js）<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E6%AD%A5%E9%A9%9F%E5%9B%9B%E5%9C%A8%E6%87%89%E7%94%A8%E5%85%A5%E5%8F%A3%E5%BC%95%E5%85%A5-i18n-%E8%A8%AD%E5%AE%9A%E4%BE%8B%E5%A6%82-indexjs" class="hash-link" aria-label="步驟四：在應用入口引入 i18n 設定（例如 index.js）的直接連結" title="步驟四：在應用入口引入 i18n 設定（例如 index.js）的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">ReactDOM</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-dom'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">App</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./App'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./i18n'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token maybe-class-name">ReactDOM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">render</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&lt;</span><span class="token maybe-class-name">App</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">getElementById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'root'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟五在元件中使用翻譯">步驟五：在元件中使用翻譯<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E6%AD%A5%E9%A9%9F%E4%BA%94%E5%9C%A8%E5%85%83%E4%BB%B6%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%BF%BB%E8%AD%AF" class="hash-link" aria-label="步驟五：在元件中使用翻譯的直接連結" title="步驟五：在元件中使用翻譯的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useTranslation </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-i18next'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">HomePage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> i18n </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useTranslation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">changeLanguage</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">lang</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    i18n</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">changeLanguage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">lang</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token function" style="color:rgb(80, 250, 123)">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'home.title'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token function" style="color:rgb(80, 250, 123)">t</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'greeting'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'KD'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">changeLanguage</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript string" style="color:rgb(255, 121, 198)">'en'</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">English</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">changeLanguage</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript string" style="color:rgb(255, 121, 198)">'zh'</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">中文</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">button</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token maybe-class-name">HomePage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="預期畫面輸出">預期畫面輸出<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E9%A0%90%E6%9C%9F%E7%95%AB%E9%9D%A2%E8%BC%B8%E5%87%BA" class="hash-link" aria-label="預期畫面輸出的直接連結" title="預期畫面輸出的直接連結" translate="no">​</a></h3>
<p>使用者進入頁面，根據瀏覽器語言自動載入對應語系，或透過按鈕切換語言：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">歡迎來到首頁</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">哈囉，KD！</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">[English] [中文]</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-frontend-i18n-intro-tutorial%20copy#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>i18n 是每個想要「走向國際」的產品所必備的基礎建設之一。透過妥善設計的翻譯架構與工具整合，不僅能提升使用者體驗，也有助於日後擴展新市場與新語系。</p>
<p>建議開發者在專案初期就規劃好 i18n 架構，並搭配良好的團隊流程與翻譯管理工具，將繁瑣的翻譯作業系統化，避免日後重構的成本。</p>]]></content:encoded>
            <category>i18n</category>
            <category>react</category>
            <category>vue</category>
        </item>
        <item>
            <title><![CDATA[微前端（Micro-Frontend）介紹與入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial</guid>
            <pubDate>Tue, 31 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>隨著前端應用日益複雜、團隊規模擴大，「前端單體應用」（Monolithic Frontend）逐漸面臨維護困難、部署不靈活、開發效率低落等問題。微前端（Micro-Frontend）是一種將大型前端應用拆解為數個獨立子應用的架構設計理念，靈感來自後端的微服務（Microservices）架構。每個子應用可以由不同的團隊獨立開發、部署、維護，並共同組成一個整體的產品。</p>
<p>微前端不是某個框架，而是一種架構模式。它的目標是促進前端大型專案的模組化、團隊分工清晰、技術選型彈性，進而提升整體開發與交付效率。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="微前端的常見做法">微前端的常見做法<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E5%BE%AE%E5%89%8D%E7%AB%AF%E7%9A%84%E5%B8%B8%E8%A6%8B%E5%81%9A%E6%B3%95" class="hash-link" aria-label="微前端的常見做法的直接連結" title="微前端的常見做法的直接連結" translate="no">​</a></h3>
<ol>
<li class="">Module Federation（Webpack 5 原生支援，超常見）</li>
</ol>
<ul>
<li class="">各子應用直接共享模組，不用重複打包</li>
<li class="">Webpack 官方強項，Vite 沒有原生支援</li>
</ul>
<ol start="2">
<li class="">iframe / Web Component（框架無關，通用做法）</li>
</ol>
<ul>
<li class="">每個子應用獨立部署，用 iframe 或 custom elements 包裝</li>
<li class="">Vite/React/Vue/Angular 都可以做</li>
</ul>
<ol start="3">
<li class="">乾淨的 build output + 部署整合</li>
</ol>
<ul>
<li class="">
<p>子應用都 build 出靜態資源，整合到主應用路由</p>
</li>
<li class="">
<p>跟工具無關，Vite 也能勝任</p>
</li>
<li class="">
<p><strong>定義</strong>：微前端是一種將前端應用拆解為多個獨立子應用的架構設計模式。</p>
</li>
<li class="">
<p><strong>目的</strong>：</p>
<ul>
<li class="">支援大型團隊並行開發</li>
<li class="">提高部署彈性（單一子應用可獨立上線）</li>
<li class="">增加技術選擇自由度（不同子應用可使用不同框架）</li>
</ul>
</li>
<li class="">
<p><strong>核心概念</strong>：</p>
<ul>
<li class="">子應用獨立開發、測試與部署</li>
<li class="">主應用統一載入與整合子應用</li>
<li class="">子應用可共享部分資源（如登入狀態、UI 元件）</li>
</ul>
</li>
<li class="">
<p><strong>常見實作方式</strong>：</p>
<ul>
<li class="">iframe（早期簡單做法，但 UX 不佳）</li>
<li class="">Web Component（標準化但整合與溝通略複雜）</li>
<li class="">JavaScript 插入與渲染（如 single-spa、Module Federation）</li>
</ul>
</li>
<li class="">
<p><strong>適用時機</strong>：</p>
<ul>
<li class="">專案規模大、開發團隊超過 2 組以上</li>
<li class="">需要支援異步部署與灰階上線</li>
<li class="">跨框架共存需求（如同時有 React 與 Vue）</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微前端實作方式簡介">微前端實作方式簡介<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E5%BE%AE%E5%89%8D%E7%AB%AF%E5%AF%A6%E4%BD%9C%E6%96%B9%E5%BC%8F%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="微前端實作方式簡介的直接連結" title="微前端實作方式簡介的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-iframe不推薦">1. iframe（不推薦）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#1-iframe%E4%B8%8D%E6%8E%A8%E8%96%A6" class="hash-link" aria-label="1. iframe（不推薦）的直接連結" title="1. iframe（不推薦）的直接連結" translate="no">​</a></h3>
<p>將子應用放入 <code>iframe</code> 中載入，雖然簡單，但隔離性太強（無法共用狀態、樣式），SEO 和體驗差，不推薦用於現代 Web 專案。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-web-components中立">2. Web Components（中立）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#2-web-components%E4%B8%AD%E7%AB%8B" class="hash-link" aria-label="2. Web Components（中立）的直接連結" title="2. Web Components（中立）的直接連結" translate="no">​</a></h3>
<p>透過瀏覽器原生的 Custom Elements 技術（如 <code>my-app-widget</code>），讓子應用變成一個可重用的 HTML 元件，框架中立，但整合難度高。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-javascript-插入與路由分流主流">3. JavaScript 插入與路由分流（主流）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#3-javascript-%E6%8F%92%E5%85%A5%E8%88%87%E8%B7%AF%E7%94%B1%E5%88%86%E6%B5%81%E4%B8%BB%E6%B5%81" class="hash-link" aria-label="3. JavaScript 插入與路由分流（主流）的直接連結" title="3. JavaScript 插入與路由分流（主流）的直接連結" translate="no">​</a></h3>
<p>由主應用動態載入子應用（HTML、JS、CSS），並透過路由或 DOM 控制子應用顯示。可使用像是：</p>
<ul>
<li class=""><a href="https://single-spa.js.org/" target="_blank" rel="noopener noreferrer" class="">single-spa</a></li>
<li class=""><a href="https://webpack.js.org/concepts/module-federation/" target="_blank" rel="noopener noreferrer" class="">Module Federation</a></li>
<li class=""><a href="https://qiankun.umijs.org/" target="_blank" rel="noopener noreferrer" class="">qiankun</a></li>
</ul>
<p>其中 <code>qiankun</code> 是阿里開源的基於 <code>single-spa</code> 的微前端框架，中文文件齊全且上手容易。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例使用-qiankun-快速建立微前端架構">實際範例：使用 qiankun 快速建立微前端架構<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E4%BD%BF%E7%94%A8-qiankun-%E5%BF%AB%E9%80%9F%E5%BB%BA%E7%AB%8B%E5%BE%AE%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%A7%8B" class="hash-link" aria-label="實際範例：使用 qiankun 快速建立微前端架構的直接連結" title="實際範例：使用 qiankun 快速建立微前端架構的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例說明">範例說明<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E7%AF%84%E4%BE%8B%E8%AA%AA%E6%98%8E" class="hash-link" aria-label="範例說明的直接連結" title="範例說明的直接連結" translate="no">​</a></h3>
<p>目標：建立一個主應用（main-app），載入兩個子應用（react-app、vue-app）</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-安裝-qiankun主應用">1. 安裝 qiankun（主應用）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#1-%E5%AE%89%E8%A3%9D-qiankun%E4%B8%BB%E6%87%89%E7%94%A8" class="hash-link" aria-label="1. 安裝 qiankun（主應用）的直接連結" title="1. 安裝 qiankun（主應用）的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">npm install qiankun</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-主應用主體程式碼main-appsrcmaints">2. 主應用主體程式碼（main-app/src/main.ts）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#2-%E4%B8%BB%E6%87%89%E7%94%A8%E4%B8%BB%E9%AB%94%E7%A8%8B%E5%BC%8F%E7%A2%BCmain-appsrcmaints" class="hash-link" aria-label="2. 主應用主體程式碼（main-app/src/main.ts）的直接連結" title="2. 主應用主體程式碼（main-app/src/main.ts）的直接連結" translate="no">​</a></h3>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> registerMicroApps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> start </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'qiankun'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">registerMicroApps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-app'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    entry</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'//localhost:3001'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    container</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'#subapp-container'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    activeRule</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'vue-app'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    entry</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'//localhost:3002'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    container</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'#subapp-container'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    activeRule</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'/vue'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">start</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-主應用-html-模板">3. 主應用 HTML 模板<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#3-%E4%B8%BB%E6%87%89%E7%94%A8-html-%E6%A8%A1%E6%9D%BF" class="hash-link" aria-label="3. 主應用 HTML 模板的直接連結" title="3. 主應用 HTML 模板的直接連結" translate="no">​</a></h3>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">subapp-container</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-子應用需支援-qiankun-的生命週期函式以-react-為例">4. 子應用需支援 qiankun 的生命週期函式（以 React 為例）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#4-%E5%AD%90%E6%87%89%E7%94%A8%E9%9C%80%E6%94%AF%E6%8F%B4-qiankun-%E7%9A%84%E7%94%9F%E5%91%BD%E9%80%B1%E6%9C%9F%E5%87%BD%E5%BC%8F%E4%BB%A5-react-%E7%82%BA%E4%BE%8B" class="hash-link" aria-label="4. 子應用需支援 qiankun 的生命週期函式（以 React 為例）的直接連結" title="4. 子應用需支援 qiankun 的生命週期函式（以 React 為例）的直接連結" translate="no">​</a></h3>
<div class="language-ts codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ts codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">bootstrap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin" style="color:rgb(189, 147, 249)">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'React app bootstraped'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">mount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">props</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ReactDOM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">render</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token operator">&lt;</span><span class="token plain">App </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getElementById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'root'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">unmount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  ReactDOM</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">unmountComponentAtNode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token function" style="color:rgb(80, 250, 123)">getElementById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'root'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-子應用-webpack-設定publicpath">5. 子應用 Webpack 設定（publicPath）<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#5-%E5%AD%90%E6%87%89%E7%94%A8-webpack-%E8%A8%AD%E5%AE%9Apublicpath" class="hash-link" aria-label="5. 子應用 Webpack 設定（publicPath）的直接連結" title="5. 子應用 Webpack 設定（publicPath）的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token literal-property property">output</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">publicPath</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'http://localhost:3001/'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="微前端的挑戰與注意事項">微前端的挑戰與注意事項<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E5%BE%AE%E5%89%8D%E7%AB%AF%E7%9A%84%E6%8C%91%E6%88%B0%E8%88%87%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85" class="hash-link" aria-label="微前端的挑戰與注意事項的直接連結" title="微前端的挑戰與注意事項的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>樣式隔離</strong>：CSS 必須避免衝突，可搭配 CSS Modules、Scoped CSS。</li>
<li class=""><strong>狀態共享</strong>：登入資訊、使用者資料等需透過 global event 或共享 storage 處理。</li>
<li class=""><strong>路由協調</strong>：子應用與主應用須協調 route 設計，避免相互干擾。</li>
<li class=""><strong>部署整合</strong>：CI/CD pipeline 需考慮子應用與主應用的獨立部署與測試。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-micro-frontend-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>微前端是一種極具彈性的架構設計理念，適合中大型團隊協作、複雜前端系統的模組化開發。不過它也帶來額外的技術成本與整合挑戰。在決定導入微前端前，應評估專案規模、開發團隊結構與維運資源是否適合。</p>
<p>實作上，建議可從單一框架開始（如 React + qiankun），逐步拆分模組與部署機制，再逐步進化為多框架混合的微前端架構，避免過早複雜化系統。</p>]]></content:encoded>
            <category>npm</category>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>微前端</category>
            <category>Micro-Frontend</category>
        </item>
        <item>
            <title><![CDATA[React 效能優化入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial</guid>
            <pubDate>Fri, 27 Dec 2024 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>隨著前端應用日益龐大，單頁應用（SPA）在初次載入時常面臨 JavaScript 檔案過大、載入時間過久的問題，導致使用者等待時間過長、效能下降。為了解決這個問題，React 與現代建構工具（如 Webpack、Vite）提供了 Code Splitting（程式碼分割）與 Lazy Loading（延遲載入）兩種策略，協助開發者更有效地管理與優化應用程式的載入流程。</p>
<p>程式碼分割（Code Splitting）和惰性載入（Lazy Loading）都是用來優化網頁效能的方法，它們都旨在減少初始加載時間，但實現方式和目標略有不同。 程式碼分割是將程式碼分割成多個較小的塊，而惰性載入則是在需要時才加載這些塊。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一程式碼分割code-splitting">一、程式碼分割（Code Splitting）<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E4%B8%80%E7%A8%8B%E5%BC%8F%E7%A2%BC%E5%88%86%E5%89%B2code-splitting" class="hash-link" aria-label="一、程式碼分割（Code Splitting）的直接連結" title="一、程式碼分割（Code Splitting）的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="概念">概念:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E6%A6%82%E5%BF%B5" class="hash-link" aria-label="概念:的直接連結" title="概念:的直接連結" translate="no">​</a></h3>
<p>程式碼分割是將一個大型的 JavaScript 應用程式分割成多個較小的、獨立的塊，每個塊包含應用程式的一部分程式碼。 這些塊通常是根據路由、元件或功能來分割的。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="目標">目標:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%9B%AE%E6%A8%99" class="hash-link" aria-label="目標:的直接連結" title="目標:的直接連結" translate="no">​</a></h3>
<p>主要目標是減少應用程式的初始加載時間，通過只加載使用者當前需要的程式碼塊，而不是一次性加載所有程式碼。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實現方式">實現方式:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%AF%A6%E7%8F%BE%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="實現方式:的直接連結" title="實現方式:的直接連結" translate="no">​</a></h3>
<p>程式碼分割通常使用打包工具（如 Webpack、Rollup 等）和動態<code>import()</code>語法來實現。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用時機">使用時機:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E4%BD%BF%E7%94%A8%E6%99%82%E6%A9%9F" class="hash-link" aria-label="使用時機:的直接連結" title="使用時機:的直接連結" translate="no">​</a></h3>
<p>在編譯時（build time）就進行分割。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二惰性載入lazy-loading">二、惰性載入（Lazy Loading）<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E4%BA%8C%E6%83%B0%E6%80%A7%E8%BC%89%E5%85%A5lazy-loading" class="hash-link" aria-label="二、惰性載入（Lazy Loading）的直接連結" title="二、惰性載入（Lazy Loading）的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="概念-1">概念:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E6%A6%82%E5%BF%B5-1" class="hash-link" aria-label="概念:的直接連結" title="概念:的直接連結" translate="no">​</a></h3>
<p>惰性載入是指在需要的時候才加載程式碼，而不是在應用程式初始化時就加載所有程式碼。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="目標-1">目標:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%9B%AE%E6%A8%99-1" class="hash-link" aria-label="目標:的直接連結" title="目標:的直接連結" translate="no">​</a></h3>
<p>減少應用程式的初始加載時間，特別是對於大型應用程式或元件。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實現方式-1">實現方式:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%AF%A6%E7%8F%BE%E6%96%B9%E5%BC%8F-1" class="hash-link" aria-label="實現方式:的直接連結" title="實現方式:的直接連結" translate="no">​</a></h3>
<p>惰性載入通常使用 React.lazy 和 Suspense 元件來實現，也可以配合程式碼分割一起使用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用時機-1">使用時機:<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E4%BD%BF%E7%94%A8%E6%99%82%E6%A9%9F-1" class="hash-link" aria-label="使用時機:的直接連結" title="使用時機:的直接連結" translate="no">​</a></h3>
<p>在執行時（runtime）才加載，通常是當使用者訪問某個路由、觸發某個事件或需要顯示某個元件時。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="差異總結">差異總結<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%B7%AE%E7%95%B0%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="差異總結的直接連結" title="差異總結的直接連結" translate="no">​</a></h2>
<table><thead><tr><th>特性</th><th>程式碼分割 Code Splitting</th><th>惰性載入 Lazy Loading</th></tr></thead><tbody><tr><td>概念</td><td>將程式碼分割成多個塊</td><td>在需要時才加載程式碼</td></tr><tr><td>目標</td><td>減少初始加載時間，優化效能</td><td>減少初始加載時間，優化效能</td></tr><tr><td>實現方式</td><td>打包工具，dynamic import()</td><td>React.lazy, Suspense, dynamic import()</td></tr><tr><td>時機</td><td>編譯時</td><td>執行時</td></tr><tr><td>關聯性</td><td>程式碼分割是惰性載入的基礎，惰性載入可以利用程式碼分割的結果。</td><td></td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>Code Splitting（程式碼分割）</strong></p>
<ul>
<li class="">是一種將整個應用程式切割成多個檔案的技術</li>
<li class="">通常由 Webpack、Rollup 等建構工具自動處理</li>
<li class="">可應用於 route-based 分割、component-based 分割等情境</li>
<li class="">不代表一定是延遲載入，僅是結構上的切割</li>
</ul>
</li>
<li class="">
<p><strong>Lazy Loading（延遲載入）</strong></p>
<ul>
<li class="">是一種執行時載入程式碼的策略</li>
<li class="">常與 <code>import()</code> 搭配，直到實際使用時才載入</li>
<li class="">通常透過 <code>React.lazy</code>、<code>Suspense</code> 實現元件的懶載入</li>
<li class="">是 Code Splitting 的使用方式之一</li>
</ul>
</li>
<li class="">
<p><strong>兩者關係</strong></p>
<ul>
<li class="">Code Splitting 是靜態建構階段的優化策略</li>
<li class="">Lazy Loading 是執行階段的載入行為</li>
<li class="">Lazy Loading 必須建立在已做 Code Splitting 的前提上</li>
</ul>
</li>
<li class="">
<p><strong>效益</strong></p>
<ul>
<li class="">減少主程式 bundle 的大小</li>
<li class="">提升首次載入速度（First Contentful Paint）</li>
<li class="">延遲不必要的資源載入，節省頻寬與記憶體</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例">實際範例<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際範例的直接連結" title="實際範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例一傳統未分割的情況單一-bundle">範例一：傳統未分割的情況（單一 bundle）<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%80%E5%82%B3%E7%B5%B1%E6%9C%AA%E5%88%86%E5%89%B2%E7%9A%84%E6%83%85%E6%B3%81%E5%96%AE%E4%B8%80-bundle" class="hash-link" aria-label="範例一：傳統未分割的情況（單一 bundle）的直接連結" title="範例一：傳統未分割的情況（單一 bundle）的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// App.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">HomePage</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./HomePage'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">Dashboard</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./Dashboard'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">HomePage</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Dashboard</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>這樣寫會導致 HomePage 和 Dashboard 在應用一開始就被載入，無論使用者有沒有看到這些元件。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例二使用-reactlazy-實現-lazy-loading-與-code-splitting">範例二：使用 React.lazy 實現 Lazy Loading 與 Code Splitting<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%BA%8C%E4%BD%BF%E7%94%A8-reactlazy-%E5%AF%A6%E7%8F%BE-lazy-loading-%E8%88%87-code-splitting" class="hash-link" aria-label="範例二：使用 React.lazy 實現 Lazy Loading 與 Code Splitting的直接連結" title="範例二：使用 React.lazy 實現 Lazy Loading 與 Code Splitting的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// App.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Suspense</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// Lazy Loading：只有在渲染時才動態 import</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">HomePage</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./HomePage'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Dashboard</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./Dashboard'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">fallback</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript plain-text" style="color:rgb(255, 121, 198)">載入中...</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">HomePage</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Dashboard</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>使用 <code>React.lazy()</code> 搭配 <code>import()</code> 會讓 Webpack 將這些元件建立為獨立的 chunk。
真正渲染時（如使用者切換頁面），才會觸發載入行為，減少初始 bundle 體積。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例三route-based-code-splittingreact-router">範例三：Route-based Code Splitting（React Router）<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%89route-based-code-splittingreact-router" class="hash-link" aria-label="範例三：Route-based Code Splitting（React Router）的直接連結" title="範例三：Route-based Code Splitting（React Router）的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// AppRouter.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Suspense</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> lazy </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">BrowserRouter</span><span class="token imports"> </span><span class="token imports keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token imports"> </span><span class="token imports maybe-class-name">Router</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Route</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Switch</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-router-dom'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Home</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./pages/Home'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Dashboard</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./pages/Dashboard'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">AppRouter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Router</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">fallback</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript plain-text" style="color:rgb(255, 121, 198)">頁面載入中...</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Switch</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Route</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">path</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">/home</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript maybe-class-name" style="color:rgb(255, 121, 198)">Home</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Route</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">path</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">/dashboard</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript maybe-class-name" style="color:rgb(255, 121, 198)">Dashboard</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Switch</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Router</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>使用路由為單位切割頁面元件，是最常見的 Code Splitting 實務做法。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例四動態-import-實作非元件的延遲載入">範例四：動態 import 實作非元件的延遲載入<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%AF%84%E4%BE%8B%E5%9B%9B%E5%8B%95%E6%85%8B-import-%E5%AF%A6%E4%BD%9C%E9%9D%9E%E5%85%83%E4%BB%B6%E7%9A%84%E5%BB%B6%E9%81%B2%E8%BC%89%E5%85%A5" class="hash-link" aria-label="範例四：動態 import 實作非元件的延遲載入的直接連結" title="範例四：動態 import 實作非元件的延遲載入的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// utils.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">heavyCalculation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// 假設這段計算非常耗時</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> input </span><span class="token operator">**</span><span class="token plain"> </span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// App.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">handleClick</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./utils'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">then</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> heavyCalculation </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">heavyCalculation</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">5</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>在某些不需要立即執行的邏輯或大型工具函式庫，也可以透過 <code>import()</code> 動態載入來延遲其成本。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="常見問題與補充">常見問題與補充<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%B8%B8%E8%A6%8B%E5%95%8F%E9%A1%8C%E8%88%87%E8%A3%9C%E5%85%85" class="hash-link" aria-label="常見問題與補充的直接連結" title="常見問題與補充的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="q1code-splitting-是自動的嗎">Q1：Code Splitting 是自動的嗎？<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#q1code-splitting-%E6%98%AF%E8%87%AA%E5%8B%95%E7%9A%84%E5%97%8E" class="hash-link" aria-label="Q1：Code Splitting 是自動的嗎？的直接連結" title="Q1：Code Splitting 是自動的嗎？的直接連結" translate="no">​</a></h3>
<ul>
<li class="">大部分情況下需要手動設計入口點（如 <code>React.lazy</code> 或 <code>import()</code>），Webpack 才會建立分離的 chunk。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="q2只有使用-reactlazy-才能-lazy-load-嗎">Q2：只有使用 <code>React.lazy</code> 才能 Lazy Load 嗎？<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#q2%E5%8F%AA%E6%9C%89%E4%BD%BF%E7%94%A8-reactlazy-%E6%89%8D%E8%83%BD-lazy-load-%E5%97%8E" class="hash-link" aria-label="q2只有使用-reactlazy-才能-lazy-load-嗎的直接連結" title="q2只有使用-reactlazy-才能-lazy-load-嗎的直接連結" translate="no">​</a></h3>
<ul>
<li class="">不一定，<code>import()</code> 是底層機制，也可配合其他框架（Vue、Svelte）或工具（React Loadable）使用。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="q3懶載入的元件可以預載嗎">Q3：懶載入的元件可以預載嗎？<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#q3%E6%87%B6%E8%BC%89%E5%85%A5%E7%9A%84%E5%85%83%E4%BB%B6%E5%8F%AF%E4%BB%A5%E9%A0%90%E8%BC%89%E5%97%8E" class="hash-link" aria-label="Q3：懶載入的元件可以預載嗎？的直接連結" title="Q3：懶載入的元件可以預載嗎？的直接連結" translate="no">​</a></h3>
<ul>
<li class="">可以，透過 <code>import().then()</code> 觸發一次即可放進瀏覽器快取，達到「預熱」效果。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>在前端應用越來越大型化的今天，掌握 Code Splitting 與 Lazy Loading 的差異與使用場景，已成為每位前端工程師的必備技能。Code Splitting 解決的是「結構上的模組分離」，Lazy Loading 則是「載入時機的延後」。兩者密不可分，但用法與思考層次不同。</p>
<p>實務上可先針對頁面級路由進行分割，再進一步優化元件級的載入、工具模組載入時機，逐步降低初始 bundle 體積，提升網站效能與使用者體驗。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-react-lazy-loading-code-splitting-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://medium.com/starbugs/react-%E7%82%BA%E5%A4%AA%E9%BE%90%E5%A4%A7%E7%9A%84%E7%A8%8B%E5%BC%8F%E7%A2%BC%E5%81%9A-lazy-loading-%E5%92%8C-code-splitting-7384626a6e0d" target="_blank" rel="noopener noreferrer" class="">React | 為太龐大的程式碼做 Lazy Loading 和 Code Splitting</a></li>
</ol>]]></content:encoded>
            <category>Performance Optimization</category>
            <category>React</category>
            <category>ES Module</category>
        </item>
        <item>
            <title><![CDATA[React 效能優化入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial</guid>
            <pubDate>Thu, 26 Dec 2024 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>React 作為現代前端開發的主流函式庫之一，強調 UI 的組件化與狀態驅動式渲染。然而，隨著應用規模擴大與資料變得動態頻繁，React 應用可能出現重新渲染過多、載入過慢或記憶體占用過高等問題，影響使用者體驗與開發效率。為此，瞭解與掌握 React 的效能優化技巧，成為中高階開發者的重要功課。</p>
<p>本篇筆記將介紹 React 效能優化的核心原則與常見實作方式，搭配簡單的程式碼範例說明實際操作，協助你建立清晰的優化思維與實作經驗。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>避免不必要的重新渲染</strong></p>
<ul>
<li class="">使用 <code>React.memo</code> 包裹純函式組件</li>
<li class="">適當使用 <code>useMemo</code> 與 <code>useCallback</code> 記憶運算結果或函式引用</li>
</ul>
</li>
<li class="">
<p><strong>Lazy loading（Code Splitting）</strong></p>
<ul>
<li class="">使用 <code>React.lazy</code> 與 <code>Suspense</code> 實現組件按需載入</li>
</ul>
</li>
<li class="">
<p><strong>列表渲染優化</strong></p>
<ul>
<li class="">提供穩定的 <code>key</code>，避免 diff 錯誤導致重繪</li>
<li class="">處理大量資料時可結合虛擬化工具（如 <code>react-window</code>）</li>
</ul>
</li>
<li class="">
<p><strong>狀態管理與邏輯分離</strong></p>
<ul>
<li class="">將全域狀態與 UI 狀態分離，減少重渲染範圍</li>
<li class="">減少 props 傳遞鏈，避免深層組件無謂更新</li>
</ul>
</li>
<li class="">
<p><strong>避免 inline 宣告與函式</strong></p>
<ul>
<li class="">每次 render 都會產生新函式或物件，導致子組件重新渲染</li>
</ul>
</li>
<li class="">
<p><strong>效能分析與工具</strong></p>
<ul>
<li class="">使用 React DevTools 的 Profiler 模組分析 render 開銷</li>
<li class="">善用 Chrome DevTools、Lighthouse 等協助調校效能</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例">實際範例<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際範例的直接連結" title="實際範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-避免不必要的渲染使用-reactmemo">1. 避免不必要的渲染：使用 <code>React.memo</code><a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#1-%E9%81%BF%E5%85%8D%E4%B8%8D%E5%BF%85%E8%A6%81%E7%9A%84%E6%B8%B2%E6%9F%93%E4%BD%BF%E7%94%A8-reactmemo" class="hash-link" aria-label="1-避免不必要的渲染使用-reactmemo的直接連結" title="1-避免不必要的渲染使用-reactmemo的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 子元件</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">TodoItem</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">memo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">TodoItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> todo</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> onToggle </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Render:'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">li</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">input</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">type</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">checkbox</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">checked</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">todo</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">completed</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onChange</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript function" style="color:rgb(80, 250, 123)">onToggle</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">todo</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">id</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">li</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>若未使用 <code>React.memo</code>，即使 <code>todo</code> 資料未變，只要父層重新 render，<code>TodoItem</code> 就會跟著重新 render。使用 <code>React.memo</code> 可避免這種不必要的重新渲染。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-函式記憶使用-usecallback">2. 函式記憶：使用 <code>useCallback</code><a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#2-%E5%87%BD%E5%BC%8F%E8%A8%98%E6%86%B6%E4%BD%BF%E7%94%A8-usecallback" class="hash-link" aria-label="2-函式記憶使用-usecallback的直接連結" title="2-函式記憶使用-usecallback的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> onToggle </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useCallback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">id</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">setTodos</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">prev</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    prev</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">map</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">id</span><span class="token plain"> </span><span class="token operator">===</span><span class="token plain"> id </span><span class="token operator">?</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token spread operator">...</span><span class="token plain">todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">completed</span><span class="token operator">:</span><span class="token plain"> </span><span class="token operator">!</span><span class="token plain">todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">completed</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">:</span><span class="token plain"> todo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>如果 <code>onToggle</code> 每次 render 都重新宣告，會導致 <code>React.memo</code> 判斷 props 改變，從而重新渲染子元件。使用 <code>useCallback</code> 可以保留函式參考的一致性。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-虛擬滾動列表使用-react-window">3. 虛擬滾動列表：使用 <code>react-window</code><a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#3-%E8%99%9B%E6%93%AC%E6%BB%BE%E5%8B%95%E5%88%97%E8%A1%A8%E4%BD%BF%E7%94%A8-react-window" class="hash-link" aria-label="3-虛擬滾動列表使用-react-window的直接連結" title="3-虛擬滾動列表使用-react-window的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">FixedSizeList</span><span class="token imports"> </span><span class="token imports keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token imports"> </span><span class="token imports maybe-class-name">List</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-window'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">Row</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> index</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> style </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">style</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">Row </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">MyList</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">List</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">300</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">itemCount</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">1000</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">itemSize</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">35</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">300</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token maybe-class-name">Row</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">List</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p><code>react-window</code> 提供虛擬滾動的能力，只 render 可視範圍內的項目，大幅減少 DOM 結點，提高大數據列表效能。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-懶載入元件使用-reactlazy">4. 懶載入元件：使用 <code>React.lazy</code><a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#4-%E6%87%B6%E8%BC%89%E5%85%A5%E5%85%83%E4%BB%B6%E4%BD%BF%E7%94%A8-reactlazy" class="hash-link" aria-label="4-懶載入元件使用-reactlazy的直接連結" title="4-懶載入元件使用-reactlazy的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Suspense</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Chart</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./Chart'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">Dashboard</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">fallback</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript plain-text" style="color:rgb(255, 121, 198)">Loading chart...</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Chart</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>將大型組件分割成懶載入模組，可避免初次載入體積過大，提升頁面初始加載速度。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-使用-profiler-分析效能瓶頸">5. 使用 Profiler 分析效能瓶頸<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#5-%E4%BD%BF%E7%94%A8-profiler-%E5%88%86%E6%9E%90%E6%95%88%E8%83%BD%E7%93%B6%E9%A0%B8" class="hash-link" aria-label="5. 使用 Profiler 分析效能瓶頸的直接連結" title="5. 使用 Profiler 分析效能瓶頸的直接連結" translate="no">​</a></h3>
<p>React DevTools 提供 Profiler 模組，可追蹤各元件 render 時間與次數，有助於識別過度渲染或效能低落的元件。</p>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Profiler</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Profiler</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">  </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">TodoList</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)">  </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onRender</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript parameter" style="color:rgb(255, 121, 198)">id</span><span class="token tag script language-javascript parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token tag script language-javascript parameter" style="color:rgb(255, 121, 198)"> phase</span><span class="token tag script language-javascript parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token tag script language-javascript parameter" style="color:rgb(255, 121, 198)"> actualDuration</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript arrow operator" style="color:rgb(255, 121, 198)">=&gt;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"> </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">    </span><span class="token tag script language-javascript console class-name" style="color:rgb(255, 121, 198)">console</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token tag script language-javascript template-string interpolation" style="color:rgb(255, 121, 198)">id</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript template-string string" style="color:rgb(255, 121, 198)"> rendered in </span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token tag script language-javascript template-string interpolation" style="color:rgb(255, 121, 198)">actualDuration</span><span class="token tag script language-javascript template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript template-string string" style="color:rgb(255, 121, 198)">ms</span><span class="token tag script language-javascript template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">  </span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token tag" style="color:rgb(255, 121, 198)"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">TodoList</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">todos</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">todos</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Profiler</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="結語">結語<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-intro-tutorial#%E7%B5%90%E8%AA%9E" class="hash-link" aria-label="結語的直接連結" title="結語的直接連結" translate="no">​</a></h2>
<p>React 效能優化並非一蹴可幾，而是需隨著應用規模與需求不斷調整與改善的過程。透過理解 Virtual DOM 的運作原理、掌握各種 Hook 的特性，以及活用分析工具，我們可以更有策略地針對效能瓶頸逐步優化，打造流暢且可維護的使用者體驗。</p>
<p>建議從小型優化（如 <code>React.memo</code>、<code>useCallback</code>）著手，並逐步引入懶載入與虛擬化等進階技巧，讓 React 應用能夠隨著功能擴展持續保持高效能。</p>
<p>如果你對特定效能問題有興趣，例如圖片載入優化、CSR vs SSR 效能比較等，也可以再深入探討不同的進階主題。</p>]]></content:encoded>
            <category>Performance Optimization</category>
            <category>React</category>
            <category>ES Module</category>
        </item>
        <item>
            <title><![CDATA[React 效能優化 SOP 檢核清單入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial</guid>
            <pubDate>Wed, 25 Dec 2024 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在大型單頁應用（SPA）與複雜互動式介面中，效能瓶頸常常來自不必要的重新渲染、大型 bundle 導致的載入緩慢，以及過度操作 DOM 所造成的卡頓。建立一份標準化的「效能優化檢核清單」（SOP，Standard Operating Procedure），能夠在開發流程中明確指出應檢查的重點、落實最佳實踐，並透過持續監控與回饋，進一步強化團隊的效能意識與程式品質。</p>
<p>本篇筆記將依照從「程式撰寫到部署」的不同階段，提出具體的檢核項目，並搭配最常見的 React 效能優化技術範例，協助你快速掌握如何在日常開發與 Code Review 中落實效能優化。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>一、避免不必要的重新渲染</strong></p>
<ul>
<li class="">使用 <code>React.memo</code> 包裹純函式元件</li>
<li class="">針對函式與物件 props，使用 <code>useCallback</code>、<code>useMemo</code> 進行記憶</li>
<li class="">避免 JSX inline 宣告函式或物件</li>
</ul>
</li>
<li class="">
<p><strong>二、State 管理與元件分離</strong></p>
<ul>
<li class="">下放 state 至影響範圍最小的元件</li>
<li class="">UI 狀態（開關、Modal 等）與業務資料分離</li>
<li class="">避免全域 context 過度包覆，導致大範圍 re-render</li>
</ul>
</li>
<li class="">
<p><strong>三、列表與大量資料渲染優化</strong></p>
<ul>
<li class="">確保 <code>key</code> 穩定（使用唯一 id，非 index）</li>
<li class="">採用虛擬滾動（<code>react-window</code>、<code>react-virtualized</code>）</li>
<li class="">分頁或懶加載機制</li>
</ul>
</li>
<li class="">
<p><strong>四、Code Splitting 與懶載入</strong></p>
<ul>
<li class="">使用 <code>React.lazy</code> + <code>Suspense</code> 分割大型元件</li>
<li class="">路由層級拆分，動態 <code>import()</code></li>
<li class="">圖片與第三方資源延遲加載</li>
</ul>
</li>
<li class="">
<p><strong>五、效能分析與監控工具</strong></p>
<ul>
<li class="">React DevTools Profiler：分析元件 render 次數與耗時</li>
<li class="">Lighthouse / Web Vitals：追蹤 FCP、LCP、TTFB 等指標</li>
<li class="">Bundle 分析（Webpack Bundle Analyzer、Source Map Explorer）</li>
</ul>
</li>
<li class="">
<p><strong>六、CI／Code Review 效能檢查</strong></p>
<ul>
<li class="">將檢核清單納入 Pull Request 模板</li>
<li class="">自動化檢測 bundle size 變化</li>
<li class="">定期性能測試腳本（Cypress、Playwright + Lighthouse）</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例">實際範例<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際範例的直接連結" title="實際範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例一避免不必要的重新渲染">範例一：避免不必要的重新渲染<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%80%E9%81%BF%E5%85%8D%E4%B8%8D%E5%BF%85%E8%A6%81%E7%9A%84%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="範例一：避免不必要的重新渲染的直接連結" title="範例一：避免不必要的重新渲染的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ChildComponent.jsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">ChildComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> data</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> onClick </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'ChildComponent render'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">onClick</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">memo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token maybe-class-name">ChildComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ParentComponent.jsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> useCallback</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> useMemo </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">ChildComponent</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./ChildComponent'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">ParentComponent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> initialData </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setCount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// useMemo 記憶 data 物件，避免因父組件重新 render 而改變 reference</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> data </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useMemo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">text</span><span class="token operator">:</span><span class="token plain"> initialData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">initialData</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)">// useCallback 記憶函式，不會因為 count 變化而重新建立</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> handleClick </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useCallback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">setCount</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">c</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> c </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">點擊次數：</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">count</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">ChildComponent</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">data</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">data</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onClick</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">handleClick</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<blockquote>
<p><strong>檢核點</strong>：</p>
<ul>
<li class="">ChildComponent 是否用 <code>React.memo</code> 包裹？</li>
<li class="">data 物件是否用 <code>useMemo</code>？</li>
<li class="">onClick 是否用 <code>useCallback</code>？</li>
</ul>
</blockquote>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例二列表虛擬化">範例二：列表虛擬化<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%BA%8C%E5%88%97%E8%A1%A8%E8%99%9B%E6%93%AC%E5%8C%96" class="hash-link" aria-label="範例二：列表虛擬化的直接連結" title="範例二：列表虛擬化的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// ListView.jsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">FixedSizeList</span><span class="token imports"> </span><span class="token imports keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token imports"> </span><span class="token imports maybe-class-name">List</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-window'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Row</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">memo</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> index</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> style </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">style</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">style</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">列表項目 #</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">index</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">ListView</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">List</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">height</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">400</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">itemCount</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">10000</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">itemSize</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">35</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript string" style="color:rgb(255, 121, 198)">'100%'</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token maybe-class-name">Row</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">List</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<blockquote>
<p><strong>檢核點</strong>：</p>
<ul>
<li class="">是否針對長列表導入虛擬化？</li>
<li class="">itemSize 與 height 設定是否合理？</li>
</ul>
</blockquote>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例三code-splitting-與懶載入">範例三：Code Splitting 與懶載入<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%89code-splitting-%E8%88%87%E6%87%B6%E8%BC%89%E5%85%A5" class="hash-link" aria-label="範例三：Code Splitting 與懶載入的直接連結" title="範例三：Code Splitting 與懶載入的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// routes.jsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Suspense</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">BrowserRouter</span><span class="token imports"> </span><span class="token imports keyword module" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token imports"> </span><span class="token imports maybe-class-name">Router</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Route</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports maybe-class-name">Switch</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react-router-dom'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Home</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./pages/Home'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">Dashboard</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">lazy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'./pages/Dashboard'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">AppRouter</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Router</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">fallback</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript plain-text" style="color:rgb(255, 121, 198)">載入中...</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag script language-javascript tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag script language-javascript tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Switch</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Route</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">exact</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">path</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">/</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript maybe-class-name" style="color:rgb(255, 121, 198)">Home</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">          </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Route</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">path</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">/dashboard</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">component</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript maybe-class-name" style="color:rgb(255, 121, 198)">Dashboard</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">        </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Switch</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Suspense</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Router</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<blockquote>
<p><strong>檢核點</strong>：</p>
<ul>
<li class="">是否有針對路由或大型元件進行懶載入？</li>
<li class="">fallback UI 是否友善？</li>
</ul>
</blockquote>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例四效能分析">範例四：效能分析<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E7%AF%84%E4%BE%8B%E5%9B%9B%E6%95%88%E8%83%BD%E5%88%86%E6%9E%90" class="hash-link" aria-label="範例四：效能分析的直接連結" title="範例四：效能分析的直接連結" translate="no">​</a></h3>
<div class="language-jsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">Profiler</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">TodoList</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./TodoList'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">onRenderCallback</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">id</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> phase</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> actualDuration</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">id</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)"> </span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">phase</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)"> 耗時：</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">actualDuration</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token template-string interpolation method function property-access" style="color:rgb(80, 250, 123)">toFixed</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token template-string interpolation number">2</span><span class="token template-string interpolation punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string string" style="color:rgb(255, 121, 198)">ms</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Profiler</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">TodoList</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">onRender</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">onRenderCallback</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">TodoList</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag class-name" style="color:rgb(255, 121, 198)">Profiler</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<blockquote>
<p><strong>檢核點</strong>：</p>
<ul>
<li class="">是否使用 Profiler 區隔並記錄核心元件耗時？</li>
<li class="">是否定期檢視開發者工具數據？</li>
</ul>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>以上檢核清單涵蓋了從程式撰寫、元件切分，到效能分析與持續監控的各個面向。建議將此清單整合至 Pull Request 模板中，並在團隊中推廣效能優化文化。持續在日常開發中落實這些檢查，能確保應用在功能增長的同時仍保持流暢的使用者體驗，並降低潛在的性能退化風險。若需將本文轉為 Markdown、PDF 或 Notion 模板，歡迎隨時提出。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="補充react-效能優化-sop-檢核清單">補充：React 效能優化 SOP 檢核清單<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E8%A3%9C%E5%85%85react-%E6%95%88%E8%83%BD%E5%84%AA%E5%8C%96-sop-%E6%AA%A2%E6%A0%B8%E6%B8%85%E5%96%AE" class="hash-link" aria-label="補充：React 效能優化 SOP 檢核清單的直接連結" title="補充：React 效能優化 SOP 檢核清單的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一避免不必要的重新渲染">一、避免不必要的重新渲染<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E4%B8%80%E9%81%BF%E5%85%8D%E4%B8%8D%E5%BF%85%E8%A6%81%E7%9A%84%E9%87%8D%E6%96%B0%E6%B8%B2%E6%9F%93" class="hash-link" aria-label="一、避免不必要的重新渲染的直接連結" title="一、避免不必要的重新渲染的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否使用 <code>React.memo</code> 包裹純函式元件？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否有使用 <code>useCallback</code> 記憶傳遞的函式 props？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否有使用 <code>useMemo</code> 記憶計算結果，避免重複計算？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否避免在 JSX 中直接宣告函式或物件（例如 inline style）？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="二state-管理與元件分離">二、State 管理與元件分離<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E4%BA%8Cstate-%E7%AE%A1%E7%90%86%E8%88%87%E5%85%83%E4%BB%B6%E5%88%86%E9%9B%A2" class="hash-link" aria-label="二、State 管理與元件分離的直接連結" title="二、State 管理與元件分離的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否將狀態下放至最小影響範圍的元件中？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否避免使用不必要的 lifting state up？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否將 UI 狀態（如開關、hover 狀態）與全域狀態分離？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="三props-傳遞與結構優化">三、Props 傳遞與結構優化<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E4%B8%89props-%E5%82%B3%E9%81%9E%E8%88%87%E7%B5%90%E6%A7%8B%E5%84%AA%E5%8C%96" class="hash-link" aria-label="三、Props 傳遞與結構優化的直接連結" title="三、Props 傳遞與結構優化的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否控制 props 深層傳遞導致的層層 re-render？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否 props 結構穩定、可預期？（避免 object/array 每次都變）</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="四渲染大量資料時的處理">四、渲染大量資料時的處理<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E5%9B%9B%E6%B8%B2%E6%9F%93%E5%A4%A7%E9%87%8F%E8%B3%87%E6%96%99%E6%99%82%E7%9A%84%E8%99%95%E7%90%86" class="hash-link" aria-label="四、渲染大量資料時的處理的直接連結" title="四、渲染大量資料時的處理的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否針對長列表使用虛擬化工具（如 <code>react-window</code>, <code>react-virtualized</code>）？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否有合理使用 <code>key</code>（且為穩定值，例如 id 而非 index）？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="五資源載入與-code-splitting">五、資源載入與 Code Splitting<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E4%BA%94%E8%B3%87%E6%BA%90%E8%BC%89%E5%85%A5%E8%88%87-code-splitting" class="hash-link" aria-label="五、資源載入與 Code Splitting的直接連結" title="五、資源載入與 Code Splitting的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否使用 <code>React.lazy</code> + <code>Suspense</code> 實現元件懶載入？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否進行 route-based code splitting（使用動態 import）？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否有壓縮圖片、延遲圖片載入（lazy loading）？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="六效能監控與分析">六、效能監控與分析<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E5%85%AD%E6%95%88%E8%83%BD%E7%9B%A3%E6%8E%A7%E8%88%87%E5%88%86%E6%9E%90" class="hash-link" aria-label="六、效能監控與分析的直接連結" title="六、效能監控與分析的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否使用 React DevTools Profiler 檢查 render 頻率與時間？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否分析過 Lighthouse / Web Vitals 的效能指標？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否檢查 Bundle Size（Webpack 分析工具、SourceMap Explorer 等）？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="七避免常見陷阱">七、避免常見陷阱<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E4%B8%83%E9%81%BF%E5%85%8D%E5%B8%B8%E8%A6%8B%E9%99%B7%E9%98%B1" class="hash-link" aria-label="七、避免常見陷阱的直接連結" title="七、避免常見陷阱的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否避免每次 render 都新建匿名函式？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否避免重複 render 同一資料來源？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否避免過度依賴 context 導致全頁重 render？</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="八開發階段優化習慣">八、開發階段優化習慣<a href="https://tech.kdchang.com/blog/learning-notes-react-performance-optimization-sop-intro-tutorial#%E5%85%AB%E9%96%8B%E7%99%BC%E9%9A%8E%E6%AE%B5%E5%84%AA%E5%8C%96%E7%BF%92%E6%85%A3" class="hash-link" aria-label="八、開發階段優化習慣的直接連結" title="八、開發階段優化習慣的直接連結" translate="no">​</a></h3>
<ul class="contains-task-list containsTaskList_mC6p">
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否將效能優化納入 Code Review 檢查點？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否每個大型新元件都確認是否會引起不必要渲染？</li>
<li class="task-list-item"><input type="checkbox" disabled=""> <!-- -->是否測試過主流程在弱網或低效能設備上的表現？</li>
</ul>]]></content:encoded>
            <category>Performance Optimization</category>
            <category>React</category>
            <category>ES Module</category>
        </item>
        <item>
            <title><![CDATA[LangGraph 入門教學筆記：打造多步驟 AI 流程的圖形化解決方案 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial</guid>
            <pubDate>Tue, 24 Dec 2024 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>隨著生成式 AI 的應用越來越廣泛，從客服機器人、智慧問答系統到複雜的自動化工作流程，開發者面臨的不再只是單次的文字生成，而是需要處理<strong>多步驟的對話邏輯與決策流程</strong>。</p>
<p>傳統上，這類應用通常透過繁瑣的 if-else 邏輯、狀態機或多層函式巢狀處理，程式碼不易閱讀與維護。為此，<strong>LangGraph</strong> 應運而生。它是一個開源的 Python 函式庫，讓開發者可以用「流程圖」的方式清晰地定義每一步的處理邏輯，進而打造更穩定且模組化的 AI Workflow。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>LangGraph 是什麼？</strong></p>
<ul>
<li class="">由 LangChain 團隊開發的 AI Workflow 工具，透過流程圖（Graph）定義多步驟的推理過程。</li>
<li class="">每一個節點（Node）代表一個處理步驟，從 LLM 回覆、條件判斷到工具呼叫等皆可定義成節點。</li>
</ul>
</li>
<li class="">
<p><strong>核心特色</strong></p>
<ul>
<li class="">使用有向圖（DAG）表示流程，每個節點都有明確的輸入與輸出狀態。</li>
<li class="">支援條件分支、迴圈、自訂狀態、記憶上下文。</li>
<li class="">可與 LangChain、OpenAI、Anthropic 等服務整合。</li>
<li class="">適合用於構建 Agent、Chatbot、多階段處理流程。</li>
</ul>
</li>
<li class="">
<p><strong>應用情境</strong></p>
<ul>
<li class="">客製化對話代理人（如智能客服）</li>
<li class="">多階段資訊處理（如：檢索、分類、摘要）</li>
<li class="">工具選擇與執行流程（如：根據輸入選擇工具）</li>
<li class="">擴展型 LLM 應用（如：RAG、Tool Use）</li>
</ul>
</li>
<li class="">
<p><strong>重要元件說明</strong></p>
<ul>
<li class=""><code>StateGraph</code>：定義整體流程圖。</li>
<li class=""><code>Node</code>：每個節點代表一個具邏輯意義的步驟。</li>
<li class=""><code>State</code>：儲存目前上下文狀態，可自訂欄位。</li>
<li class=""><code>Conditional Edge</code>：根據邏輯結果決定下一個節點。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例打造一個-faq-對話機器人">實際範例：打造一個 FAQ 對話機器人<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E6%89%93%E9%80%A0%E4%B8%80%E5%80%8B-faq-%E5%B0%8D%E8%A9%B1%E6%A9%9F%E5%99%A8%E4%BA%BA" class="hash-link" aria-label="實際範例：打造一個 FAQ 對話機器人的直接連結" title="實際範例：打造一個 FAQ 對話機器人的直接連結" translate="no">​</a></h2>
<p>本範例將建立一個簡單的對話流程：</p>
<ol>
<li class="">使用者輸入問題。</li>
<li class="">呼叫 OpenAI GPT 模型回覆。</li>
<li class="">若輸入為 "bye"，流程結束；否則持續對話。</li>
</ol>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-安裝套件">1. 安裝套件<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#1-%E5%AE%89%E8%A3%9D%E5%A5%97%E4%BB%B6" class="hash-link" aria-label="1. 安裝套件的直接連結" title="1. 安裝套件的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pip install langgraph langchain openai</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-定義狀態與回應節點">2. 定義狀態與回應節點<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#2-%E5%AE%9A%E7%BE%A9%E7%8B%80%E6%85%8B%E8%88%87%E5%9B%9E%E6%87%89%E7%AF%80%E9%BB%9E" class="hash-link" aria-label="2. 定義狀態與回應節點的直接連結" title="2. 定義狀態與回應節點的直接連結" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> langgraph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">graph </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> StateGraph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> END</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> langchain</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">chat_models </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> ChatOpenAI</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> typing </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> TypedDict</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 自訂狀態格式</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">class</span><span class="token plain"> </span><span class="token class-name">ConversationState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">TypedDict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    messages</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    last_user_input</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">str</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 初始化 LLM</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">llm </span><span class="token operator">=</span><span class="token plain"> ChatOpenAI</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">model</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"gpt-4"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> temperature</span><span class="token operator">=</span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 處理回應的節點</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">generate_response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ConversationState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    user_input </span><span class="token operator">=</span><span class="token plain"> state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"last_user_input"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"messages"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"User: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">user_input</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    response </span><span class="token operator">=</span><span class="token plain"> llm</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">predict</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"請回答以下問題：</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">user_input</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"messages"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"AI: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">response</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> state</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-定義流程結束條件與流程圖">3. 定義流程結束條件與流程圖<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#3-%E5%AE%9A%E7%BE%A9%E6%B5%81%E7%A8%8B%E7%B5%90%E6%9D%9F%E6%A2%9D%E4%BB%B6%E8%88%87%E6%B5%81%E7%A8%8B%E5%9C%96" class="hash-link" aria-label="3. 定義流程結束條件與流程圖的直接連結" title="3. 定義流程結束條件與流程圖的直接連結" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 判斷是否要結束對話</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">should_continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ConversationState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"last_user_input"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">lower</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">strip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"bye"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> END</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"generate"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 建立流程圖</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">builder </span><span class="token operator">=</span><span class="token plain"> StateGraph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">ConversationState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">add_node</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"generate"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> generate_response</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">set_entry_point</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"generate"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">add_conditional_edges</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"generate"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> should_continue</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">graph </span><span class="token operator">=</span><span class="token plain"> builder</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token builtin" style="color:rgb(189, 147, 249)">compile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-執行對話流程">4. 執行對話流程<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#4-%E5%9F%B7%E8%A1%8C%E5%B0%8D%E8%A9%B1%E6%B5%81%E7%A8%8B" class="hash-link" aria-label="4. 執行對話流程的直接連結" title="4. 執行對話流程的直接連結" translate="no">​</a></h3>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 初始狀態</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"messages"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"last_user_input"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"你好，這是什麼系統？"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 執行第一輪</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state </span><span class="token operator">=</span><span class="token plain"> graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">invoke</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 模擬第二輪</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"last_user_input"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"LangGraph 是什麼？"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state </span><span class="token operator">=</span><span class="token plain"> graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">invoke</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 模擬結束對話</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"last_user_input"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"bye"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">state </span><span class="token operator">=</span><span class="token plain"> graph</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">invoke</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 印出對話記錄</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> msg </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> state</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"messages"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">msg</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例輸出結果">範例輸出結果<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E7%AF%84%E4%BE%8B%E8%BC%B8%E5%87%BA%E7%B5%90%E6%9E%9C" class="hash-link" aria-label="範例輸出結果的直接連結" title="範例輸出結果的直接連結" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">User: 你好，這是什麼系統？</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">AI: 這是一個由 LangGraph 架構的對話系統。</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">User: LangGraph 是什麼？</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">AI: LangGraph 是一個讓開發者用流程圖方式設計 AI 應用的工具。</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">User: bye</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">AI: 感謝使用，祝您有美好的一天。</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結與延伸">總結與延伸<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E7%B8%BD%E7%B5%90%E8%88%87%E5%BB%B6%E4%BC%B8" class="hash-link" aria-label="總結與延伸的直接連結" title="總結與延伸的直接連結" translate="no">​</a></h2>
<p>LangGraph 為 LLM 應用程式帶來一個明確的結構化框架，讓我們能夠模組化管理多步驟流程、狀態記憶與條件判斷。相較於傳統方式，它更適合用來構建複雜、可維護的對話式 AI 應用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="延伸應用可以包括">延伸應用可以包括：<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E5%BB%B6%E4%BC%B8%E6%87%89%E7%94%A8%E5%8F%AF%E4%BB%A5%E5%8C%85%E6%8B%AC" class="hash-link" aria-label="延伸應用可以包括：的直接連結" title="延伸應用可以包括：的直接連結" translate="no">​</a></h3>
<ul>
<li class="">整合 LangChain 工具（如：向量資料庫、搜尋引擎）</li>
<li class="">建構具有分支與回饋機制的智能 Agent</li>
<li class="">開發能根據上下文自我修正的 RAG 系統</li>
<li class="">將整個 LangGraph 部署為 Web API 或背景工作流程</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-lang-graph-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://ywctech.net/ml-ai/langchain-langgraph-agent-part1/" target="_blank" rel="noopener noreferrer" class="">LangGraph: LangChain Agent 的殺手鐧 (入門)</a></li>
</ol>]]></content:encoded>
            <category>html</category>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>LangGraph</category>
        </item>
        <item>
            <title><![CDATA[useSWR 入門教學筆記：打造高效、簡潔的資料請求方式 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial</guid>
            <pubDate>Tue, 24 Dec 2024 02:23:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在現代前端開發中，資料的取得與管理是不可或缺的一環。傳統上，我們可能使用 <code>useEffect</code> 搭配 <code>fetch</code> 或 <code>axios</code> 來處理資料請求，但這樣的方式不僅冗長，還需要手動管理 loading、error 狀態與快取邏輯。為了解決這些問題，Vercel 推出的 SWR（stale-while-revalidate）提供了一種簡潔、聲明式且高效的資料取得方式，特別適合搭配 React 應用開發。</p>
<p>本文將介紹 SWR 的核心觀念、使用方式與基本範例，幫助我們快速上手並應用於實務開發中。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>SWR 是什麼？</strong></p>
<ul>
<li class="">SWR 是由 Vercel 開發的 React Hooks 函式庫，提供資料快取與同步機制。</li>
<li class="">名稱來自 HTTP 快取策略 “stale-while-revalidate”，意指：<strong>先顯示舊資料，再重新驗證更新資料</strong>。</li>
</ul>
</li>
<li class="">
<p><strong>為什麼要使用 SWR？</strong></p>
<ul>
<li class="">自動處理資料快取與重新驗證。</li>
<li class="">簡化資料請求邏輯，減少樣板程式碼。</li>
<li class="">支援多種進階功能（錯誤重試、revalidate on focus、polling 等）。</li>
</ul>
</li>
<li class="">
<p><strong>基本用法</strong></p>
<ul>
<li class="">使用 <code>useSWR(key, fetcher)</code> 進行資料請求。</li>
<li class=""><code>key</code>：唯一識別資料來源的 key，通常為 API 路徑。</li>
<li class=""><code>fetcher</code>：資料請求函式，可使用 <code>fetch</code> 或 <code>axios</code> 實作。</li>
</ul>
</li>
<li class="">
<p><strong>常見功能</strong></p>
<ul>
<li class=""><code>isLoading</code>、<code>error</code> 狀態管理。</li>
<li class="">自動重試與重新整理資料。</li>
<li class="">快取與全域共用資料（Shared cache）。</li>
<li class="">手動重新驗證資料（revalidate）。</li>
<li class="">支援 SSR、Pagination、Mutation 等進階功能。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例取得-github-使用者資料">實際範例：取得 GitHub 使用者資料<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E5%8F%96%E5%BE%97-github-%E4%BD%BF%E7%94%A8%E8%80%85%E8%B3%87%E6%96%99" class="hash-link" aria-label="實際範例：取得 GitHub 使用者資料的直接連結" title="實際範例：取得 GitHub 使用者資料的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-安裝-swr">1. 安裝 SWR<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#1-%E5%AE%89%E8%A3%9D-swr" class="hash-link" aria-label="1. 安裝 SWR的直接連結" title="1. 安裝 SWR的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">npm install swr</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># 或使用 yarn</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">yarn add swr</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-撰寫-fetcher-函式">2. 撰寫 fetcher 函式<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#2-%E6%92%B0%E5%AF%AB-fetcher-%E5%87%BD%E5%BC%8F" class="hash-link" aria-label="2. 撰寫 fetcher 函式的直接連結" title="2. 撰寫 fetcher 函式的直接連結" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// libs/fetcher.ts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">fetcher</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">url</span><span class="token operator">:</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">string</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">fetch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">url</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">then</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-在元件中使用-useswr">3. 在元件中使用 useSWR<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#3-%E5%9C%A8%E5%85%83%E4%BB%B6%E4%B8%AD%E4%BD%BF%E7%94%A8-useswr" class="hash-link" aria-label="3. 在元件中使用 useSWR的直接連結" title="3. 在元件中使用 useSWR的直接連結" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// pages/UserProfile.tsx</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports">useSWR</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'swr'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> fetcher </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'../libs/fetcher'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">UserProfile</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> isLoading </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useSWR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'https://api.github.com/users/octocat'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">isLoading</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">載入中...</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">載入失敗：</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">error</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">message</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">h1</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">GitHub：</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">login</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text">Followers：</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">followers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">p</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">      </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">img</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">src</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript" style="color:rgb(255, 121, 198)">data</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token tag script language-javascript property-access" style="color:rgb(255, 121, 198)">avatar_url</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">width</span><span class="token tag script language-javascript script-punctuation punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token tag script language-javascript number" style="color:rgb(255, 121, 198)">100</span><span class="token tag script language-javascript punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">/&gt;</span><span class="token plain-text"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain-text">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">div</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token maybe-class-name">UserProfile</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-手動重新驗證資料">4. 手動重新驗證資料<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#4-%E6%89%8B%E5%8B%95%E9%87%8D%E6%96%B0%E9%A9%97%E8%AD%89%E8%B3%87%E6%96%99" class="hash-link" aria-label="4. 手動重新驗證資料的直接連結" title="4. 手動重新驗證資料的直接連結" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> mutate </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useSWR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/api/data'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 手動刷新資料</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">handleRefresh</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">mutate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-搭配條件式載入">5. 搭配條件式載入<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#5-%E6%90%AD%E9%85%8D%E6%A2%9D%E4%BB%B6%E5%BC%8F%E8%BC%89%E5%85%A5" class="hash-link" aria-label="5. 搭配條件式載入的直接連結" title="5. 搭配條件式載入的直接連結" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> shouldFetch </span><span class="token operator">=</span><span class="token plain"> userId </span><span class="token operator">!==</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useSWR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">shouldFetch </span><span class="token operator">?</span><span class="token plain"> </span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token template-string string" style="color:rgb(255, 121, 198)">/api/users/</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">${</span><span class="token template-string interpolation">userId</span><span class="token template-string interpolation interpolation-punctuation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token template-string template-punctuation string" style="color:rgb(255, 121, 198)">`</span><span class="token plain"> </span><span class="token operator">:</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-自訂快取與設定">6. 自訂快取與設定<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#6-%E8%87%AA%E8%A8%82%E5%BF%AB%E5%8F%96%E8%88%87%E8%A8%AD%E5%AE%9A" class="hash-link" aria-label="6. 自訂快取與設定的直接連結" title="6. 自訂快取與設定的直接連結" translate="no">​</a></h3>
<div class="language-tsx codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-tsx codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports">useSWR</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'swr'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> error </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useSWR</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'/api/data'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> fetcher</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  refreshInterval</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">10000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 每 10 秒重新抓資料</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  revalidateOnFocus</span><span class="token operator">:</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 回到畫面時自動刷新</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  dedupingInterval</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">5000</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 阻止過於頻繁的 API 請求</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>SWR 提供了一種優雅、聲明式的方式來管理 React 應用中的資料請求與快取，不僅能有效簡化程式碼，還能提高使用者體驗與應用效能。其彈性與擴充性也適合應用於中大型專案中。</p>
<p>當我們熟悉了 SWR 的基本用法後，接下來也可以進一步探索以下功能：</p>
<ul>
<li class=""><strong>Mutation API</strong>：用於資料寫入後手動更新快取。</li>
<li class=""><strong>依賴 key 的動態載入</strong>：搭配 router 參數動態請求資料。</li>
<li class=""><strong>全域快取策略自訂（SWRConfig）</strong>：統一設定所有請求的行為。</li>
</ul>
<p>透過 SWR，我們不再需要手動處理快取與副作用邏輯，只需專注於資料的呈現與邏輯本身，是開發現代 React 應用的絕佳利器。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-useswr-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://swr.vercel.app/" target="_blank" rel="noopener noreferrer" class="">React Hooks for Data Fetching</a></li>
</ol>]]></content:encoded>
            <category>html</category>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>react</category>
            <category>fetch</category>
            <category>useSWR</category>
        </item>
        <item>
            <title><![CDATA[JavaScript 可選鏈接運算符（Optional Chaining）介紹與入門教學 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial copy</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial copy</guid>
            <pubDate>Sat, 21 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在 <code>JavaScript</code> 中，處理深層嵌套結構時，我們經常會遇到 <code>null</code> 或 <code>undefined</code> 的問題。例如，當我們需要訪問一個對象的屬性，而該屬性本身可能不存在時，傳統的做法會導致錯誤，這樣的情況會非常繁瑣。為了解決這個問題，JavaScript 引入了 <strong>可選鏈接運算符（Optional Chaining）</strong>，簡化了屬性訪問過程，並防止了因為屬性為 <code>null</code> 或 <code>undefined</code> 造成的錯誤。</p>
<p>本文將詳細介紹可選鏈接運算符的概念、用法以及常見的實際應用場景。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-可選鏈接運算符的基本語法">1. 可選鏈接運算符的基本語法<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#1-%E5%8F%AF%E9%81%B8%E9%8F%88%E6%8E%A5%E9%81%8B%E7%AE%97%E7%AC%A6%E7%9A%84%E5%9F%BA%E6%9C%AC%E8%AA%9E%E6%B3%95" class="hash-link" aria-label="1. 可選鏈接運算符的基本語法的直接連結" title="1. 可選鏈接運算符的基本語法的直接連結" translate="no">​</a></h3>
<p><strong>可選鏈接運算符（<code>?.</code>）</strong> 是 JavaScript 中一種新的語法，通過它我們可以安全地訪問對象的屬性，並且在中途如果遇到 <code>null</code> 或 <code>undefined</code>，就會停止執行並返回 <code>undefined</code>，而不是拋出錯誤。</p>
<p>基本語法結構如下：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">object</span><span class="token operator">?.</span><span class="token plain">property</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">object</span><span class="token operator">?.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">object</span><span class="token operator">?.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">method</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<ul>
<li class=""><code>object?.property</code>：如果 <code>object</code> 為 <code>null</code> 或 <code>undefined</code>，則返回 <code>undefined</code>，否則返回對象的 <code>property</code> 屬性。</li>
<li class=""><code>object?.[key]</code>：這是動態屬性名的情況，與 <code>object?.property</code> 類似，當 <code>key</code> 是變數或表達式時，這種語法很有用。</li>
<li class=""><code>object?.method()</code>：如果 <code>object</code> 或 <code>method</code> 為 <code>null</code> 或 <code>undefined</code>，則返回 <code>undefined</code>，不會調用該方法。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-為什麼需要可選鏈接運算符">2. 為什麼需要可選鏈接運算符？<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#2-%E7%82%BA%E4%BB%80%E9%BA%BC%E9%9C%80%E8%A6%81%E5%8F%AF%E9%81%B8%E9%8F%88%E6%8E%A5%E9%81%8B%E7%AE%97%E7%AC%A6" class="hash-link" aria-label="2. 為什麼需要可選鏈接運算符？的直接連結" title="2. 為什麼需要可選鏈接運算符？的直接連結" translate="no">​</a></h3>
<p>在傳統 JavaScript 中，當我們處理嵌套對象的屬性時，若某個屬性不存在或是 <code>null</code>、<code>undefined</code>，我們會遇到錯誤。例如：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Alice'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">address</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">street</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'123 Main St'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">street</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "123 Main St"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">phone</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// TypeError: Cannot read property 'number' of undefined</span><br></span></code></pre></div></div>
<p>在這個例子中，當我們嘗試訪問 <code>user.phone.number</code> 時，由於 <code>phone</code> 屬性不存在，會拋出錯誤。為了解決這個問題，通常我們需要進行多層檢查：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user </span><span class="token operator">&amp;&amp;</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">phone</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">phone</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// undefined</span><br></span></code></pre></div></div>
<p>這樣的寫法看起來雜亂，並且很難處理更深層次的嵌套。可選鏈接運算符解決了這個問題，使得代碼更加簡潔和安全。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-可選鏈接運算符的應用場景">3. 可選鏈接運算符的應用場景<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#3-%E5%8F%AF%E9%81%B8%E9%8F%88%E6%8E%A5%E9%81%8B%E7%AE%97%E7%AC%A6%E7%9A%84%E6%87%89%E7%94%A8%E5%A0%B4%E6%99%AF" class="hash-link" aria-label="3. 可選鏈接運算符的應用場景的直接連結" title="3. 可選鏈接運算符的應用場景的直接連結" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="31-訪問對象屬性">3.1 訪問對象屬性<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#31-%E8%A8%AA%E5%95%8F%E5%B0%8D%E8%B1%A1%E5%B1%AC%E6%80%A7" class="hash-link" aria-label="3.1 訪問對象屬性的直接連結" title="3.1 訪問對象屬性的直接連結" translate="no">​</a></h4>
<p>當我們需要訪問對象的某一層屬性時，如果中間層級的某個屬性為 <code>null</code> 或 <code>undefined</code>，那麼使用可選鏈接運算符就能防止錯誤的拋出。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Alice'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">address</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">street</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'123 Main St'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token operator">?.</span><span class="token plain">address</span><span class="token operator">?.</span><span class="token plain">street</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "123 Main St"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token operator">?.</span><span class="token plain">phone</span><span class="token operator">?.</span><span class="token plain">number</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// undefined</span><br></span></code></pre></div></div>
<p>在這個例子中，<code>user?.address?.street</code> 會安全地返回 <code>street</code> 屬性，而 <code>user?.phone?.number</code> 會返回 <code>undefined</code>，因為 <code>phone</code> 屬性並不存在。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="32-訪問數組元素">3.2 訪問數組元素<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#32-%E8%A8%AA%E5%95%8F%E6%95%B8%E7%B5%84%E5%85%83%E7%B4%A0" class="hash-link" aria-label="3.2 訪問數組元素的直接連結" title="3.2 訪問數組元素的直接連結" translate="no">​</a></h4>
<p>在操作數組時，如果我們想訪問某個索引的元素，也可以使用可選鏈接運算符來避免錯誤。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> array </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">array</span><span class="token operator">?.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 2</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">array</span><span class="token operator">?.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// undefined</span><br></span></code></pre></div></div>
<p>這裡，<code>array?.[1]</code> 會返回 <code>2</code>，而 <code>array?.[10]</code> 會返回 <code>undefined</code>，即使索引超出了數組的範圍。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="33-調用對象方法">3.3 調用對象方法<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#33-%E8%AA%BF%E7%94%A8%E5%B0%8D%E8%B1%A1%E6%96%B9%E6%B3%95" class="hash-link" aria-label="3.3 調用對象方法的直接連結" title="3.3 調用對象方法的直接連結" translate="no">​</a></h4>
<p>如果對象的方法不存在，使用可選鏈接運算符可以避免拋出錯誤，並且返回 <code>undefined</code>。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Alice'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function" style="color:rgb(80, 250, 123)">greet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'Hello!'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">user</span><span class="token operator">?.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">greet</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "Hello!"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">user</span><span class="token operator">?.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">sayGoodbye</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// undefined</span><br></span></code></pre></div></div>
<p>在這個例子中，<code>user?.greet()</code> 會調用 <code>greet</code> 方法並顯示 "Hello!"，而 <code>user?.sayGoodbye()</code> 則返回 <code>undefined</code>，因為 <code>sayGoodbye</code> 方法不存在。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="34-動態屬性名">3.4 動態屬性名<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#34-%E5%8B%95%E6%85%8B%E5%B1%AC%E6%80%A7%E5%90%8D" class="hash-link" aria-label="3.4 動態屬性名的直接連結" title="3.4 動態屬性名的直接連結" translate="no">​</a></h4>
<p>可選鏈接運算符也支持用動態屬性名來訪問對象屬性，這在處理具有不確定屬性的對象時非常有用。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Alice'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">preferences</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">theme</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'dark'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> key </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token operator">?.</span><span class="token plain">preferences</span><span class="token operator">?.</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "dark"</span><br></span></code></pre></div></div>
<p>在這個例子中，<code>key</code> 是一個變量，表示要訪問的屬性名，<code>user?.preferences?.[key]</code> 可以安全地獲取 <code>preferences</code> 中的 <code>theme</code> 屬性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-與傳統方法的比較">4. 與傳統方法的比較<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#4-%E8%88%87%E5%82%B3%E7%B5%B1%E6%96%B9%E6%B3%95%E7%9A%84%E6%AF%94%E8%BC%83" class="hash-link" aria-label="4. 與傳統方法的比較的直接連結" title="4. 與傳統方法的比較的直接連結" translate="no">​</a></h3>
<p>使用可選鏈接運算符，我們的代碼變得更加簡潔，減少了不必要的檢查。傳統的方式可能需要多次檢查對象的存在，才能安全地訪問某個屬性，而可選鏈接運算符讓這一過程變得直觀且易於維護。</p>
<p>傳統方法：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user </span><span class="token operator">&amp;&amp;</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">address</span><span class="token plain"> </span><span class="token operator">&amp;&amp;</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">street</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">address</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">street</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>使用可選鏈接運算符：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token operator">?.</span><span class="token plain">address</span><span class="token operator">?.</span><span class="token plain">street</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-可選鏈接運算符與-null-合併運算符">5. 可選鏈接運算符與 <code>null</code> 合併運算符（<code>??</code>）<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#5-%E5%8F%AF%E9%81%B8%E9%8F%88%E6%8E%A5%E9%81%8B%E7%AE%97%E7%AC%A6%E8%88%87-null-%E5%90%88%E4%BD%B5%E9%81%8B%E7%AE%97%E7%AC%A6" class="hash-link" aria-label="5-可選鏈接運算符與-null-合併運算符的直接連結" title="5-可選鏈接運算符與-null-合併運算符的直接連結" translate="no">​</a></h3>
<p>可選鏈接運算符經常與 <code>null</code> 合併運算符（<code>??</code>）一起使用。<code>??</code> 用來返回當前值是否為 <code>null</code> 或 <code>undefined</code>，如果是則返回其右側的值，否則返回當前值。</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword null nil" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> name </span><span class="token operator">=</span><span class="token plain"> user</span><span class="token operator">?.</span><span class="token plain">name </span><span class="token operator">??</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'Default Name'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// "Default Name"</span><br></span></code></pre></div></div>
<p>在這裡，<code>user?.name</code> 會返回 <code>undefined</code>，因為 <code>user</code> 是 <code>null</code>，而 <code>??</code> 會將其替換為 <code>'Default Name'</code>。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-總結">6. 總結<a href="https://tech.kdchang.com/blog/learning-notes-javascript-optional-chaining-intro-tutorial%20copy#6-%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="6. 總結的直接連結" title="6. 總結的直接連結" translate="no">​</a></h3>
<p>可選鏈接運算符（<code>?.</code>）是 JavaScript 中非常實用的一個特性，它簡化了嵌套對象屬性訪問的邏輯，避免了 <code>null</code> 或 <code>undefined</code> 帶來的錯誤，使代碼更加簡潔且容易理解。無論是在處理複雜的 API 返回數據還是操作動態結構的對象時，可選鏈接運算符都能發揮重要作用。在日常開發中，我們可以利用它來編寫更健壯、可讀性更強的代碼。</p>]]></content:encoded>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>frontend</category>
            <category>javascript</category>
            <category>Optional Chaining</category>
        </item>
        <item>
            <title><![CDATA[NextAuth.js 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial</guid>
            <pubDate>Sat, 21 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[一、前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="一前言">一、前言<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E4%B8%80%E5%89%8D%E8%A8%80" class="hash-link" aria-label="一、前言的直接連結" title="一、前言的直接連結" translate="no">​</a></h2>
<p>隨著 Web 開發從傳統伺服器渲染演變為前後端分離架構，處理「使用者認證」變得更為複雜。許多開發者不再單純依賴 session + cookie 的方式，而是轉向 token-based 的 JWT 或 OAuth 解決方案。</p>
<p>如果你正在使用 <strong>Next.js</strong>，那麼 <strong>NextAuth.js</strong> 是一套高度整合、彈性高且極為方便的認證函式庫。它支援多種認證方式（如 OAuth、Email、Credentials、LDAP、JWT 等），可與 Next.js 無縫整合，適合快速導入登入機制。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="二重點摘要">二、重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E4%BA%8C%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="二、重點摘要的直接連結" title="二、重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p>開源且專為 <strong>Next.js</strong> 設計的認證函式庫</p>
</li>
<li class="">
<p>支援：</p>
<ul>
<li class="">OAuth 第三方登入（Google、GitHub、Facebook、LINE 等）</li>
<li class="">Email 登入（magic link）</li>
<li class="">自定義帳密登入（Credentials provider）</li>
<li class="">JWT 無狀態驗證</li>
</ul>
</li>
<li class="">
<p>自動處理：</p>
<ul>
<li class="">session 建立與維護</li>
<li class="">cookies 管理</li>
<li class="">CSRF 保護</li>
</ul>
</li>
<li class="">
<p>可與資料庫整合（支援 Prisma、TypeORM、MongoDB 等）</p>
</li>
<li class="">
<p>可自定義：</p>
<ul>
<li class="">登入畫面</li>
<li class="">回傳的使用者資料</li>
<li class="">授權邏輯與回傳 token 欄位</li>
</ul>
</li>
<li class="">
<p>適用場景：企業內部登入、SaaS 後台、會員管理系統、整合第三方帳戶等</p>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="三建立-nextauth-基本專案">三、建立 NextAuth 基本專案<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E4%B8%89%E5%BB%BA%E7%AB%8B-nextauth-%E5%9F%BA%E6%9C%AC%E5%B0%88%E6%A1%88" class="hash-link" aria-label="三、建立 NextAuth 基本專案的直接連結" title="三、建立 NextAuth 基本專案的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-建立-nextjs-專案">1. 建立 Next.js 專案<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#1-%E5%BB%BA%E7%AB%8B-nextjs-%E5%B0%88%E6%A1%88" class="hash-link" aria-label="1. 建立 Next.js 專案的直接連結" title="1. 建立 Next.js 專案的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">npx create-next-app@latest nextauth-demo</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">cd nextauth-demo</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">npm install next-auth</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-建立-api-route">2. 建立 API Route<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#2-%E5%BB%BA%E7%AB%8B-api-route" class="hash-link" aria-label="2. 建立 API Route的直接連結" title="2. 建立 API Route的直接連結" translate="no">​</a></h3>
<p>在 <code>pages/api/auth/[...nextauth].js</code> 中新增設定：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// pages/api/auth/[...nextauth].js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">NextAuth</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'next-auth'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">GitHubProvider</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'next-auth/providers/github'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">NextAuth</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">providers</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">GitHubProvider</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">clientId</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">GITHUB_CLIENT_ID</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">clientSecret</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">GITHUB_CLIENT_SECRET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">callbacks</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> session</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> token </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">id</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> token</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">sub</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-設定環境變數">3. 設定環境變數<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#3-%E8%A8%AD%E5%AE%9A%E7%92%B0%E5%A2%83%E8%AE%8A%E6%95%B8" class="hash-link" aria-label="3. 設定環境變數的直接連結" title="3. 設定環境變數的直接連結" translate="no">​</a></h3>
<p>在 <code>.env.local</code> 中新增：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">GITHUB_CLIENT_ID=你的 GitHub OAuth ID</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">GITHUB_CLIENT_SECRET=你的 GitHub OAuth Secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">NEXTAUTH_SECRET=隨機生成的 secret（可用 openssl rand -base64 32）</span><br></span></code></pre></div></div>
<p>若使用 JWT，可加入：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">NEXTAUTH_JWT_SECRET=任意密鑰</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-在前端使用登入與登出功能">4. 在前端使用登入與登出功能<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#4-%E5%9C%A8%E5%89%8D%E7%AB%AF%E4%BD%BF%E7%94%A8%E7%99%BB%E5%85%A5%E8%88%87%E7%99%BB%E5%87%BA%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="4. 在前端使用登入與登出功能的直接連結" title="4. 在前端使用登入與登出功能的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// pages/index.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useSession</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> signIn</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> signOut </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'next-auth/react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">Home</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">data</span><span class="token operator">:</span><span class="token plain"> session </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useSession</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain">歡迎，</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain">，你已登入</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token plain">button onClick</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">signOut</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token plain">登出</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">button</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain">尚未登入</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">button onClick</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">signIn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'github'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token plain">使用 </span><span class="token maybe-class-name">GitHub</span><span class="token plain"> 登入</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">button</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-在-_appjs-包裝-sessionprovider">5. 在 <code>_app.js</code> 包裝 <code>SessionProvider</code><a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#5-%E5%9C%A8-_appjs-%E5%8C%85%E8%A3%9D-sessionprovider" class="hash-link" aria-label="5-在-_appjs-包裝-sessionprovider的直接連結" title="5-在-_appjs-包裝-sessionprovider的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// pages/_app.js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> </span><span class="token imports maybe-class-name">SessionProvider</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'next-auth/react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token parameter"> </span><span class="token parameter maybe-class-name">Component</span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token parameter"> pageProps </span><span class="token parameter punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token maybe-class-name">SessionProvider</span><span class="token plain"> session</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">pageProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token maybe-class-name">Component</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token spread operator">...</span><span class="token plain">pageProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">SessionProvider</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="四自訂登入方式credentials">四、自訂登入方式（Credentials）<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E5%9B%9B%E8%87%AA%E8%A8%82%E7%99%BB%E5%85%A5%E6%96%B9%E5%BC%8Fcredentials" class="hash-link" aria-label="四、自訂登入方式（Credentials）的直接連結" title="四、自訂登入方式（Credentials）的直接連結" translate="no">​</a></h2>
<p>除了 OAuth，也可以自訂帳號密碼登入：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// [...nextauth].js</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">CredentialsProvider</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"next-auth/providers/credentials"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token literal-property property">providers</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token function maybe-class-name" style="color:rgb(80, 250, 123)">CredentialsProvider</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Credentials"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">credentials</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">username</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"帳號"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"text"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token literal-property property">password</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">label</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"密碼"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">type</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"password"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">authorize</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">credentials</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">verifyUser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">credentials</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">username</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> credentials</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">password</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token keyword null nil" style="color:rgb(189, 147, 249);font-style:italic">null</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><br></span></code></pre></div></div>
<blockquote>
<p>注意：<code>authorize</code> 函式需自行驗證帳密，並回傳 <code>user</code> 物件，如 <code>{ id, name, email }</code></p>
</blockquote>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="五整合-jwt-模式無狀態認證">五、整合 JWT 模式（無狀態認證）<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E4%BA%94%E6%95%B4%E5%90%88-jwt-%E6%A8%A1%E5%BC%8F%E7%84%A1%E7%8B%80%E6%85%8B%E8%AA%8D%E8%AD%89" class="hash-link" aria-label="五、整合 JWT 模式（無狀態認證）的直接連結" title="五、整合 JWT 模式（無狀態認證）的直接連結" translate="no">​</a></h2>
<p>啟用 JWT 模式，只需設定：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token literal-property property">session</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">strategy</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"jwt"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token literal-property property">jwt</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token literal-property property">secret</span><span class="token operator">:</span><span class="token plain"> process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token constant" style="color:rgb(189, 147, 249)">NEXTAUTH_SECRET</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><br></span></code></pre></div></div>
<p>這樣每次登入都會發出加密的 JWT，並由前端自動存於 cookie。你可以透過 <code>getToken()</code> 從 API 端存取 token 中的自訂欄位。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="六取得-server-端-session">六、取得 Server 端 Session<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E5%85%AD%E5%8F%96%E5%BE%97-server-%E7%AB%AF-session" class="hash-link" aria-label="六、取得 Server 端 Session的直接連結" title="六、取得 Server 端 Session的直接連結" translate="no">​</a></h2>
<p>如果你需要在 API route 或 SSR 頁面取得使用者登入資訊，可使用：</p>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> getServerSession </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'next-auth/next'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> authOptions </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'./auth/[...nextauth]'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getServerSideProps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> session </span><span class="token operator">=</span><span class="token plain"> </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">await</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">getServerSession</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">req</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> context</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">res</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> authOptions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token literal-property property">props</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      session</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="七總結與延伸">七、總結與延伸<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E4%B8%83%E7%B8%BD%E7%B5%90%E8%88%87%E5%BB%B6%E4%BC%B8" class="hash-link" aria-label="七、總結與延伸的直接連結" title="七、總結與延伸的直接連結" translate="no">​</a></h2>
<table><thead><tr><th>功能</th><th>NextAuth 表現</th></tr></thead><tbody><tr><td>快速整合第三方登入</td><td>非常方便，僅需 provider 設定</td></tr><tr><td>安全性</td><td>預設 CSRF 保護、HttpOnly cookie</td></tr><tr><td>自訂性</td><td>高度可調整 callback、UI、資料庫整合</td></tr><tr><td>無狀態支援</td><td>支援 JWT、Access Token</td></tr><tr><td>SSR/SPA 支援</td><td>完整整合 getServerSideProps、Client Hook</td></tr></tbody></table>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="延伸功能建議">延伸功能建議<a href="https://tech.kdchang.com/blog/learning-notes-nextauth-js-intro-tutorial#%E5%BB%B6%E4%BC%B8%E5%8A%9F%E8%83%BD%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="延伸功能建議的直接連結" title="延伸功能建議的直接連結" translate="no">​</a></h2>
<ul>
<li class="">整合 Prisma 儲存使用者資料與 session（可自動生成 schema）</li>
<li class="">自訂登入 UI 與跳轉路徑</li>
<li class="">客製化 JWT payload 內容（如 role, id, user_type）</li>
<li class="">使用 <code>getToken()</code> 或 <code>useSession()</code> 搭配權限控制</li>
<li class="">多平台登入支援（Web、Mobile API）</li>
</ul>]]></content:encoded>
            <category>NextAuth.js</category>
            <category>Next.js</category>
        </item>
        <item>
            <title><![CDATA[React Context API 入門教學 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial</guid>
            <pubDate>Sat, 21 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[React Context API 是 React 提供的一種方式，讓我們能夠在組件樹中傳遞資料，而不需要一層層地使用 props。Context API 可以解決多層嵌套組件的傳遞問題，讓我們在深層組件中輕鬆訪問到全局狀態。本文將介紹如何使用 React Context API，並提供一個簡單的範例來展示其實際應用。]]></description>
            <content:encoded><![CDATA[<p>React Context API 是 React 提供的一種方式，讓我們能夠在組件樹中傳遞資料，而不需要一層層地使用 props。Context API 可以解決多層嵌套組件的傳遞問題，讓我們在深層組件中輕鬆訪問到全局狀態。本文將介紹如何使用 React Context API，並提供一個簡單的範例來展示其實際應用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="什麼是-react-context-api">什麼是 React Context API<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E4%BB%80%E9%BA%BC%E6%98%AF-react-context-api" class="hash-link" aria-label="什麼是 React Context API的直接連結" title="什麼是 React Context API的直接連結" translate="no">​</a></h3>
<p>React Context API 是 React 的一個內建功能，它可以讓我們在組件樹中共享資料，避免多層嵌套的 props 傳遞。Context 主要由三個部分組成：</p>
<ol>
<li class=""><strong>React.createContext()</strong>：創建一個 Context 物件。</li>
<li class=""><strong>Provider</strong>：這是 Context API 中的一個組件，它用來包裹整個應用，並提供一個全局的資料源。</li>
<li class=""><strong>Consumer</strong>：這是用來訪問 Context 資料的組件，它能夠獲取 Provider 中傳遞的資料。</li>
</ol>
<p>使用 Context 的目的，是為了避免將相同的資料層層傳遞到每個組件，這樣可以讓應用的資料流變得更加簡潔。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用-context-api-的步驟">使用 Context API 的步驟<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E4%BD%BF%E7%94%A8-context-api-%E7%9A%84%E6%AD%A5%E9%A9%9F" class="hash-link" aria-label="使用 Context API 的步驟的直接連結" title="使用 Context API 的步驟的直接連結" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟-1-創建-context">步驟 1: 創建 Context<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E6%AD%A5%E9%A9%9F-1-%E5%89%B5%E5%BB%BA-context" class="hash-link" aria-label="步驟 1: 創建 Context的直接連結" title="步驟 1: 創建 Context的直接連結" translate="no">​</a></h4>
<p>首先，我們需要使用 <code>React.createContext()</code> 來創建一個 Context 物件。這個物件會返回一個 <code>Provider</code> 和 <code>Consumer</code> 組件，讓我們在應用中使用。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 創建 Context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">MyContext</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">createContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟-2-使用-provider-來傳遞資料">步驟 2: 使用 Provider 來傳遞資料<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E6%AD%A5%E9%A9%9F-2-%E4%BD%BF%E7%94%A8-provider-%E4%BE%86%E5%82%B3%E9%81%9E%E8%B3%87%E6%96%99" class="hash-link" aria-label="步驟 2: 使用 Provider 來傳遞資料的直接連結" title="步驟 2: 使用 Provider 來傳遞資料的直接連結" translate="no">​</a></h4>
<p>Context 的 <code>Provider</code> 是用來包裹應用的，它會接收一個 <code>value</code> 屬性，這個屬性就是要共享給整個組件樹的資料。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setUser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'John'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token plain"> value</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token maybe-class-name">UserProfile</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>在這個範例中，我們將一個 <code>user</code> 物件傳遞給 <code>MyContext.Provider</code> 的 <code>value</code> 屬性，這樣整個組件樹中的所有子組件都能夠訪問到這個 <code>user</code> 資料。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟-3-使用-consumer-來接收資料">步驟 3: 使用 Consumer 來接收資料<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E6%AD%A5%E9%A9%9F-3-%E4%BD%BF%E7%94%A8-consumer-%E4%BE%86%E6%8E%A5%E6%94%B6%E8%B3%87%E6%96%99" class="hash-link" aria-label="步驟 3: 使用 Consumer 來接收資料的直接連結" title="步驟 3: 使用 Consumer 來接收資料的直接連結" translate="no">​</a></h4>
<p>在需要使用資料的地方，我們可以使用 <code>MyContext.Consumer</code> 來獲取資料。<code>Consumer</code> 的 <code>children</code> 是一個函數，它會接收一個 <code>value</code> 參數，這個參數就是在 <code>Provider</code> 中傳遞的資料。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">UserProfile</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Consumer</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token operator">&lt;</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token operator">&lt;</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token maybe-class-name">Age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">age</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Consumer</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>在這個範例中，<code>UserProfile</code> 組件通過 <code>Consumer</code> 來訪問 <code>MyContext</code> 中的 <code>user</code> 資料，並渲染顯示用戶的名字和年齡。</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="步驟-4-使用-usecontext-hook-react-168-及以上">步驟 4: 使用 <code>useContext</code> Hook (React 16.8 及以上)<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E6%AD%A5%E9%A9%9F-4-%E4%BD%BF%E7%94%A8-usecontext-hook-react-168-%E5%8F%8A%E4%BB%A5%E4%B8%8A" class="hash-link" aria-label="步驟-4-使用-usecontext-hook-react-168-及以上的直接連結" title="步驟-4-使用-usecontext-hook-react-168-及以上的直接連結" translate="no">​</a></h4>
<p>React 16.8 引入了 <code>useContext</code> Hook，這樣我們可以更方便地在函數組件中使用 Context，而不需要使用 <code>Consumer</code>。這樣的寫法更加簡潔，並且避免了過多的嵌套。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useContext </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">UserProfile</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token maybe-class-name">Age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">age</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>使用 <code>useContext</code> 可以直接從 Context 中獲取資料，而不需要使用 <code>Consumer</code>。這使得代碼更簡潔，並提高了可讀性。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例">實際範例<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際範例的直接連結" title="實際範例的直接連結" translate="no">​</a></h3>
<p>下面是一個完整的範例，展示了如何使用 React Context API 來管理應用中的全局狀態。這個範例將包括一個用戶資料的管理，並能夠在多個組件中共享這些資料。</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">import</span><span class="token plain"> </span><span class="token imports maybe-class-name">React</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token imports"> useState</span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token imports"> useContext </span><span class="token imports punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">from</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'react'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 創建 Context</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token maybe-class-name">MyContext</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token maybe-class-name">React</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">createContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">App</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> setUser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">name</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'John'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token plain"> value</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token maybe-class-name">UserProfile</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token operator">&lt;</span><span class="token maybe-class-name">AgeUpdater</span><span class="token plain"> </span><span class="token operator">/</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access maybe-class-name">Provider</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">UserProfile</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">h1</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token maybe-class-name">Age</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">age</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">p</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function maybe-class-name" style="color:rgb(80, 250, 123)">AgeUpdater</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> user </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token maybe-class-name">MyContext</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> setUser </span><span class="token operator">=</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">useState</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> </span><span class="token function-variable function" style="color:rgb(80, 250, 123)">updateAge</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">setUser</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token spread operator">...</span><span class="token plain">user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token literal-property property">age</span><span class="token operator">:</span><span class="token plain"> user</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">age</span><span class="token plain"> </span><span class="token operator">+</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">&lt;</span><span class="token plain">button onClick</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain">updateAge</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token operator">&gt;</span><span class="token maybe-class-name">Increase</span><span class="token plain"> </span><span class="token maybe-class-name">Age</span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">button</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token operator">&lt;</span><span class="token operator">/</span><span class="token plain">div</span><span class="token operator">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">export</span><span class="token plain"> </span><span class="token keyword module" style="color:rgb(189, 147, 249);font-style:italic">default</span><span class="token plain"> </span><span class="token maybe-class-name">App</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例解析">範例解析<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E7%AF%84%E4%BE%8B%E8%A7%A3%E6%9E%90" class="hash-link" aria-label="範例解析的直接連結" title="範例解析的直接連結" translate="no">​</a></h3>
<ol>
<li class=""><strong>App 組件</strong>：在 <code>App</code> 組件中，我們使用 <code>useState</code> 定義了一個 <code>user</code> 資料，並通過 <code>MyContext.Provider</code> 將資料提供給下層組件。</li>
<li class=""><strong>UserProfile 組件</strong>：<code>UserProfile</code> 使用 <code>useContext</code> 來讀取 <code>MyContext</code> 中的資料，並顯示用戶的名字和年齡。</li>
<li class=""><strong>AgeUpdater 組件</strong>：這個組件同樣使用 <code>useContext</code> 來讀取和更新 <code>user</code> 資料。我們在這裡定義了一個按鈕，當按下時，會更新 <code>user</code> 的年齡。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="context-api-的優缺點">Context API 的優缺點<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#context-api-%E7%9A%84%E5%84%AA%E7%BC%BA%E9%BB%9E" class="hash-link" aria-label="Context API 的優缺點的直接連結" title="Context API 的優缺點的直接連結" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="優點">優點：<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E5%84%AA%E9%BB%9E" class="hash-link" aria-label="優點：的直接連結" title="優點：的直接連結" translate="no">​</a></h4>
<ol>
<li class=""><strong>簡化資料傳遞</strong>：當我們需要在多層嵌套的組件中共享資料時，使用 Context 可以避免繁瑣的 props 傳遞。</li>
<li class=""><strong>可擴展性</strong>：Context 非常適合用於應用中的全局狀態管理，像是用戶認證、語言設置、主題樣式等。</li>
</ol>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="缺點">缺點：<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E7%BC%BA%E9%BB%9E" class="hash-link" aria-label="缺點：的直接連結" title="缺點：的直接連結" translate="no">​</a></h4>
<ol>
<li class=""><strong>重新渲染問題</strong>：當 <code>Provider</code> 中的資料變更時，所有使用該 Context 的組件都會重新渲染。對於大型應用來說，這可能會影響性能。</li>
<li class=""><strong>狀態過度共享</strong>：Context 主要用於共享全局資料，如果將太多不相關的資料放入同一個 Context，可能會使代碼變得難以維護。</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-react-context-api-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h3>
<p>React Context API 是一個強大的工具，可以幫助我們管理應用中的全局狀態。在適當的情況下使用 Context 可以大大簡化代碼，避免深層嵌套的 props 傳遞。但也需要謹慎使用，避免過多不必要的資料共享，從而影響性能和可維護性。在開發中，我們可以根據具體需求來選擇是否使用 Context API，並搭配其他狀態管理工具（如 Redux 或 Zustand）來管理更複雜的應用狀態。</p>
<h1>參考文件</h1>
<ol>
<li class=""><a href="https://muki.tw/react-context-api/" target="_blank" rel="noopener noreferrer" class="">用 React Context API 實作跨組件傳值的功能</a></li>
</ol>]]></content:encoded>
            <category>前端</category>
            <category>前端開發</category>
            <category>前端工程</category>
            <category>frontend</category>
            <category>javascript</category>
            <category>React</category>
            <category>React.js</category>
            <category>ReactJS</category>
            <category>useContext</category>
            <category>Context API</category>
        </item>
        <item>
            <title><![CDATA[Chrome Extension 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial</guid>
            <pubDate>Fri, 20 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>Chrome Extension（Chrome 擴充功能）是針對 Google Chrome 瀏覽器開發的瀏覽器插件，能夠延伸瀏覽器的功能，提供更高效的使用體驗。你可以用它來自動化操作、強化 UI、增加捷徑、記錄內容、攔截請求等等。</p>
<p>本教學將介紹 Chrome Extension 的基本架構、開發流程與一個簡單的實作範例，協助你快速入門。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>基本架構：</strong></p>
<ul>
<li class=""><code>manifest.json</code>：擴充功能的核心設定檔</li>
<li class=""><code>background.js</code> / <code>service_worker.js</code>：背景邏輯（例如攔截請求、常駐任務）</li>
<li class=""><code>popup.html</code> + <code>popup.js</code>：點擊圖示後的互動 UI</li>
<li class=""><code>content.js</code>：注入頁面的腳本，直接與 DOM 互動</li>
</ul>
</li>
<li class="">
<p><strong>開發步驟：</strong></p>
<ul>
<li class="">建立資料夾結構與設定檔</li>
<li class="">撰寫功能腳本與 UI</li>
<li class="">在 Chrome 中載入未封裝的擴充功能</li>
<li class="">測試與除錯</li>
</ul>
</li>
<li class="">
<p><strong>核心權限與功能：</strong></p>
<ul>
<li class=""><code>permissions</code> 欄位需指定所需功能（如 <code>tabs</code>, <code>storage</code>, <code>scripting</code>）</li>
<li class=""><code>host_permissions</code> 控制哪些網站允許注入腳本</li>
<li class="">可與頁面雙向通訊</li>
</ul>
</li>
<li class="">
<p><strong>常見用途：</strong></p>
<ul>
<li class="">提高生產力（截圖、標記、翻譯）</li>
<li class="">資料擷取與分析（網頁爬蟲輔助）</li>
<li class="">儲存內容（書籤、待辦清單）</li>
<li class="">網站 UI 客製化</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例儲存選取文字的小擴充功能">實際範例：儲存選取文字的小擴充功能<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E5%84%B2%E5%AD%98%E9%81%B8%E5%8F%96%E6%96%87%E5%AD%97%E7%9A%84%E5%B0%8F%E6%93%B4%E5%85%85%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="實際範例：儲存選取文字的小擴充功能的直接連結" title="實際範例：儲存選取文字的小擴充功能的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="功能簡介">功能簡介<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E5%8A%9F%E8%83%BD%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="功能簡介的直接連結" title="功能簡介的直接連結" translate="no">​</a></h3>
<p>當使用者在網頁上選取一段文字並點擊擴充功能圖示，會將選取的文字儲存到 <code>localStorage</code>，方便後續檢視。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-專案結構">1. 專案結構<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#1-%E5%B0%88%E6%A1%88%E7%B5%90%E6%A7%8B" class="hash-link" aria-label="1. 專案結構的直接連結" title="1. 專案結構的直接連結" translate="no">​</a></h3>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">my-extension/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── manifest.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── popup.html</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── popup.js</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── content.js</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-manifestjson">2. manifest.json<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#2-manifestjson" class="hash-link" aria-label="2. manifest.json的直接連結" title="2. manifest.json的直接連結" translate="no">​</a></h3>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"manifest_version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"name"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Save Selected Text"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"version"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"1.0"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"description"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"儲存網頁中選取的文字"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"permissions"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"scripting"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"storage"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"host_permissions"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"&lt;all_urls&gt;"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"action"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"default_popup"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"popup.html"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"default_icon"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"16"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"icon16.png"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"48"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"icon48.png"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"128"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"icon128.png"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"background"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"service_worker"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"background.js"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"content_scripts"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"matches"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"&lt;all_urls&gt;"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"js"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"content.js"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-popuphtml">3. popup.html<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#3-popuphtml" class="hash-link" aria-label="3. popup.html的直接連結" title="3. popup.html的直接連結" translate="no">​</a></h3>
<div class="language-html codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-html codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token doctype punctuation" style="color:rgb(248, 248, 242)">&lt;!</span><span class="token doctype doctype-tag">DOCTYPE</span><span class="token doctype"> </span><span class="token doctype name">html</span><span class="token doctype punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">html</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">head</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">title</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">已儲存文字</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">title</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">head</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">body</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">h3</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain">你儲存的文字：</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">h3</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">ul</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">id</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">text-list</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">ul</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;</span><span class="token tag" style="color:rgb(255, 121, 198)">script</span><span class="token tag" style="color:rgb(255, 121, 198)"> </span><span class="token tag attr-name" style="color:rgb(241, 250, 140)">src</span><span class="token tag attr-value punctuation attr-equals" style="color:rgb(248, 248, 242)">=</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag attr-value" style="color:rgb(255, 121, 198)">popup.js</span><span class="token tag attr-value punctuation" style="color:rgb(248, 248, 242)">"</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token script"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">script</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">body</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&lt;/</span><span class="token tag" style="color:rgb(255, 121, 198)">html</span><span class="token tag punctuation" style="color:rgb(248, 248, 242)">&gt;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-popupjs">4. popup.js<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#4-popupjs" class="hash-link" aria-label="4. popup.js的直接連結" title="4. popup.js的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">addEventListener</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'DOMContentLoaded'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">async</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'savedTexts'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> list </span><span class="token operator">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">getElementById</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'text-list'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> texts </span><span class="token operator">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">savedTexts</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    texts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">forEach</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> li </span><span class="token operator">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">createElement</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'li'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      li</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">textContent</span><span class="token plain"> </span><span class="token operator">=</span><span class="token plain"> text</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      list</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">appendChild</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">li</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-contentjs">5. content.js<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#5-contentjs" class="hash-link" aria-label="5. content.js的直接連結" title="5. content.js的直接連結" translate="no">​</a></h3>
<div class="language-js codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-js codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">addEventListener</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'mouseup'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> selectedText </span><span class="token operator">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">window</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">getSelection</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">toString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">trim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">selectedText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'savedTexts'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> current </span><span class="token operator">=</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">savedTexts</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      current</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">push</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">selectedText</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">savedTexts</span><span class="token operator">:</span><span class="token plain"> current </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-載入擴充功能">6. 載入擴充功能<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#6-%E8%BC%89%E5%85%A5%E6%93%B4%E5%85%85%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="6. 載入擴充功能的直接連結" title="6. 載入擴充功能的直接連結" translate="no">​</a></h3>
<ol>
<li class="">打開 Chrome 瀏覽器</li>
<li class="">前往 <code>chrome://extensions/</code></li>
<li class="">開啟右上角「開發人員模式」</li>
<li class="">點選「載入未封裝項目」</li>
<li class="">選取專案資料夾（my-extension）</li>
</ol>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-測試方式">7. 測試方式<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#7-%E6%B8%AC%E8%A9%A6%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="7. 測試方式的直接連結" title="7. 測試方式的直接連結" translate="no">​</a></h3>
<ol>
<li class="">任意打開一個網頁</li>
<li class="">選取文字後放開滑鼠</li>
<li class="">點擊瀏覽器右上角的擴充功能圖示</li>
<li class="">在彈出的視窗中查看剛才儲存的文字</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="小技巧與補充">小技巧與補充<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E5%B0%8F%E6%8A%80%E5%B7%A7%E8%88%87%E8%A3%9C%E5%85%85" class="hash-link" aria-label="小技巧與補充的直接連結" title="小技巧與補充的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>使用 TypeScript</strong>：可結合 <code>Vite</code> 或 <code>webpack</code> 實現模組化開發。</li>
<li class=""><strong>Hot Reload</strong>：透過專案如 <code>crxjs</code> 可達成自動刷新套件。</li>
<li class=""><strong>權限最小化原則</strong>：僅使用必要權限以通過審核。</li>
<li class=""><strong>Storage API</strong>：可選擇使用 <code>localStorage</code>、<code>chrome.storage.local</code>、<code>sync</code> 等不同儲存方式。</li>
<li class=""><strong>message passing</strong>：背景與 content script 可用 <code>chrome.runtime.sendMessage</code> 溝通。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>Chrome Extension 是一個強大的平台，讓開發者能以 HTML、CSS、JavaScript 等前端技術打造自己的工具。無論是為了解決個人需求、改善使用體驗，還是作為產品 MVP 的雛型開發平台，Chrome Extension 都是值得投資時間學習的技術。</p>
<p>從簡單的文字儲存開始，你可以慢慢拓展功能，加入右鍵選單、快捷鍵、網頁改寫、API 串接等高階應用，打造出屬於你自己的瀏覽器擴充小工具。</p>]]></content:encoded>
            <category>Chrome Extension</category>
            <category>Chrome</category>
        </item>
        <item>
            <title><![CDATA[chrome.storage.local vs. localStorage：資料儲存方式差異入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial</guid>
            <pubDate>Fri, 20 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在前端與 Chrome 擴充功能（Chrome Extension）開發中，「資料儲存」是一項常見需求。開發者常見的兩種方式為：</p>
<ul>
<li class=""><code>localStorage</code>：瀏覽器原生提供的本地儲存 API</li>
<li class=""><code>chrome.storage.local</code>：Chrome Extension 提供的本地儲存 API</li>
</ul>
<p>兩者名稱相似，功能也都能儲存 key-value 結構資料，但其用途、行為、效能與限制卻有顯著差異。本篇筆記將帶你掌握這兩者的差別，並透過實作範例協助你在開發時做出正確選擇。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-定義與使用場景">1. 定義與使用場景<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#1-%E5%AE%9A%E7%BE%A9%E8%88%87%E4%BD%BF%E7%94%A8%E5%A0%B4%E6%99%AF" class="hash-link" aria-label="1. 定義與使用場景的直接連結" title="1. 定義與使用場景的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><code>localStorage</code> 是 Web API，適用於一般網頁與 Content Script，操作簡便但同步。</li>
<li class=""><code>chrome.storage.local</code> 是專為 Chrome 擴充功能設計的非同步儲存 API，適用於 Background、Popup、Options、Content Script 等擴充功能組件。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-主要差異比較">2. 主要差異比較<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#2-%E4%B8%BB%E8%A6%81%E5%B7%AE%E7%95%B0%E6%AF%94%E8%BC%83" class="hash-link" aria-label="2. 主要差異比較的直接連結" title="2. 主要差異比較的直接連結" translate="no">​</a></h3>
<table><thead><tr><th>項目</th><th><code>chrome.storage.local</code></th><th><code>localStorage</code></th></tr></thead><tbody><tr><td>API 類型</td><td>非同步</td><td>同步</td></tr><tr><td>儲存容量</td><td>約 5MB 以上，可依平台調整</td><td>約 5MB（視瀏覽器而定）</td></tr><tr><td>使用範圍</td><td>限於 Chrome Extension</td><td>網頁與 Content Script</td></tr><tr><td>安全性與隔離性</td><td>高（與其他網站與擴充隔離）</td><td>中（每個 domain 隔離）</td></tr><tr><td>可與 background/popup 共用</td><td>是</td><td>否</td></tr><tr><td>是否支援跨裝置同步</td><td>使用 <code>chrome.storage.sync</code> 可支援</td><td>否</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-實作差異">3. 實作差異<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#3-%E5%AF%A6%E4%BD%9C%E5%B7%AE%E7%95%B0" class="hash-link" aria-label="3. 實作差異的直接連結" title="3. 實作差異的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><code>localStorage</code> 使用方式簡單、同步，可立即取得值。</li>
<li class=""><code>chrome.storage.local</code> 是非同步設計，需透過 callback 或 Promise 取得值。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例比較">實際範例比較<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E6%AF%94%E8%BC%83" class="hash-link" aria-label="實際範例比較的直接連結" title="實際範例比較的直接連結" translate="no">​</a></h2>
<p>以下為兩者的典型儲存與讀取操作實作方式。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="一使用-localstorage同步">一、使用 <code>localStorage</code>（同步）<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E4%B8%80%E4%BD%BF%E7%94%A8-localstorage%E5%90%8C%E6%AD%A5" class="hash-link" aria-label="一使用-localstorage同步的直接連結" title="一使用-localstorage同步的直接連結" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="儲存資料">儲存資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%84%B2%E5%AD%98%E8%B3%87%E6%96%99" class="hash-link" aria-label="儲存資料的直接連結" title="儲存資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">localStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'kdchang'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="讀取資料">讀取資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E8%AE%80%E5%8F%96%E8%B3%87%E6%96%99" class="hash-link" aria-label="讀取資料的直接連結" title="讀取資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> name </span><span class="token operator">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">localStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">getItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)">// 輸出：kdchang</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="刪除資料">刪除資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%88%AA%E9%99%A4%E8%B3%87%E6%96%99" class="hash-link" aria-label="刪除資料的直接連結" title="刪除資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">localStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">removeItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="優點與限制">優點與限制<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%84%AA%E9%BB%9E%E8%88%87%E9%99%90%E5%88%B6" class="hash-link" aria-label="優點與限制的直接連結" title="優點與限制的直接連結" translate="no">​</a></h4>
<ul>
<li class=""><strong>優點</strong>：簡單、直覺、無需 callback</li>
<li class=""><strong>限制</strong>：無法在 background script 中使用、同步操作可能阻塞 UI、無跨 component 溝通機制</li>
</ul>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="二使用-chromestoragelocal非同步">二、使用 <code>chrome.storage.local</code>（非同步）<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E4%BA%8C%E4%BD%BF%E7%94%A8-chromestoragelocal%E9%9D%9E%E5%90%8C%E6%AD%A5" class="hash-link" aria-label="二使用-chromestoragelocal非同步的直接連結" title="二使用-chromestoragelocal非同步的直接連結" translate="no">​</a></h3>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="儲存資料-1">儲存資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%84%B2%E5%AD%98%E8%B3%87%E6%96%99-1" class="hash-link" aria-label="儲存資料的直接連結" title="儲存資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">username</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'kdchang'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'儲存成功'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="讀取資料-1">讀取資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E8%AE%80%E5%8F%96%E8%B3%87%E6%96%99-1" class="hash-link" aria-label="讀取資料的直接連結" title="讀取資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'讀到的值:'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">username</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="刪除資料-1">刪除資料<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%88%AA%E9%99%A4%E8%B3%87%E6%96%99-1" class="hash-link" aria-label="刪除資料的直接連結" title="刪除資料的直接連結" translate="no">​</a></h4>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">remove</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'已刪除 username'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="優點與限制-1">優點與限制<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%84%AA%E9%BB%9E%E8%88%87%E9%99%90%E5%88%B6-1" class="hash-link" aria-label="優點與限制的直接連結" title="優點與限制的直接連結" translate="no">​</a></h4>
<ul>
<li class=""><strong>優點</strong>：資料與擴充功能隔離、安全性高、能跨組件共享</li>
<li class=""><strong>限制</strong>：需處理非同步流程（可用 async/await 解決）</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實戰應用擴充功能記錄使用者偏好設定">實戰應用：擴充功能記錄使用者偏好設定<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%AF%A6%E6%88%B0%E6%87%89%E7%94%A8%E6%93%B4%E5%85%85%E5%8A%9F%E8%83%BD%E8%A8%98%E9%8C%84%E4%BD%BF%E7%94%A8%E8%80%85%E5%81%8F%E5%A5%BD%E8%A8%AD%E5%AE%9A" class="hash-link" aria-label="實戰應用：擴充功能記錄使用者偏好設定的直接連結" title="實戰應用：擴充功能記錄使用者偏好設定的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="專案背景">專案背景<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E5%B0%88%E6%A1%88%E8%83%8C%E6%99%AF" class="hash-link" aria-label="專案背景的直接連結" title="專案背景的直接連結" translate="no">​</a></h3>
<p>你開發了一個可自訂主題配色的擴充功能，使用者可以切換「深色」或「淺色」模式，並希望記錄下來。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="localstorage-實作方式限-content-script">localStorage 實作方式（限 Content Script）<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#localstorage-%E5%AF%A6%E4%BD%9C%E6%96%B9%E5%BC%8F%E9%99%90-content-script" class="hash-link" aria-label="localStorage 實作方式（限 Content Script）的直接連結" title="localStorage 實作方式（限 Content Script）的直接連結" translate="no">​</a></h3>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 儲存使用者偏好</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">localStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'dark'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 頁面載入時讀取</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">const</span><span class="token plain"> theme </span><span class="token operator">=</span><span class="token plain"> </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">localStorage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">getItem</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setAttribute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'data-theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> theme</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>此方法雖然簡便，但無法在 background script、popup 等元件中共用。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="chromestoragelocal-實作方式推薦">chrome.storage.local 實作方式（推薦）<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#chromestoragelocal-%E5%AF%A6%E4%BD%9C%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%96%A6" class="hash-link" aria-label="chrome.storage.local 實作方式（推薦）的直接連結" title="chrome.storage.local 實作方式（推薦）的直接連結" translate="no">​</a></h3>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 儲存</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">set</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> </span><span class="token literal-property property">theme</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'dark'</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token console class-name">console</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">log</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'儲存主題成功'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)">// 讀取</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">chrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">get</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">'theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token arrow operator">=&gt;</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token dom variable" style="color:rgb(189, 147, 249);font-style:italic">document</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">body</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">setAttribute</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'data-theme'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token property-access">theme</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'light'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>這種方式可讓 background.js、popup.html、options.html 都能取得相同資料，並透過 message passing 進一步溝通。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用建議與最佳實踐">使用建議與最佳實踐<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E4%BD%BF%E7%94%A8%E5%BB%BA%E8%AD%B0%E8%88%87%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90" class="hash-link" aria-label="使用建議與最佳實踐的直接連結" title="使用建議與最佳實踐的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="選擇依據">選擇依據：<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E9%81%B8%E6%93%87%E4%BE%9D%E6%93%9A" class="hash-link" aria-label="選擇依據：的直接連結" title="選擇依據：的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>Chrome Extension 專案開發時</strong>：建議一律使用 <code>chrome.storage.local</code>，搭配 async/await 管理非同步流程。</li>
<li class=""><strong>Content Script 或網頁前端小工具</strong>：若不考慮擴充功能架構，可使用 <code>localStorage</code> 快速開發。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="注意事項">注意事項：<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E6%B3%A8%E6%84%8F%E4%BA%8B%E9%A0%85" class="hash-link" aria-label="注意事項：的直接連結" title="注意事項：的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><code>chrome.storage.local</code> 每次寫入都是非同步，避免過度頻繁更新（例如輸入框每秒觸發）</li>
<li class=""><code>localStorage</code> 資料若寫入太大或格式不當，可能造成同步錯誤或被清除</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-chrome-extension-local-storage-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<table><thead><tr><th>問題</th><th>建議做法</th></tr></thead><tbody><tr><td>在 popup、background、content script 共享設定資料</td><td>使用 <code>chrome.storage.local</code></td></tr><tr><td>快速暫存使用者動作、不需跨頁</td><td>可用 <code>localStorage</code></td></tr><tr><td>需考慮非同步、可擴充、跨頁共享與安全性</td><td>優先使用 <code>chrome.storage.local</code></td></tr></tbody></table>
<p><code>chrome.storage.local</code> 與 <code>localStorage</code> 各有適用場景與特點，選擇時需考量使用環境、效能、安全性與 API 特性。透過本文你應該能更清楚何時該用哪一種方式，並應用在擴充功能與網頁開發中，打造穩定且高效的儲存邏輯。</p>]]></content:encoded>
            <category>Chrome Extension</category>
            <category>Chrome</category>
            <category>chrome.storage.local</category>
            <category>localStorage</category>
        </item>
        <item>
            <title><![CDATA[Claude Code 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial</guid>
            <pubDate>Fri, 20 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>隨著 AI 編程助手的發展，越來越多開發者開始使用 AI 工具來加速開發流程。由 Anthropic 推出的 <strong>Claude Code</strong>，是一個專為程式設計任務設計的生成式 AI 模型。它結合 Claude 的強大語言理解能力，專注於代碼撰寫、重構、除錯與文件生成，並支援多種程式語言。</p>
<p>與 GitHub Copilot、ChatGPT 等工具類似，Claude Code 目標是協助開發者更有效率地完成日常開發任務。不過，它也有獨特的優勢：例如更嚴謹的安全性考量、對上下文理解的廣度，以及與 Anthropic 所提倡的 Constitutional AI 架構相結合的「安全設計」。</p>
<p>本篇筆記將說明 Claude Code 的特色與基礎使用方式，協助你快速上手。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>Claude Code 是什麼？</strong></p>
<ul>
<li class="">由 Anthropic 推出的 AI 編程輔助模型，基於 Claude 模型微調。</li>
<li class="">專注於程式碼相關任務，如生成、除錯、補完與重構。</li>
</ul>
</li>
<li class="">
<p><strong>支援的平台與模式</strong></p>
<ul>
<li class="">可透過 Claude 官網、Slack 整合、API 介面使用。</li>
<li class="">Claude 3 模型家族中的 Sonnet 與 Opus 皆支援 Code 能力。</li>
</ul>
</li>
<li class="">
<p><strong>支援語言與任務</strong></p>
<ul>
<li class="">語言：Python、JavaScript、TypeScript、Java、C/C++、HTML/CSS、SQL 等。</li>
<li class="">任務類型：代碼生成、文件產生、除錯、測試碼產出、重構與最佳化。</li>
</ul>
</li>
<li class="">
<p><strong>與其他工具的差異</strong></p>
<ul>
<li class="">支援更長的上下文（可達 200k tokens）</li>
<li class="">回應更具可讀性與解釋性</li>
<li class="">Anthropic 特別強調「可控性」與「安全性」</li>
</ul>
</li>
<li class="">
<p><strong>使用方式</strong></p>
<ul>
<li class="">Claude 聊天介面中貼上程式碼片段與指令</li>
<li class="">使用 prompt 提示具體任務，例如 “請優化這段程式碼” 或 “請解釋下列 Python 函數的功能”</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例教學">實際範例教學<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B%E6%95%99%E5%AD%B8" class="hash-link" aria-label="實際範例教學的直接連結" title="實際範例教學的直接連結" translate="no">​</a></h2>
<p>以下範例將以 Claude Code 處理 Python 程式碼為例，展示常見任務的應用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-程式碼生成">1. 程式碼生成<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#1-%E7%A8%8B%E5%BC%8F%E7%A2%BC%E7%94%9F%E6%88%90" class="hash-link" aria-label="1. 程式碼生成的直接連結" title="1. 程式碼生成的直接連結" translate="no">​</a></h3>
<p><strong>Prompt：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">請用 Python 寫一個函式，接收一個整數列表，回傳最大值與最小值。</span><br></span></code></pre></div></div>
<p><strong>Claude Code 回應：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">find_min_max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">not</span><span class="token plain"> numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token boolean">None</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">min</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token builtin" style="color:rgb(189, 147, 249)">max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">numbers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 使用範例</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">nums </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token number">3</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">4</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">min_val</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> max_val </span><span class="token operator">=</span><span class="token plain"> find_min_max</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">nums</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">f"最小值: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">min_val</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">, 最大值: </span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string-interpolation interpolation">max_val</span><span class="token string-interpolation interpolation punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token string-interpolation string" style="color:rgb(255, 121, 198)">"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-程式碼除錯">2. 程式碼除錯<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#2-%E7%A8%8B%E5%BC%8F%E7%A2%BC%E9%99%A4%E9%8C%AF" class="hash-link" aria-label="2. 程式碼除錯的直接連結" title="2. 程式碼除錯的直接連結" translate="no">​</a></h3>
<p><strong>輸入錯誤程式碼：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">divide</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> a </span><span class="token operator">/</span><span class="token plain"> b</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">divide</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p><strong>Prompt：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">這段程式碼會發生什麼錯誤？要怎麼修改？</span><br></span></code></pre></div></div>
<p><strong>Claude Code 回應：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">這段程式碼會在除以 0 時產生 ZeroDivisionError 錯誤。你可以加入 try-except 來處理例外：</span><br></span></code></pre></div></div>
<p><strong>建議修改版本：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">divide</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> b</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">try</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> a </span><span class="token operator">/</span><span class="token plain"> b</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">except</span><span class="token plain"> ZeroDivisionError</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"除數不能為零"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">print</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">divide</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token number">10</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-重構程式碼">3. 重構程式碼<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#3-%E9%87%8D%E6%A7%8B%E7%A8%8B%E5%BC%8F%E7%A2%BC" class="hash-link" aria-label="3. 重構程式碼的直接連結" title="3. 重構程式碼的直接連結" translate="no">​</a></h3>
<p><strong>Prompt：</strong></p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">請幫我重構以下 Python 程式，使其更簡潔易讀</span><br></span></code></pre></div></div>
<p><strong>原始程式碼：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    result </span><span class="token operator">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> item </span><span class="token operator">%</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            result</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">append</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">item </span><span class="token operator">*</span><span class="token plain"> item</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> result</span><br></span></code></pre></div></div>
<p><strong>Claude Code 回應：</strong></p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">process</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">item </span><span class="token operator">**</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> item </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> data </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> item </span><span class="token operator">%</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token number">0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實用建議與提示">實用建議與提示<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E5%AF%A6%E7%94%A8%E5%BB%BA%E8%AD%B0%E8%88%87%E6%8F%90%E7%A4%BA" class="hash-link" aria-label="實用建議與提示的直接連結" title="實用建議與提示的直接連結" translate="no">​</a></h2>
<ul>
<li class=""><strong>具體的指令更有效</strong>：直接說明你要做什麼，例如「請將下列 TypeScript 重構為具名函式」，比「幫我修改程式碼」效果更好。</li>
<li class=""><strong>使用上下文編輯技巧</strong>：可貼上整個 class 或 module，再指明哪個函式需要優化。</li>
<li class=""><strong>加入期望輸出範例</strong>：讓 Claude Code 理解你的預期輸入與輸出格式。</li>
<li class=""><strong>搭配 API 使用</strong>：進階用戶可透過 Anthropic API 將 Claude Code 整合進開發工作流程或 IDE 插件。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>Claude Code 是新一代 AI 編程輔助工具的代表之一，其簡潔清晰的語言理解能力與較高的上下文記憶範圍，使其特別適合處理複雜的程式重構與跨檔案邏輯分析。無論你是初學者還是資深開發者，只要善用 prompt 的語言描述能力與 Claude Code 的生成特性，就能提升開發效率與程式品質。</p>
<p>若你正尋求 Copilot 或 ChatGPT 的替代方案，Claude Code 值得一試。</p>
<hr>
<p>若你有特定開發環境（如 VSCode、CLI、Slack）或語言需求，我可以進一步為你量身規劃 Claude Code 的使用方式與整合建議。需要的話也可以補充教學範例。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-claude-code-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://github.com/doggy8088/github-copilot-configs" target="_blank" rel="noopener noreferrer" class="">最佳 GitHub Copilot 設定</a></li>
<li class=""><a href="https://medium.com/@cct0201/%E5%BE%9E-%E5%AF%AB%E7%A8%8B%E5%BC%8F-%E5%88%B0-%E8%88%87-ai-%E5%85%B1%E8%88%9E-%E6%88%91%E5%9C%A8%E5%85%AC%E5%8F%B8%E6%8E%A8%E5%8B%95-vibe-coding-%E7%9A%84%E7%B6%93%E9%A9%97%E5%88%86%E4%BA%AB-015e28909290" target="_blank" rel="noopener noreferrer" class="">從「寫程式」到「與 AI 共舞」── 我在公司推動 Vibe Coding 的經驗分享</a></li>
<li class=""><a href="https://gitmind.com/tw/best-vibe-coding-tools-2025.html" target="_blank" rel="noopener noreferrer" class="">2025 年最強推薦 Vibe Coding 工具一次看懂</a></li>
</ol>]]></content:encoded>
            <category>Claude</category>
            <category>Claude Code</category>
            <category>編輯器</category>
            <category>AI IDE</category>
        </item>
        <item>
            <title><![CDATA[GitHub Copilot 與 Copilot Agent 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial</guid>
            <pubDate>Fri, 20 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>自 2021 年推出以來，<strong>GitHub Copilot</strong> 已成為全球數百萬開發者的編程助手，能根據上下文即時補全程式碼。2024 年，GitHub 推出進階功能 <strong>Copilot Workspace</strong> 和 <strong>Copilot Agents</strong>，進一步從單一代碼補全進化為具備「理解、操作與協作」能力的智慧 AI 開發助理。</p>
<p>這些新功能不僅能幫助你完成程式碼撰寫，還能協助理解需求、分析問題、規劃專案、重構模組，甚至提出 pull request 變更。本文將帶你全面認識 GitHub Copilot 及其 Agent 功能，從基本操作到進階應用，協助你提升開發效率與品質。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-github-copilot-基本功能">1. GitHub Copilot 基本功能<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#1-github-copilot-%E5%9F%BA%E6%9C%AC%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="1. GitHub Copilot 基本功能的直接連結" title="1. GitHub Copilot 基本功能的直接連結" translate="no">​</a></h3>
<ul>
<li class="">AI 程式碼補全：根據上下文推薦下一行或整段程式碼。</li>
<li class="">註解驅動撰寫：使用自然語言描述意圖，自動轉換為程式碼。</li>
<li class="">測試與樣板產生：自動產出單元測試與樣板邏輯。</li>
<li class="">支援 VS Code、JetBrains IDE、Neovim 等。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-copilot-agents-新功能">2. Copilot Agents 新功能<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#2-copilot-agents-%E6%96%B0%E5%8A%9F%E8%83%BD" class="hash-link" aria-label="2. Copilot Agents 新功能的直接連結" title="2. Copilot Agents 新功能的直接連結" translate="no">​</a></h3>
<ul>
<li class=""><strong>工作區理解（Workspace Contextualization）</strong>：理解整個專案結構，非僅單一檔案。</li>
<li class=""><strong>任務導向操作（Task-oriented Autonomy）</strong>：透過自然語言指令，Agent 可主動找出需要修改的檔案與程式邏輯。</li>
<li class=""><strong>互動式任務視窗（Copilot Workspace）</strong>：能與你共同規劃、分解、執行修改。</li>
<li class=""><strong>自動產生 Pull Request</strong>：Agent 能根據需求描述，自動建立符合需求的 PR 變更建議。</li>
<li class=""><strong>支援自然語言任務指令</strong>：如 “Add pagination to the user list page” 或 “Refactor login logic to support OAuth”。</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-適用場景">3. 適用場景<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#3-%E9%81%A9%E7%94%A8%E5%A0%B4%E6%99%AF" class="hash-link" aria-label="3. 適用場景的直接連結" title="3. 適用場景的直接連結" translate="no">​</a></h3>
<ul>
<li class="">新功能導入（快速建構雛型）</li>
<li class="">現有程式碼重構與除錯</li>
<li class="">快速理解大型專案結構</li>
<li class="">撰寫測試與文件</li>
<li class="">協作開發與程式碼審查輔助</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用條件與環境設置">使用條件與環境設置<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E4%BD%BF%E7%94%A8%E6%A2%9D%E4%BB%B6%E8%88%87%E7%92%B0%E5%A2%83%E8%A8%AD%E7%BD%AE" class="hash-link" aria-label="使用條件與環境設置的直接連結" title="使用條件與環境設置的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>基本需求</strong></p>
<ul>
<li class="">GitHub 帳號</li>
<li class="">VS Code 或 GitHub 官方網站（Copilot Workspace）</li>
<li class="">GitHub Copilot 訂閱（個人 / 企業方案）</li>
</ul>
</li>
<li class="">
<p><strong>啟用方式</strong></p>
<ul>
<li class="">在 VS Code Marketplace 安裝「GitHub Copilot」擴充功能</li>
<li class="">若已啟用 Agent 功能（目前逐步開放），將會在 Copilot 介面看到 Workspace 或 Chat 模式</li>
</ul>
</li>
<li class="">
<p><strong>使用 Copilot Workspace</strong>（預設僅企業帳戶開放測試）</p>
<ul>
<li class="">登入 GitHub，進入某個 repo</li>
<li class="">點擊 <code>Copilot</code> 按鈕開啟 Workspace</li>
<li class="">輸入任務描述，例如：<code>Convert all date logic to use UTC</code></li>
<li class="">Copilot 會分析整個程式碼庫，自動生成變更建議與 Pull Request</li>
</ul>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際範例">實際範例<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E5%AF%A6%E9%9A%9B%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際範例的直接連結" title="實際範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例一註解驅動補全">範例一：註解驅動補全<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%80%E8%A8%BB%E8%A7%A3%E9%A9%85%E5%8B%95%E8%A3%9C%E5%85%A8" class="hash-link" aria-label="範例一：註解驅動補全的直接連結" title="範例一：註解驅動補全的直接連結" translate="no">​</a></h3>
<p>輸入：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)">// 寫一個可以將字串反轉的函式</span><br></span></code></pre></div></div>
<p>Copilot 自動補全：</p>
<div class="language-javascript codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-javascript codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">reverseString</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token parameter">str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token keyword control-flow" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> str</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">split</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">reverse</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token method function property-access" style="color:rgb(80, 250, 123)">join</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例二copilot-chat-對話式問答">範例二：Copilot Chat 對話式問答<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%BA%8Ccopilot-chat-%E5%B0%8D%E8%A9%B1%E5%BC%8F%E5%95%8F%E7%AD%94" class="hash-link" aria-label="範例二：Copilot Chat 對話式問答的直接連結" title="範例二：Copilot Chat 對話式問答的直接連結" translate="no">​</a></h3>
<p>使用快捷鍵 <code>Cmd+I</code> 或從 VS Code Copilot Chat 面板提問：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">請幫我解釋這段程式碼的作用：parseDate(input, 'YYYY-MM-DD')</span><br></span></code></pre></div></div>
<p>Copilot 回應：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">這段程式碼會將字串 input 解析為特定格式的日期物件，格式為 'YYYY-MM-DD'，可能使用的是 dayjs 或 moment 函式庫。</span><br></span></code></pre></div></div>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例三copilot-agent-自動任務執行需-workspace-模式">範例三：Copilot Agent 自動任務執行（需 Workspace 模式）<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%89copilot-agent-%E8%87%AA%E5%8B%95%E4%BB%BB%E5%8B%99%E5%9F%B7%E8%A1%8C%E9%9C%80-workspace-%E6%A8%A1%E5%BC%8F" class="hash-link" aria-label="範例三：Copilot Agent 自動任務執行（需 Workspace 模式）的直接連結" title="範例三：Copilot Agent 自動任務執行（需 Workspace 模式）的直接連結" translate="no">​</a></h3>
<p>任務描述：</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Refactor login controller to support Google OAuth</span><br></span></code></pre></div></div>
<p>流程：</p>
<ol>
<li class="">Copilot 會分析 <code>controllers/login.js</code> 或相似檔案</li>
<li class="">自動識別現有的登入邏輯</li>
<li class="">插入 Google OAuth 處理邏輯（可能使用 passport.js 或其他第三方庫）</li>
<li class="">產生變更摘要與建議 Pull Request</li>
<li class="">開發者審閱後即可 merge</li>
</ol>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例四整合單元測試建議">範例四：整合單元測試建議<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E7%AF%84%E4%BE%8B%E5%9B%9B%E6%95%B4%E5%90%88%E5%96%AE%E5%85%83%E6%B8%AC%E8%A9%A6%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="範例四：整合單元測試建議的直接連結" title="範例四：整合單元測試建議的直接連結" translate="no">​</a></h3>
<p>輸入：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">is_palindrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">return</span><span class="token plain"> s </span><span class="token operator">==</span><span class="token plain"> s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token operator">-</span><span class="token number">1</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p>在測試檔案中輸入：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">def</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">test_is_palindrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><br></span></code></pre></div></div>
<p>Copilot 自動補全：</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">assert</span><span class="token plain"> is_palindrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"racecar"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token boolean">True</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">assert</span><span class="token plain"> is_palindrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"hello"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token boolean">False</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">assert</span><span class="token plain"> is_palindrome</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">""</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token operator">==</span><span class="token plain"> </span><span class="token boolean">True</span><br></span></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="建議與最佳實踐">建議與最佳實踐<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E5%BB%BA%E8%AD%B0%E8%88%87%E6%9C%80%E4%BD%B3%E5%AF%A6%E8%B8%90" class="hash-link" aria-label="建議與最佳實踐的直接連結" title="建議與最佳實踐的直接連結" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>善用自然語言描述</strong>
Copilot Agent 具有語意理解能力，越具體的任務描述，越能產出正確結果。</p>
</li>
<li class="">
<p><strong>審核與測試不可省略</strong>
AI 輔助雖強，但仍可能產生誤邏輯或不符合風格的程式碼，請務必加入測試與審查。</p>
</li>
<li class="">
<p><strong>適合用於重複與樣板任務</strong>
例如 CRUD、測試、自動 refactor、邏輯轉換等。</p>
</li>
<li class="">
<p><strong>搭配 GitHub Actions 使用更強大</strong>
可結合 CI/CD 流程，自動執行 Copilot Agent 產生的 PR。</p>
</li>
</ol>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>GitHub Copilot 已從單純的程式補全工具，蛻變為具備理解上下文、可互動操作的智慧開發代理人。透過 Copilot Agent，你不僅能提升撰寫速度，還能更高層次地規劃、修改與維護專案。</p>
<p>對個人開發者而言，Copilot 是每日開發的好助手；對團隊而言，Copilot Agent 更像是一位全天候不下班的開發實習生，協助你管理技術債、維護程式庫、強化開發工作流。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-github-copilot-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://github.com/doggy8088/github-copilot-configs" target="_blank" rel="noopener noreferrer" class="">最佳 GitHub Copilot 設定</a></li>
<li class=""><a href="https://medium.com/@cct0201/%E5%BE%9E-%E5%AF%AB%E7%A8%8B%E5%BC%8F-%E5%88%B0-%E8%88%87-ai-%E5%85%B1%E8%88%9E-%E6%88%91%E5%9C%A8%E5%85%AC%E5%8F%B8%E6%8E%A8%E5%8B%95-vibe-coding-%E7%9A%84%E7%B6%93%E9%A9%97%E5%88%86%E4%BA%AB-015e28909290" target="_blank" rel="noopener noreferrer" class="">從「寫程式」到「與 AI 共舞」── 我在公司推動 Vibe Coding 的經驗分享</a></li>
<li class=""><a href="https://gitmind.com/tw/best-vibe-coding-tools-2025.html" target="_blank" rel="noopener noreferrer" class="">2025 年最強推薦 Vibe Coding 工具一次看懂</a></li>
</ol>]]></content:encoded>
            <category>GitHub</category>
            <category>GitHub Copilot</category>
            <category>Copilot</category>
            <category>Copilot Agent</category>
            <category>編輯器</category>
        </item>
        <item>
            <title><![CDATA[n8n 入門教學筆記 | 學習筆記]]></title>
            <link>https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial</link>
            <guid>https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial</guid>
            <pubDate>Fri, 20 Dec 2024 11:33:41 GMT</pubDate>
            <description><![CDATA[前言]]></description>
            <content:encoded><![CDATA[<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="前言">前言<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E5%89%8D%E8%A8%80" class="hash-link" aria-label="前言的直接連結" title="前言的直接連結" translate="no">​</a></h2>
<p>在現代軟體開發與數位工作流程中，「自動化」已成為提升效率的關鍵。無論是處理 API 整合、資料同步、通知發送，或是各種無需人工介入的重複性工作，選擇一套靈活的工作流程自動化工具是必要的。</p>
<p><strong>n8n（pronounced "n-eight-n"）</strong> 是一個強大的開源自動化工具，具備類似 Zapier 的拖拉式流程設計介面，但更強調<strong>可自建、開源、自主掌控資料與程式彈性</strong>。它支援超過 400 個內建整合（如 Slack、Google Sheets、Notion、MySQL、HTTP API 等），同時允許用戶透過 JavaScript 編寫邏輯節點，打造彈性極高的自動化流程。</p>
<p>相較於其他自動化工具，n8n 的最大優勢在於：「你可以在本機或伺服器上自行部署，資料完全由你掌控。」這點對於重視隱私或希望建立企業內部自動化平台的團隊尤其重要。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="重點摘要">重點摘要<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E9%87%8D%E9%BB%9E%E6%91%98%E8%A6%81" class="hash-link" aria-label="重點摘要的直接連結" title="重點摘要的直接連結" translate="no">​</a></h2>
<ul>
<li class="">
<p><strong>n8n 是什麼？</strong></p>
<ul>
<li class="">一個開源、自託管、可視化的工作流程自動化工具。</li>
<li class="">名稱源自 “Node + Node = Workflow”，以節點（Node）為單位建構流程。</li>
</ul>
</li>
<li class="">
<p><strong>主要特色</strong></p>
<ul>
<li class="">開源、自託管、資料不外流。</li>
<li class="">拖放式流程設計器，門檻低但彈性高。</li>
<li class="">可撰寫 JavaScript 處理邏輯。</li>
<li class="">支援 webhook、排程、event-based 工作流程。</li>
<li class="">擁有超過 400 個現成整合節點（Google、Slack、GitHub、HTTP Request 等）。</li>
</ul>
</li>
<li class="">
<p><strong>使用場景</strong></p>
<ul>
<li class="">第三方 API 整合與自動同步。</li>
<li class="">資料轉換與清洗（ETL）。</li>
<li class="">表單提交後自動寄信／填寫 Google Sheets。</li>
<li class="">發布內容到社群平台。</li>
<li class="">團隊協作通知（如 Jira、Slack 整合）。</li>
<li class="">自動監控 RSS、新留言、社群評論等。</li>
</ul>
</li>
<li class="">
<p><strong>部署方式</strong></p>
<ul>
<li class="">支援 Docker、本地安裝、雲端託管（n8n.cloud）、Render、Railway 等平台。</li>
<li class="">可選擇免費版本或企業進階授權。</li>
</ul>
</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="安裝與啟用方式">安裝與啟用方式<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E5%AE%89%E8%A3%9D%E8%88%87%E5%95%9F%E7%94%A8%E6%96%B9%E5%BC%8F" class="hash-link" aria-label="安裝與啟用方式的直接連結" title="安裝與啟用方式的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-使用-docker-部署推薦">1. 使用 Docker 部署（推薦）<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#1-%E4%BD%BF%E7%94%A8-docker-%E9%83%A8%E7%BD%B2%E6%8E%A8%E8%96%A6" class="hash-link" aria-label="1. 使用 Docker 部署（推薦）的直接連結" title="1. 使用 Docker 部署（推薦）的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">docker run -it --rm \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  -p 5678:5678 \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  -v ~/.n8n:/home/node/.n8n \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  n8nio/n8n</span><br></span></code></pre></div></div>
<p>開啟瀏覽器進入 <a href="http://localhost:5678/" target="_blank" rel="noopener noreferrer" class="">http://localhost:5678</a> 即可開始使用。</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-使用-npm-安裝">2. 使用 npm 安裝<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#2-%E4%BD%BF%E7%94%A8-npm-%E5%AE%89%E8%A3%9D" class="hash-link" aria-label="2. 使用 npm 安裝的直接連結" title="2. 使用 npm 安裝的直接連結" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">npm install n8n -g</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">n8n</span><br></span></code></pre></div></div>
<p>同樣可透過 <a href="http://localhost:5678/" target="_blank" rel="noopener noreferrer" class="">http://localhost:5678</a> 開啟界面。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="使用介面簡介">使用介面簡介<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E4%BD%BF%E7%94%A8%E4%BB%8B%E9%9D%A2%E7%B0%A1%E4%BB%8B" class="hash-link" aria-label="使用介面簡介的直接連結" title="使用介面簡介的直接連結" translate="no">​</a></h2>
<p>n8n 的主界面由以下幾個部分組成：</p>
<ul>
<li class=""><strong>Canvas（畫布）</strong>：可拖拉節點設計流程。</li>
<li class=""><strong>左側工具列</strong>：內建各種整合節點分類（Webhook、HTTP Request、Email 等）。</li>
<li class=""><strong>節點屬性面板</strong>：選取節點後可編輯其參數設定。</li>
<li class=""><strong>執行與測試工具</strong>：可以逐步執行流程、查看輸入與輸出資料。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實際應用範例">實際應用範例<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E5%AF%A6%E9%9A%9B%E6%87%89%E7%94%A8%E7%AF%84%E4%BE%8B" class="hash-link" aria-label="實際應用範例的直接連結" title="實際應用範例的直接連結" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例一接收-webhook-並寄送-slack-通知">範例一：接收 Webhook 並寄送 Slack 通知<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%80%E6%8E%A5%E6%94%B6-webhook-%E4%B8%A6%E5%AF%84%E9%80%81-slack-%E9%80%9A%E7%9F%A5" class="hash-link" aria-label="範例一：接收 Webhook 並寄送 Slack 通知的直接連結" title="範例一：接收 Webhook 並寄送 Slack 通知的直接連結" translate="no">​</a></h3>
<p><strong>目標流程</strong>：當收到 Webhook（如表單送出），自動發送一則 Slack 通知。</p>
<ol>
<li class="">
<p>新增節點 <code>Webhook</code>：</p>
<ul>
<li class="">設定 HTTP 方法為 <code>POST</code></li>
<li class="">指定路徑為 <code>/contact</code></li>
</ul>
</li>
<li class="">
<p>新增節點 <code>Slack</code>：</p>
<ul>
<li class="">動作選擇 <code>Send Message</code></li>
<li class="">填入 Slack OAuth 認證與頻道資訊</li>
<li class="">訊息可使用 <code>{{$json["name"]}}</code> 這類變數取得 webhook 傳入資料</li>
</ul>
</li>
<li class="">
<p>將 <code>Webhook</code> → <code>Slack</code> 連接起來。</p>
</li>
<li class="">
<p>啟用工作流程，對 <code>/webhook/contact</code> 發送 POST 請求，即會收到通知。</p>
</li>
</ol>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例二每天早上-9-點將-mysql-資料匯出至-google-sheets">範例二：每天早上 9 點將 MySQL 資料匯出至 Google Sheets<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%BA%8C%E6%AF%8F%E5%A4%A9%E6%97%A9%E4%B8%8A-9-%E9%BB%9E%E5%B0%87-mysql-%E8%B3%87%E6%96%99%E5%8C%AF%E5%87%BA%E8%87%B3-google-sheets" class="hash-link" aria-label="範例二：每天早上 9 點將 MySQL 資料匯出至 Google Sheets的直接連結" title="範例二：每天早上 9 點將 MySQL 資料匯出至 Google Sheets的直接連結" translate="no">​</a></h3>
<ol>
<li class="">
<p>使用 <code>Cron</code> 節點設定時間為每天早上 9 點。</p>
</li>
<li class="">
<p>使用 <code>MySQL</code> 節點撈出指定資料表的內容。</p>
</li>
<li class="">
<p>使用 <code>Google Sheets</code> 節點將資料新增至指定試算表。</p>
</li>
<li class="">
<p>可加上一個 <code>Function</code> 節點清洗或轉換資料格式。</p>
</li>
</ol>
<p>這樣可輕鬆建立每日自動報表流程，無需撰寫一行 shell script 或排程任務。</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="範例三api-整合流程串接-chatgpt-回覆留言">範例三：API 整合流程（串接 ChatGPT 回覆留言）<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E7%AF%84%E4%BE%8B%E4%B8%89api-%E6%95%B4%E5%90%88%E6%B5%81%E7%A8%8B%E4%B8%B2%E6%8E%A5-chatgpt-%E5%9B%9E%E8%A6%86%E7%95%99%E8%A8%80" class="hash-link" aria-label="範例三：API 整合流程（串接 ChatGPT 回覆留言）的直接連結" title="範例三：API 整合流程（串接 ChatGPT 回覆留言）的直接連結" translate="no">​</a></h3>
<ol>
<li class="">
<p>使用 <code>Webhook</code> 節點接收前端留言內容。</p>
</li>
<li class="">
<p>使用 <code>OpenAI</code> 節點（或 HTTP Request + 自行送出 API 請求）傳送留言給 ChatGPT 並取得回覆。</p>
</li>
<li class="">
<p>使用 <code>Send Email</code> 或 <code>Telegram</code> 節點自動回覆用戶。</p>
</li>
</ol>
<p>n8n 支援 JSON 處理與條件邏輯節點，讓你可根據留言內容分類處理或轉送不同部門。</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="實用建議">實用建議<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E5%AF%A6%E7%94%A8%E5%BB%BA%E8%AD%B0" class="hash-link" aria-label="實用建議的直接連結" title="實用建議的直接連結" translate="no">​</a></h2>
<ul>
<li class="">建議搭配 <strong>版本控制（匯出 workflow JSON）</strong>，便於多人協作與備份。</li>
<li class="">若要部署至生產環境，建議設定密碼驗證與 HTTPS 保護。</li>
<li class="">可以將變數或 API key 設定為「環境變數」集中管理，提升安全性與可維護性。</li>
<li class="">利用 <code>Function</code> 與 <code>Set</code> 節點進行複雜邏輯處理與欄位映射。</li>
<li class="">若有即時性需求，可使用 webhook + queue-based 設計方式，避免封鎖主流程。</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="總結">總結<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E7%B8%BD%E7%B5%90" class="hash-link" aria-label="總結的直接連結" title="總結的直接連結" translate="no">​</a></h2>
<p>n8n 是一款功能強大且靈活的開源工作流程自動化工具，不僅適合開發者，也適合營運、行銷、客服等跨部門自動化需求。透過拖拉節點的方式，任何人都可以建立自動化流程，取代繁瑣重複的手動操作。</p>
<p>與 Zapier、Make 等 SaaS 工具不同，n8n 提供了極高的可控性與可擴充性。無論是 API 整合、資料清洗、事件觸發，還是每日任務排程，n8n 都能成為你構建智慧工作流程的得力助手。</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="參考文件">參考文件<a href="https://tech.kdchang.com/blog/learning-notes-n8n-intro-tutorial#%E5%8F%83%E8%80%83%E6%96%87%E4%BB%B6" class="hash-link" aria-label="參考文件的直接連結" title="參考文件的直接連結" translate="no">​</a></h2>
<ol>
<li class=""><a href="https://adbest.com.tw/blog/what-is-n8n/" target="_blank" rel="noopener noreferrer" class="">n8n 新手中文教學：6 步安裝、7 步驟部署第一支工作流！費用？</a></li>
</ol>]]></content:encoded>
            <category>n8n</category>
            <category>VS Code</category>
            <category>工作流程自動化工具</category>
        </item>
    </channel>
</rss>