<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Matt Wang&#39;s Blog</title>
  
  <subtitle>技術分享 × 程式開發 × 創意實踐</subtitle>
  <link href="https://www.mattwang.xyz/atom.xml" rel="self"/>
  
  <link href="https://www.mattwang.xyz/"/>
  <updated>2026-02-06T09:18:11.640Z</updated>
  <id>https://www.mattwang.xyz/</id>
  
  <author>
    <name>Matt Wang</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>歡迎來到 Matt Wang 的技術部落格</title>
    <link href="https://www.mattwang.xyz/2026/02/06/welcome-to-my-blog/"/>
    <id>https://www.mattwang.xyz/2026/02/06/welcome-to-my-blog/</id>
    <published>2026-02-06T04:00:00.000Z</published>
    <updated>2026-02-06T09:18:11.640Z</updated>
    
    <content type="html"><![CDATA[<h2 id="👋-歡迎！"><a href="#👋-歡迎！" class="headerlink" title="👋 歡迎！"></a>👋 歡迎！</h2><p>歡迎來到我的技術部落格！這裡將記錄我在軟體開發、專案實踐過程中的技術見解、學習心得與實戰經驗。</p><span id="more"></span><h2 id="✨-關於這個部落格"><a href="#✨-關於這個部落格" class="headerlink" title="✨ 關於這個部落格"></a>✨ 關於這個部落格</h2><p>這個部落格是我個人網站 <a href="https://www.mattwang.xyz/">mattwang.xyz</a> 的一部分，使用 <strong>Hexo</strong> 靜態網站生成器結合客製化的 <strong>Tailwind CSS</strong> 主題打造而成。整體設計風格與主站保持一致，提供統一的使用者體驗。</p><h3 id="技術架構"><a href="#技術架構" class="headerlink" title="技術架構"></a>技術架構</h3><ul><li><strong>主站框架</strong>：React + TypeScript + Vite</li><li><strong>部落格引擎</strong>：Hexo</li><li><strong>樣式系統</strong>：Tailwind CSS（客製化主題）</li><li><strong>部署平台</strong>：AWS Amplify</li><li><strong>留言系統</strong>：Giscus（基於 GitHub Discussions）</li></ul><h2 id="📝-部落格內容規劃"><a href="#📝-部落格內容規劃" class="headerlink" title="📝 部落格內容規劃"></a>📝 部落格內容規劃</h2><p>未來我會在這裡分享：</p><h3 id="技術文章"><a href="#技術文章" class="headerlink" title="技術文章"></a>技術文章</h3><ul><li><strong>Web 開發</strong>：React、Vue.js、TypeScript 等前端技術</li><li><strong>後端開發</strong>：Node.js、Laravel、資料庫設計</li><li><strong>雲端服務</strong>：AWS 實戰經驗、Serverless 架構</li><li><strong>開發工具</strong>：CI&#x2F;CD、Docker、Git 工作流程</li></ul><h3 id="專案經驗"><a href="#專案經驗" class="headerlink" title="專案經驗"></a>專案經驗</h3><ul><li>實際專案的架構設計與決策思考</li><li>技術選型的考量與權衡</li><li>遇到的挑戰與解決方案</li></ul><h3 id="學習筆記"><a href="#學習筆記" class="headerlink" title="學習筆記"></a>學習筆記</h3><ul><li>新技術的學習心得</li><li>程式設計書籍閱讀筆記</li><li>線上課程與技術文件整理</li></ul><h2 id="🎯-建立這個部落格的目標"><a href="#🎯-建立這個部落格的目標" class="headerlink" title="🎯 建立這個部落格的目標"></a>🎯 建立這個部落格的目標</h2><ol><li><strong>記錄成長軌跡</strong>：透過寫作梳理技術知識，建立個人知識庫</li><li><strong>分享實戰經驗</strong>：將工作中的實際經驗轉化為可複製的知識</li><li><strong>持續學習</strong>：透過輸出倒逼輸入，保持學習動力</li><li><strong>技術交流</strong>：與其他開發者交流討論，共同成長</li></ol><h2 id="💬-互動與交流"><a href="#💬-互動與交流" class="headerlink" title="💬 互動與交流"></a>💬 互動與交流</h2><p>部落格整合了 <strong>Giscus</strong> 留言系統，所有留言都會儲存在 GitHub Discussions 中。歡迎在文章下方留言討論，分享你的想法與經驗！</p><p>你也可以透過以下方式找到我：</p><ul><li><strong>個人網站</strong>：<a href="https://www.mattwang.xyz/">https://www.mattwang.xyz</a></li><li><strong>GitHub</strong>：<a href="https://github.com/matt7137">@matt7137</a></li><li><strong>Email</strong>：透過網站聯絡表單</li></ul><h2 id="🚀-下一步"><a href="#🚀-下一步" class="headerlink" title="🚀 下一步"></a>🚀 下一步</h2><p>接下來幾週，我計劃撰寫以下主題的文章：</p><ul><li><input disabled="" type="checkbox"> 如何整合 Hexo 部落格到 React 專案中</li><li><input disabled="" type="checkbox"> Tailwind CSS 客製化主題開發實戰</li><li><input disabled="" type="checkbox"> AWS Amplify 部署全端專案完整流程</li><li><input disabled="" type="checkbox"> React 18 + TypeScript 最佳實踐</li></ul><p>敬請期待！如果你有感興趣的主題，歡迎在下方留言告訴我。</p><hr><p>感謝你的閱讀，讓我們一起在技術的道路上持續前進！🚀</p>]]></content>
    
    
    <summary type="html">這是我的技術部落格首篇文章，分享關於這個部落格的建立過程與未來規劃。</summary>
    
    
    
    <category term="公告" scheme="https://www.mattwang.xyz/categories/%E5%85%AC%E5%91%8A/"/>
    
    
    <category term="部落格" scheme="https://www.mattwang.xyz/tags/%E9%83%A8%E8%90%BD%E6%A0%BC/"/>
    
    <category term="記錄" scheme="https://www.mattwang.xyz/tags/%E8%A8%98%E9%8C%84/"/>
    
  </entry>
  
  <entry>
    <title>React 18 新特性深度解析</title>
    <link href="https://www.mattwang.xyz/2026/02/05/react-18-new-features/"/>
    <id>https://www.mattwang.xyz/2026/02/05/react-18-new-features/</id>
    <published>2026-02-05T02:30:00.000Z</published>
    <updated>2026-02-06T09:18:11.640Z</updated>
    
    <content type="html"><![CDATA[<h2 id="🎯-前言"><a href="#🎯-前言" class="headerlink" title="🎯 前言"></a>🎯 前言</h2><p>React 18 是 React 團隊發布的重大更新版本，引入了許多改進效能和開發體驗的新特性。本文將深入探討這些新特性的原理與應用場景。</p><span id="more"></span><h2 id="✨-核心新特性"><a href="#✨-核心新特性" class="headerlink" title="✨ 核心新特性"></a>✨ 核心新特性</h2><h3 id="1-Concurrent-Rendering（並發渲染）"><a href="#1-Concurrent-Rendering（並發渲染）" class="headerlink" title="1. Concurrent Rendering（並發渲染）"></a>1. Concurrent Rendering（並發渲染）</h3><p>React 18 最重要的更新就是引入了並發渲染機制。這使得 React 能夠同時處理多個版本的 UI，並根據優先級中斷和恢復渲染工作。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// React 18 之前</span></span><br><span class="line"><span class="keyword">import</span> &#123; render &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">App</span> /&gt;</span></span>, <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// React 18</span></span><br><span class="line"><span class="keyword">import</span> &#123; createRoot &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom/client&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> root = <span class="title function_">createRoot</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&#x27;root&#x27;</span>));</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">App</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><h3 id="2-Automatic-Batching（自動批次處理）"><a href="#2-Automatic-Batching（自動批次處理）" class="headerlink" title="2. Automatic Batching（自動批次處理）"></a>2. Automatic Batching（自動批次處理）</h3><p>在 React 18 之前，只有在 React 事件處理器中的狀態更新會被批次處理。現在，所有狀態更新都會自動批次處理，包括 Promise、setTimeout、原生事件處理器等。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// React 18 會自動批次處理這些更新</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">fetch</span>(<span class="string">&#x27;/api/data&#x27;</span>).<span class="title function_">then</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(<span class="function"><span class="params">c</span> =&gt;</span> c + <span class="number">1</span>);  <span class="comment">// 不會立即重新渲染</span></span><br><span class="line">    <span class="title function_">setFlag</span>(<span class="function"><span class="params">f</span> =&gt;</span> !f);      <span class="comment">// 不會立即重新渲染</span></span><br><span class="line">    <span class="comment">// React 只會在最後重新渲染一次</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果需要立即渲染，可以使用 <code>flushSync</code>：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; flushSync &#125; <span class="keyword">from</span> <span class="string">&#x27;react-dom&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">handleClick</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">flushSync</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(<span class="function"><span class="params">c</span> =&gt;</span> c + <span class="number">1</span>);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">// React 此時已經更新 DOM</span></span><br><span class="line">  <span class="title function_">flushSync</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setFlag</span>(<span class="function"><span class="params">f</span> =&gt;</span> !f);</span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="comment">// React 再次更新 DOM</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Transitions（過渡）"><a href="#3-Transitions（過渡）" class="headerlink" title="3. Transitions（過渡）"></a>3. Transitions（過渡）</h3><p>Transitions 讓你可以標記某些更新為「非緊急」，React 會優先處理其他更新。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useTransition, useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SearchResults</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [isPending, startTransition] = <span class="title function_">useTransition</span>();</span><br><span class="line">  <span class="keyword">const</span> [query, setQuery] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> [results, setResults] = <span class="title function_">useState</span>([]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">function</span> <span class="title function_">handleChange</span>(<span class="params">e</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> value = e.<span class="property">target</span>.<span class="property">value</span>;</span><br><span class="line">    <span class="title function_">setQuery</span>(value);  <span class="comment">// 緊急更新：顯示輸入</span></span><br><span class="line"></span><br><span class="line">    <span class="title function_">startTransition</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 非緊急更新：顯示結果</span></span><br><span class="line">      <span class="title function_">setResults</span>(<span class="title function_">filterResults</span>(value));</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">input</span> <span class="attr">value</span>=<span class="string">&#123;query&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;handleChange&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;isPending &amp;&amp; <span class="tag">&lt;<span class="name">Spinner</span> /&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ResultsList</span> <span class="attr">results</span>=<span class="string">&#123;results&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-Suspense-改進"><a href="#4-Suspense-改進" class="headerlink" title="4. Suspense 改進"></a>4. Suspense 改進</h3><p>React 18 改進了 Suspense 的行為，使其在伺服器端渲染和並發渲染中更加穩定。</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Suspense</span>, lazy &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Comments</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&#x27;./Comments&#x27;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Post</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">PostContent</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Suspense</span> <span class="attr">fallback</span>=<span class="string">&#123;</span>&lt;<span class="attr">Spinner</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Comments</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Suspense</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🚀-實際應用場景"><a href="#🚀-實際應用場景" class="headerlink" title="🚀 實際應用場景"></a>🚀 實際應用場景</h2><h3 id="場景-1：搜尋建議"><a href="#場景-1：搜尋建議" class="headerlink" title="場景 1：搜尋建議"></a>場景 1：搜尋建議</h3><p>使用 Transitions 處理搜尋輸入和結果更新：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">SearchBox</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [isPending, startTransition] = <span class="title function_">useTransition</span>();</span><br><span class="line">  <span class="keyword">const</span> [input, setInput] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">  <span class="keyword">const</span> [suggestions, setSuggestions] = <span class="title function_">useState</span>([]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleChange</span> = (<span class="params">e</span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setInput</span>(e.<span class="property">target</span>.<span class="property">value</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="title function_">startTransition</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 這個更新不會阻塞輸入</span></span><br><span class="line">      <span class="keyword">const</span> filtered = <span class="title function_">filterSuggestions</span>(e.<span class="property">target</span>.<span class="property">value</span>);</span><br><span class="line">      <span class="title function_">setSuggestions</span>(filtered);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">input</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">value</span>=<span class="string">&#123;input&#125;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onChange</span>=<span class="string">&#123;handleChange&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">className</span>=<span class="string">&#123;isPending</span> ? &#x27;<span class="attr">loading</span>&#x27; <span class="attr">:</span> &#x27;&#x27;&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">SuggestionList</span> <span class="attr">items</span>=<span class="string">&#123;suggestions&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="場景-2：資料載入"><a href="#場景-2：資料載入" class="headerlink" title="場景 2：資料載入"></a>場景 2：資料載入</h3><p>結合 Suspense 實現優雅的載入狀態：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ProfilePage</span>(<span class="params">&#123; userId &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Suspense</span> <span class="attr">fallback</span>=<span class="string">&#123;</span>&lt;<span class="attr">ProfileSkeleton</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ProfileDetails</span> <span class="attr">userId</span>=<span class="string">&#123;userId&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Suspense</span> <span class="attr">fallback</span>=<span class="string">&#123;</span>&lt;<span class="attr">PostsSkeleton</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">ProfilePosts</span> <span class="attr">userId</span>=<span class="string">&#123;userId&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Suspense</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Suspense</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="📊-效能影響"><a href="#📊-效能影響" class="headerlink" title="📊 效能影響"></a>📊 效能影響</h2><p>根據 React 團隊的測試和社群反饋：</p><ul><li><strong>Automatic Batching</strong>：減少 50% 以上的不必要渲染</li><li><strong>Concurrent Rendering</strong>：改善大型列表和複雜 UI 的互動響應速度</li><li><strong>Transitions</strong>：讓使用者輸入更加流暢，即使在處理大量資料時</li></ul><h2 id="⚠️-遷移注意事項"><a href="#⚠️-遷移注意事項" class="headerlink" title="⚠️ 遷移注意事項"></a>⚠️ 遷移注意事項</h2><h3 id="需要更新的地方"><a href="#需要更新的地方" class="headerlink" title="需要更新的地方"></a>需要更新的地方</h3><ol><li>使用新的 Root API</li><li>檢查是否依賴同步渲染的邏輯</li><li>測試第三方函式庫的相容性</li></ol><h3 id="Strict-Mode-的變化"><a href="#Strict-Mode-的變化" class="headerlink" title="Strict Mode 的變化"></a>Strict Mode 的變化</h3><p>React 18 的 Strict Mode 會在開發環境中雙重調用某些函數，幫助發現副作用問題：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 這些函數會被調用兩次（僅開發環境）</span></span><br><span class="line">- 函數元件本體</span><br><span class="line">- useState、useReducer、useMemo 的初始化函數</span><br><span class="line">- <span class="title class_">Class</span> 元件的 constructor、render 等</span><br></pre></td></tr></table></figure><h2 id="🎓-最佳實踐"><a href="#🎓-最佳實踐" class="headerlink" title="🎓 最佳實踐"></a>🎓 最佳實踐</h2><ol><li><strong>優先使用 Transitions</strong>：對於非緊急的 UI 更新，使用 <code>useTransition</code> 或 <code>useDeferredValue</code></li><li><strong>合理使用 Suspense</strong>：為不同的 UI 區塊設定適當的 Suspense 邊界</li><li><strong>避免過度使用 flushSync</strong>：只在必要時強制同步更新</li><li><strong>測試並發行為</strong>：確保元件在並發模式下正常運作</li></ol><h2 id="📚-延伸閱讀"><a href="#📚-延伸閱讀" class="headerlink" title="📚 延伸閱讀"></a>📚 延伸閱讀</h2><ul><li><a href="https://react.dev/">React 18 官方文件</a></li><li><a href="https://react.dev/blog/2022/03/29/react-v18">Concurrent Features in React 18</a></li><li><a href="https://react.dev/blog/2022/03/08/react-18-upgrade-guide">React 18 升級指南</a></li></ul><h2 id="💭-結語"><a href="#💭-結語" class="headerlink" title="💭 結語"></a>💭 結語</h2><p>React 18 的新特性為我們提供了更好的工具來建構高效能的使用者介面。雖然遷移需要一些工作，但這些改進絕對值得投資。</p><p>你的專案已經升級到 React 18 了嗎？在使用新特性的過程中有遇到什麼問題嗎？歡迎在下方留言分享你的經驗！</p><hr><p><strong>標籤</strong>：#React #Frontend #WebDevelopment #JavaScript</p>]]></content>
    
    
    <summary type="html">深入探討 React 18 的重要新特性，包括 Concurrent Rendering、Automatic Batching、Transitions 等，並提供實際應用場景與程式碼範例。</summary>
    
    
    
    <category term="前端開發" scheme="https://www.mattwang.xyz/categories/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/"/>
    
    <category term="React" scheme="https://www.mattwang.xyz/categories/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/React/"/>
    
    
    <category term="React" scheme="https://www.mattwang.xyz/tags/React/"/>
    
    <category term="JavaScript" scheme="https://www.mattwang.xyz/tags/JavaScript/"/>
    
    <category term="前端框架" scheme="https://www.mattwang.xyz/tags/%E5%89%8D%E7%AB%AF%E6%A1%86%E6%9E%B6/"/>
    
  </entry>
  
  <entry>
    <title>Tailwind CSS 深色模式完整實踐指南</title>
    <link href="https://www.mattwang.xyz/2026/02/04/tailwind-dark-mode-guide/"/>
    <id>https://www.mattwang.xyz/2026/02/04/tailwind-dark-mode-guide/</id>
    <published>2026-02-04T06:20:00.000Z</published>
    <updated>2026-02-06T09:18:11.640Z</updated>
    
    <content type="html"><![CDATA[<h2 id="🌙-為什麼需要深色模式？"><a href="#🌙-為什麼需要深色模式？" class="headerlink" title="🌙 為什麼需要深色模式？"></a>🌙 為什麼需要深色模式？</h2><p>深色模式不僅僅是一個美觀的功能，它還能：</p><ul><li>減少眼睛疲勞，特別是在低光環境下</li><li>延長 OLED 螢幕裝置的電池壽命</li><li>提升使用者體驗和滿意度</li><li>展現應用的專業性和現代感</li></ul><span id="more"></span><h2 id="🎨-Tailwind-CSS-深色模式策略"><a href="#🎨-Tailwind-CSS-深色模式策略" class="headerlink" title="🎨 Tailwind CSS 深色模式策略"></a>🎨 Tailwind CSS 深色模式策略</h2><p>Tailwind CSS 提供三種深色模式策略：</p><h3 id="1-Media-Query-策略"><a href="#1-Media-Query-策略" class="headerlink" title="1. Media Query 策略"></a>1. Media Query 策略</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">darkMode</span>: <span class="string">&#x27;media&#x27;</span>, <span class="comment">// 根據系統偏好</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-Class-策略（推薦）"><a href="#2-Class-策略（推薦）" class="headerlink" title="2. Class 策略（推薦）"></a>2. Class 策略（推薦）</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">darkMode</span>: <span class="string">&#x27;class&#x27;</span>, <span class="comment">// 手動控制</span></span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-Selector-策略"><a href="#3-Selector-策略" class="headerlink" title="3. Selector 策略"></a>3. Selector 策略</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">darkMode</span>: [<span class="string">&#x27;selector&#x27;</span>, <span class="string">&#x27;[data-mode=&quot;dark&quot;]&#x27;</span>],</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>本文將聚焦於最靈活的 <strong>class 策略</strong>。</p><h2 id="🛠️-實作步驟"><a href="#🛠️-實作步驟" class="headerlink" title="🛠️ 實作步驟"></a>🛠️ 實作步驟</h2><h3 id="步驟-1：配置-Tailwind"><a href="#步驟-1：配置-Tailwind" class="headerlink" title="步驟 1：配置 Tailwind"></a>步驟 1：配置 Tailwind</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="comment">/** <span class="doctag">@type</span> &#123;<span class="type">import(&#x27;tailwindcss&#x27;).Config</span>&#125; */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">  <span class="attr">content</span>: [</span><br><span class="line">    <span class="string">&quot;./index.html&quot;</span>,</span><br><span class="line">    <span class="string">&quot;./src/**/*.&#123;js,ts,jsx,tsx&#125;&quot;</span>,</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">darkMode</span>: <span class="string">&#x27;class&#x27;</span>,</span><br><span class="line">  <span class="attr">theme</span>: &#123;</span><br><span class="line">    <span class="attr">extend</span>: &#123;</span><br><span class="line">      <span class="attr">colors</span>: &#123;</span><br><span class="line">        <span class="comment">// 自訂顏色變數</span></span><br><span class="line">        <span class="attr">primary</span>: <span class="string">&quot;#2b7cee&quot;</span>,</span><br><span class="line">        <span class="string">&quot;background-light&quot;</span>: <span class="string">&quot;#f8fafc&quot;</span>,</span><br><span class="line">        <span class="string">&quot;surface-light&quot;</span>: <span class="string">&quot;#ffffff&quot;</span>,</span><br><span class="line">        <span class="comment">// ...深色模式顏色會自動處理</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="步驟-2：建立-Dark-Mode-Hook"><a href="#步驟-2：建立-Dark-Mode-Hook" class="headerlink" title="步驟 2：建立 Dark Mode Hook"></a>步驟 2：建立 Dark Mode Hook</h3><figure class="highlight typescript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// hooks/useDarkMode.ts</span></span><br><span class="line"><span class="keyword">import</span> &#123; useEffect, useState &#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Theme</span> = <span class="string">&#x27;light&#x27;</span> | <span class="string">&#x27;dark&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useDarkMode</span>(<span class="params"></span>): [<span class="title class_">Theme</span>, <span class="function">(<span class="params"><span class="attr">theme</span>: <span class="title class_">Theme</span></span>) =&gt;</span> <span class="built_in">void</span>] &#123;</span><br><span class="line">  <span class="keyword">const</span> [theme, setTheme] = useState&lt;<span class="title class_">Theme</span>&gt;(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 1. 檢查 localStorage</span></span><br><span class="line">    <span class="keyword">const</span> saved = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;darkMode&#x27;</span>);</span><br><span class="line">    <span class="keyword">if</span> (saved) &#123;</span><br><span class="line">      <span class="keyword">return</span> saved === <span class="string">&#x27;true&#x27;</span> ? <span class="string">&#x27;dark&#x27;</span> : <span class="string">&#x27;light&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 2. 檢查系統偏好</span></span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="property">matches</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="string">&#x27;dark&#x27;</span>;</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 3. 預設淺色</span></span><br><span class="line">    <span class="keyword">return</span> <span class="string">&#x27;light&#x27;</span>;</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> root = <span class="variable language_">document</span>.<span class="property">documentElement</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">if</span> (theme === <span class="string">&#x27;dark&#x27;</span>) &#123;</span><br><span class="line">      root.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&#x27;dark&#x27;</span>);</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      root.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&#x27;dark&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 儲存偏好</span></span><br><span class="line">    <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&#x27;darkMode&#x27;</span>, <span class="title class_">String</span>(theme === <span class="string">&#x27;dark&#x27;</span>));</span><br><span class="line">  &#125;, [theme]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> [theme, setTheme];</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="步驟-3：建立切換按鈕元件"><a href="#步驟-3：建立切換按鈕元件" class="headerlink" title="步驟 3：建立切換按鈕元件"></a>步驟 3：建立切換按鈕元件</h3><figure class="highlight tsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// components/DarkModeToggle.tsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; useDarkMode &#125; <span class="keyword">from</span> <span class="string">&#x27;../hooks/useDarkMode&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">DarkModeToggle</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [theme, setTheme] = <span class="title function_">useDarkMode</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">toggleTheme</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setTheme</span>(theme === <span class="string">&#x27;light&#x27;</span> ? <span class="string">&#x27;dark&#x27;</span> : <span class="string">&#x27;light&#x27;</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">onClick</span>=<span class="string">&#123;toggleTheme&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">className</span>=<span class="string">&quot;p-2 rounded-lg bg-gray-100 dark:bg-gray-800 </span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">                 hover:bg-gray-200 dark:hover:bg-gray-700 </span></span></span></span><br><span class="line"><span class="string"><span class="tag"><span class="language-xml">                 transition-colors&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">aria-label</span>=<span class="string">&quot;Toggle Dark Mode&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">span</span> <span class="attr">className</span>=<span class="string">&quot;material-symbols-outlined&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;theme === &#x27;light&#x27; ? &#x27;dark_mode&#x27; : &#x27;light_mode&#x27;&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="步驟-4：預防-Flash-of-Unstyled-Content-FOUC"><a href="#步驟-4：預防-Flash-of-Unstyled-Content-FOUC" class="headerlink" title="步驟 4：預防 Flash of Unstyled Content (FOUC)"></a>步驟 4：預防 Flash of Unstyled Content (FOUC)</h3><p>在 HTML 的 <code>&lt;head&gt;</code> 中加入腳本，在頁面載入前設定主題：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">&lt;!-- index.html --&gt;</span></span><br><span class="line"><span class="meta">&lt;!DOCTYPE <span class="keyword">html</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">html</span> <span class="attr">lang</span>=<span class="string">&quot;zh-TW&quot;</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">head</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">charset</span>=<span class="string">&quot;UTF-8&quot;</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;viewport&quot;</span> <span class="attr">content</span>=<span class="string">&quot;width=device-width, initial-scale=1.0&quot;</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">  <span class="comment">&lt;!-- 預防閃爍 --&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span><br><span class="line"><span class="language-javascript">    (<span class="keyword">function</span>(<span class="params"></span>) &#123;</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">const</span> savedTheme = <span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;darkMode&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      <span class="keyword">if</span> (savedTheme === <span class="string">&#x27;true&#x27;</span> || </span></span><br><span class="line"><span class="language-javascript">          (!savedTheme &amp;&amp; <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>).<span class="property">matches</span>)) &#123;</span></span><br><span class="line"><span class="language-javascript">        <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&#x27;dark&#x27;</span>);</span></span><br><span class="line"><span class="language-javascript">      &#125;</span></span><br><span class="line"><span class="language-javascript">    &#125;)();</span></span><br><span class="line"><span class="language-javascript">  </span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span><br><span class="line">  </span><br><span class="line">  <span class="tag">&lt;<span class="name">title</span>&gt;</span>My App<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">  <span class="tag">&lt;<span class="name">div</span> <span class="attr">id</span>=<span class="string">&quot;root&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">div</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">body</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">html</span>&gt;</span></span><br></pre></td></tr></table></figure><h2 id="🎯-實用樣式模式"><a href="#🎯-實用樣式模式" class="headerlink" title="🎯 實用樣式模式"></a>🎯 實用樣式模式</h2><h3 id="基礎顏色切換"><a href="#基礎顏色切換" class="headerlink" title="基礎顏色切換"></a>基礎顏色切換</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=<span class="string">&quot;bg-white dark:bg-gray-900 </span></span><br><span class="line"><span class="string">                text-gray-900 dark:text-white&quot;</span>&gt;</span><br><span class="line">  內容</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="邊框和陰影"><a href="#邊框和陰影" class="headerlink" title="邊框和陰影"></a>邊框和陰影</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=<span class="string">&quot;border border-gray-200 dark:border-gray-700</span></span><br><span class="line"><span class="string">                shadow-sm hover:shadow-md </span></span><br><span class="line"><span class="string">                dark:shadow-gray-900/50&quot;</span>&gt;</span><br><span class="line">  卡片內容</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h3 id="互動狀態"><a href="#互動狀態" class="headerlink" title="互動狀態"></a>互動狀態</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;button className=<span class="string">&quot;bg-blue-500 hover:bg-blue-600 </span></span><br><span class="line"><span class="string">                   dark:bg-blue-600 dark:hover:bg-blue-700</span></span><br><span class="line"><span class="string">                   text-white transition-colors&quot;</span>&gt;</span><br><span class="line">  按鈕</span><br><span class="line">&lt;/button&gt;</span><br></pre></td></tr></table></figure><h3 id="漸層背景"><a href="#漸層背景" class="headerlink" title="漸層背景"></a>漸層背景</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=<span class="string">&quot;bg-gradient-to-br from-blue-50 to-indigo-100</span></span><br><span class="line"><span class="string">                dark:from-gray-900 dark:to-gray-800&quot;</span>&gt;</span><br><span class="line">  漸層背景區域</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="🔄-跨頁面-應用同步"><a href="#🔄-跨頁面-應用同步" class="headerlink" title="🔄 跨頁面&#x2F;應用同步"></a>🔄 跨頁面&#x2F;應用同步</h2><p>如果你有多個網站或應用需要同步深色模式設定：</p><h3 id="方法-1：使用-Storage-Event"><a href="#方法-1：使用-Storage-Event" class="headerlink" title="方法 1：使用 Storage Event"></a>方法 1：使用 Storage Event</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 監聽其他分頁的變化</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&#x27;storage&#x27;</span>, <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (e.<span class="property">key</span> === <span class="string">&#x27;darkMode&#x27;</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> isDark = e.<span class="property">newValue</span> === <span class="string">&#x27;true&#x27;</span>;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">classList</span>.<span class="title function_">toggle</span>(<span class="string">&#x27;dark&#x27;</span>, isDark);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="方法-2：使用相同的-localStorage-Key"><a href="#方法-2：使用相同的-localStorage-Key" class="headerlink" title="方法 2：使用相同的 localStorage Key"></a>方法 2：使用相同的 localStorage Key</h3><p>確保所有網站使用相同的 key 和網域：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 統一的 Storage Key</span></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">THEME_KEY</span> = <span class="string">&#x27;app-theme-preference&#x27;</span>;</span><br></pre></td></tr></table></figure><h2 id="📱-響應式設計考量"><a href="#📱-響應式設計考量" class="headerlink" title="📱 響應式設計考量"></a>📱 響應式設計考量</h2><p>不同裝置上的深色模式體驗：</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">&lt;div className=<span class="string">&quot;</span></span><br><span class="line"><span class="string">  // 手機：較暗的背景</span></span><br><span class="line"><span class="string">  bg-gray-900 </span></span><br><span class="line"><span class="string">  // 平板以上：稍微亮一點</span></span><br><span class="line"><span class="string">  md:dark:bg-gray-800</span></span><br><span class="line"><span class="string">  // 桌面：更豐富的層次</span></span><br><span class="line"><span class="string">  lg:dark:bg-gradient-to-br lg:dark:from-gray-900 lg:dark:to-gray-800</span></span><br><span class="line"><span class="string">&quot;</span>&gt;</span><br><span class="line">  響應式深色模式</span><br><span class="line">&lt;/div&gt;</span><br></pre></td></tr></table></figure><h2 id="🎨-設計最佳實踐"><a href="#🎨-設計最佳實踐" class="headerlink" title="🎨 設計最佳實踐"></a>🎨 設計最佳實踐</h2><h3 id="1-對比度要足夠"><a href="#1-對比度要足夠" class="headerlink" title="1. 對比度要足夠"></a>1. 對比度要足夠</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 好的對比度</span></span><br><span class="line">text-gray-<span class="number">900</span> <span class="attr">dark</span>:text-gray-<span class="number">100</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 避免這樣（對比度不足）</span></span><br><span class="line">text-gray-<span class="number">700</span> <span class="attr">dark</span>:text-gray-<span class="number">400</span></span><br></pre></td></tr></table></figure><h3 id="2-使用語意化顏色"><a href="#2-使用語意化顏色" class="headerlink" title="2. 使用語意化顏色"></a>2. 使用語意化顏色</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="attr">theme</span>: &#123;</span><br><span class="line">  <span class="attr">extend</span>: &#123;</span><br><span class="line">    <span class="attr">colors</span>: &#123;</span><br><span class="line">      <span class="string">&#x27;text-primary&#x27;</span>: <span class="string">&#x27;rgb(15 23 42)&#x27;</span>, <span class="comment">// slate-900</span></span><br><span class="line">      <span class="string">&#x27;text-secondary&#x27;</span>: <span class="string">&#x27;rgb(100 116 139)&#x27;</span>, <span class="comment">// slate-500</span></span><br><span class="line">      <span class="string">&#x27;surface&#x27;</span>: <span class="string">&#x27;rgb(255 255 255)&#x27;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="3-圖片和媒體處理"><a href="#3-圖片和媒體處理" class="headerlink" title="3. 圖片和媒體處理"></a>3. 圖片和媒體處理</h3><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;img </span><br><span class="line">  src=<span class="string">&quot;/hero.jpg&quot;</span></span><br><span class="line">  alt=<span class="string">&quot;Hero&quot;</span></span><br><span class="line">  className=<span class="string">&quot;dark:opacity-80 dark:contrast-90&quot;</span></span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><h2 id="🧪-測試深色模式"><a href="#🧪-測試深色模式" class="headerlink" title="🧪 測試深色模式"></a>🧪 測試深色模式</h2><h3 id="瀏覽器-DevTools"><a href="#瀏覽器-DevTools" class="headerlink" title="瀏覽器 DevTools"></a>瀏覽器 DevTools</h3><ol><li>開啟 Chrome DevTools</li><li>按 <code>Cmd/Ctrl + Shift + P</code></li><li>輸入 “Emulate CSS prefers-color-scheme”</li><li>選擇 dark 或 light</li></ol><h3 id="自動化測試"><a href="#自動化測試" class="headerlink" title="自動化測試"></a>自動化測試</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// cypress/e2e/dark-mode.cy.js</span></span><br><span class="line"><span class="title function_">describe</span>(<span class="string">&#x27;Dark Mode&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">it</span>(<span class="string">&#x27;應該能切換深色模式&#x27;</span>, <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    cy.<span class="title function_">visit</span>(<span class="string">&#x27;/&#x27;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 預設應該是淺色</span></span><br><span class="line">    cy.<span class="title function_">get</span>(<span class="string">&#x27;html&#x27;</span>).<span class="title function_">should</span>(<span class="string">&#x27;not.have.class&#x27;</span>, <span class="string">&#x27;dark&#x27;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 點擊切換按鈕</span></span><br><span class="line">    cy.<span class="title function_">get</span>(<span class="string">&#x27;[aria-label=&quot;Toggle Dark Mode&quot;]&#x27;</span>).<span class="title function_">click</span>();</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 應該變成深色</span></span><br><span class="line">    cy.<span class="title function_">get</span>(<span class="string">&#x27;html&#x27;</span>).<span class="title function_">should</span>(<span class="string">&#x27;have.class&#x27;</span>, <span class="string">&#x27;dark&#x27;</span>);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// localStorage 應該已儲存</span></span><br><span class="line">    cy.<span class="title function_">window</span>().<span class="title function_">then</span>(<span class="function">(<span class="params">win</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">expect</span>(win.<span class="property">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;darkMode&#x27;</span>)).<span class="property">to</span>.<span class="title function_">equal</span>(<span class="string">&#x27;true&#x27;</span>);</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="📊-效能考量"><a href="#📊-效能考量" class="headerlink" title="📊 效能考量"></a>📊 效能考量</h2><h3 id="1-CSS-檔案大小"><a href="#1-CSS-檔案大小" class="headerlink" title="1. CSS 檔案大小"></a>1. CSS 檔案大小</h3><p>使用 class 策略會產生額外的 CSS，但 Tailwind 的 PurgeCSS 會移除未使用的樣式：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// tailwind.config.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">content</span>: [<span class="string">&#x27;./src/**/*.&#123;js,jsx,ts,tsx&#125;&#x27;</span>],</span><br><span class="line">  <span class="comment">// Tailwind 會自動移除未使用的深色模式 class</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="2-重排和重繪"><a href="#2-重排和重繪" class="headerlink" title="2. 重排和重繪"></a>2. 重排和重繪</h3><p>切換深色模式會觸發重排，優化方法：</p><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用 CSS 變數減少重排</span></span><br><span class="line">:root &#123;</span><br><span class="line">  --bg-<span class="attr">color</span>: #ffffff;</span><br><span class="line">  --text-<span class="attr">color</span>: #<span class="number">000000</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">.<span class="property">dark</span> &#123;</span><br><span class="line">  --bg-<span class="attr">color</span>: #1a1a1a;</span><br><span class="line">  --text-<span class="attr">color</span>: #ffffff;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="🔍-常見問題解決"><a href="#🔍-常見問題解決" class="headerlink" title="🔍 常見問題解決"></a>🔍 常見問題解決</h2><h3 id="Q1-切換時出現閃爍"><a href="#Q1-切換時出現閃爍" class="headerlink" title="Q1: 切換時出現閃爍"></a>Q1: 切換時出現閃爍</h3><p><strong>解決方案</strong>：確保在 <code>&lt;head&gt;</code> 中加入初始化腳本（見步驟 4）</p><h3 id="Q2-第三方元件不支援深色模式"><a href="#Q2-第三方元件不支援深色模式" class="headerlink" title="Q2: 第三方元件不支援深色模式"></a>Q2: 第三方元件不支援深色模式</h3><p><strong>解決方案</strong>：使用 CSS 變數包裝</p><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.custom-component</span> &#123;</span><br><span class="line">  <span class="attribute">background</span>: <span class="built_in">var</span>(--bg-color);</span><br><span class="line">  <span class="attribute">color</span>: <span class="built_in">var</span>(--text-color);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="Q3-圖片在深色模式下太亮"><a href="#Q3-圖片在深色模式下太亮" class="headerlink" title="Q3: 圖片在深色模式下太亮"></a>Q3: 圖片在深色模式下太亮</h3><p><strong>解決方案</strong>：使用濾鏡調整</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">&lt;img </span><br><span class="line">  className=<span class="string">&quot;dark:brightness-90 dark:contrast-125&quot;</span></span><br><span class="line">  src=<span class="string">&quot;/image.jpg&quot;</span></span><br><span class="line">/&gt;</span><br></pre></td></tr></table></figure><h2 id="🚀-進階技巧"><a href="#🚀-進階技巧" class="headerlink" title="🚀 進階技巧"></a>🚀 進階技巧</h2><h3 id="1-系統偏好變化監聽"><a href="#1-系統偏好變化監聽" class="headerlink" title="1. 系統偏好變化監聽"></a>1. 系統偏好變化監聽</h3><figure class="highlight javascript"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> mediaQuery = <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(<span class="string">&#x27;(prefers-color-scheme: dark)&#x27;</span>);</span><br><span class="line">  </span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleChange</span> = (<span class="params">e: MediaQueryListEvent</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">if</span> (!<span class="variable language_">localStorage</span>.<span class="title function_">getItem</span>(<span class="string">&#x27;darkMode&#x27;</span>)) &#123;</span><br><span class="line">      <span class="title function_">setTheme</span>(e.<span class="property">matches</span> ? <span class="string">&#x27;dark&#x27;</span> : <span class="string">&#x27;light&#x27;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  </span><br><span class="line">  mediaQuery.<span class="title function_">addEventListener</span>(<span class="string">&#x27;change&#x27;</span>, handleChange);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> mediaQuery.<span class="title function_">removeEventListener</span>(<span class="string">&#x27;change&#x27;</span>, handleChange);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><h3 id="2-動畫過渡"><a href="#2-動畫過渡" class="headerlink" title="2. 動畫過渡"></a>2. 動畫過渡</h3><figure class="highlight css"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* globals.css */</span></span><br><span class="line">* &#123;</span><br><span class="line">  <span class="attribute">transition</span>: background-color <span class="number">0.3s</span> ease, </span><br><span class="line">              border-color <span class="number">0.3s</span> ease,</span><br><span class="line">              color <span class="number">0.3s</span> ease;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="📚-資源推薦"><a href="#📚-資源推薦" class="headerlink" title="📚 資源推薦"></a>📚 資源推薦</h2><ul><li><a href="https://tailwindcss.com/docs/dark-mode">Tailwind CSS Dark Mode 文件</a></li><li><a href="https://darkmodelist.com/">Dark Mode Best Practices</a></li><li><a href="https://material.io/design/color/dark-theme.html">Material Design Dark Theme</a></li></ul><h2 id="💭-總結"><a href="#💭-總結" class="headerlink" title="💭 總結"></a>💭 總結</h2><p>實現一個完整的深色模式系統需要考慮：</p><ol><li>✅ 使用者偏好檢測和儲存</li><li>✅ 避免閃爍的初始化</li><li>✅ 語意化的顏色系統</li><li>✅ 良好的對比度和可讀性</li><li>✅ 跨平台和跨頁面同步</li></ol><p>希望這篇文章能幫助你在專案中實現優雅的深色模式！</p><p>你的網站有實作深色模式嗎？遇到什麼挑戰？歡迎在下方留言討論！</p><hr><p><strong>相關文章</strong>：</p><ul><li><a href="/blog/tailwind-customization">Tailwind CSS 客製化配置實戰</a></li><li><a href="/blog/react-hooks-advanced">React Hooks 進階應用</a></li></ul>]]></content>
    
    
    <summary type="html">從零開始實現一個完整的深色模式系統，包括 Tailwind CSS 配置、React 整合、狀態管理，以及跨網站同步的最佳實踐。</summary>
    
    
    
    <category term="前端開發" scheme="https://www.mattwang.xyz/categories/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/"/>
    
    <category term="CSS" scheme="https://www.mattwang.xyz/categories/%E5%89%8D%E7%AB%AF%E9%96%8B%E7%99%BC/CSS/"/>
    
    
    <category term="Tailwind CSS" scheme="https://www.mattwang.xyz/tags/Tailwind-CSS/"/>
    
    <category term="Dark Mode" scheme="https://www.mattwang.xyz/tags/Dark-Mode/"/>
    
    <category term="UI Design" scheme="https://www.mattwang.xyz/tags/UI-Design/"/>
    
  </entry>
  
</feed>
