<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>H多看源码多读书 - 勤于思考善领悟</title>
  
  
  <link href="http://example.com/atom.xml" rel="self"/>
  
  <link href="http://example.com/"/>
  <updated>2026-04-02T08:39:33.918Z</updated>
  <id>http://example.com/</id>
  
  <author>
    <name>ZHB</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>【置顶】超级有用的html/css/js/vue等技巧 持续更新中</title>
    <link href="http://example.com/posts/8e2e021e.html"/>
    <id>http://example.com/posts/8e2e021e.html</id>
    <published>2030-08-22T11:22:13.000Z</published>
    <updated>2026-04-02T08:39:33.918Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、HTML"><a href="#一、HTML" class="headerlink" title="一、HTML"></a><strong>一、HTML</strong></h2><p>暂无</p><br><h2 id="二、CSS"><a href="#二、CSS" class="headerlink" title="二、CSS"></a><strong>二、CSS</strong></h2><h3 id="1、清除浮动"><a href="#1、清除浮动" class="headerlink" title="1、清除浮动"></a><strong><font color='red'>1、清除浮动</font></strong></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><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></pre></td><td class="code"><pre><span class="line">// 清除浮动（双伪元素）</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:before</span>,</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&#x27;&#x27;</span>;</span><br><span class="line">  <span class="attribute">display</span>: table;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.clearfix</span><span class="selector-pseudo">:after</span> &#123;</span><br><span class="line">  <span class="attribute">clear</span>: both;</span><br><span class="line">&#125;</span><br><span class="line">// ie6 <span class="number">7</span> 专门清除浮动的样式</span><br><span class="line"><span class="selector-class">.clearfix</span> &#123;</span><br><span class="line">  *<span class="attribute">zoom</span>: <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>清除浮动的原理</strong></p><p><code>clear:both</code>清除浮动的关键</p><p><code>clear</code>是CSS中的定位属性，规定元素的哪一侧不允许其他浮动元素。那么<code>clear:both</code>就是规定在左右两侧均不允许浮动元素。</p><p><code>clear</code>属性只能在块级元素上其作用，这就是清除浮动样式中<code>display:table</code>的作用。</p><br><h2 id="三、JavaScript"><a href="#三、JavaScript" class="headerlink" title="三、JavaScript"></a><strong>三、JavaScript</strong></h2><h3 id="1、基于URLSearchParams或URL获取queryString的值"><a href="#1、基于URLSearchParams或URL获取queryString的值" class="headerlink" title="1、基于URLSearchParams或URL获取queryString的值"></a><strong><font color='red'>1、基于URLSearchParams或URL获取queryString的值</font></strong></h3><p>测试链接：</p><p><a href="https://cf.cmpay.com/mpl/credit_orderResult.html?orderNo=50620520605&orderDate=2052">https://cf.cmpay.com/mpl/credit_orderResult.html?orderNo=50620520605&amp;orderDate=2052</a></p><p><strong>URLSearchParams</strong></p><figure class="highlight js"><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">// location.search 取到的是&quot;?orderNo=50620520605&amp;orderDate=2052&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> urlSP = <span class="keyword">new</span> <span class="title class_">URLSearchParams</span>(location.<span class="property">search</span>);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getQueryString</span>(<span class="params">key</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> urlSP.<span class="title function_">get</span>(key)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">getQueryString</span>(<span class="string">&#x27;orderNo&#x27;</span>) <span class="comment">// 50620520605</span></span><br></pre></td></tr></table></figure><p><strong>URL</strong></p><figure class="highlight js"><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="keyword">const</span> urlObj = <span class="keyword">new</span> <span class="title function_">URL</span>(location.<span class="property">href</span>);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getQueryString</span>(<span class="params">key</span>) &#123;</span><br><span class="line"><span class="keyword">return</span> urlObj.<span class="property">searchParams</span>.<span class="title function_">get</span>(key)</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">getQueryString</span>(<span class="string">&#x27;orderNo&#x27;</span>) <span class="comment">// 50620520605</span></span><br></pre></td></tr></table></figure><br><h3 id="2、基于atob和btoa的base64编码和解码"><a href="#2、基于atob和btoa的base64编码和解码" class="headerlink" title="2、基于atob和btoa的base64编码和解码"></a><strong><font color='red'>2、基于atob和btoa的base64编码和解码</font></strong></h3><p>浏览器内置了base64编码和解码的能力</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"># <span class="built_in">encodeURIComponent</span>() 函数可把字符串作为 <span class="variable constant_">URI</span> 组件进行编码</span><br><span class="line"># <span class="built_in">unescape</span>() 函数可对通过 <span class="built_in">escape</span>() 编码的字符串进行解码</span><br><span class="line"># <span class="title function_">btoa</span>() 方法用于创建一个 base-<span class="number">64</span> 编码的字符串</span><br><span class="line"># <span class="title function_">atob</span>() 方法用于解码使用 base-<span class="number">64</span> 编码的字符串</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">utf8_to_b64</span>(<span class="params"> str </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="variable language_">window</span>.<span class="title function_">btoa</span>(<span class="built_in">unescape</span>(<span class="built_in">encodeURIComponent</span>( str )));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">b64_to_utf8</span>(<span class="params"> str </span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="built_in">decodeURIComponent</span>(<span class="built_in">escape</span>(<span class="variable language_">window</span>.<span class="title function_">atob</span>( str )));</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title function_">utf8_to_b64</span>(<span class="string">&#x27;✓ à la mode&#x27;</span>); <span class="comment">// &quot;4pyTIMOgIGxhIG1vZGU=&quot;</span></span><br><span class="line"><span class="title function_">b64_to_utf8</span>(<span class="string">&#x27;4pyTIMOgIGxhIG1vZGU=&#x27;</span>); <span class="comment">// &quot;✓ à la mode&quot;</span></span><br></pre></td></tr></table></figure><br><h3 id="3、相对地址转换为绝对地址"><a href="#3、相对地址转换为绝对地址" class="headerlink" title="3、相对地址转换为绝对地址"></a><strong><font color='red'>3、相对地址转换为绝对地址</font></strong></h3><figure class="highlight js"><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="keyword">function</span> <span class="title function_">realativeToAbs</span>(<span class="params">href</span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> aEl = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;a&quot;</span>);</span><br><span class="line">    aEl.<span class="property">href</span> = href;</span><br><span class="line">    <span class="keyword">const</span> result = aEl.<span class="property">href</span>;</span><br><span class="line">    aEl = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">realativeToAbs</span>(<span class="string">&quot;../a/b/b/index.html&quot;</span>) <span class="comment">// http://127.0.0.1:5500/a/b/b/index.html</span></span><br></pre></td></tr></table></figure><br><h3 id="4-、禁止图片拖拽、禁止右键、禁止选择、禁止复制"><a href="#4-、禁止图片拖拽、禁止右键、禁止选择、禁止复制" class="headerlink" title="4 、禁止图片拖拽、禁止右键、禁止选择、禁止复制"></a><strong><font color='red'>4 、禁止图片拖拽、禁止右键、禁止选择、禁止复制</font></strong></h3><figure class="highlight js"><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="string">&#x27;dragstart&#x27;</span>,<span class="string">&#x27;contextmenu&#x27;</span>, <span class="string">&#x27;selectstart&#x27;</span>, <span class="string">&#x27;copy&#x27;</span>].<span class="title function_">forEach</span>(<span class="keyword">function</span> (<span class="params">ev</span>) &#123;</span><br><span class="line">    <span class="variable language_">document</span>.<span class="title function_">addEventListener</span>(ev, <span class="keyword">function</span> (<span class="params">ev</span>) &#123;</span><br><span class="line">        ev.<span class="title function_">preventDefault</span>();</span><br><span class="line">        ev.<span class="property">returnValue</span> = <span class="literal">false</span>;</span><br><span class="line">    &#125;)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><br><h3 id="5、es6的解构赋值"><a href="#5、es6的解构赋值" class="headerlink" title="5、es6的解构赋值"></a><strong><font color='red'>5、es6的解构赋值</font></strong></h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> obj = &#123;</span><br><span class="line">    <span class="attr">a</span>:<span class="number">1</span>,</span><br><span class="line">    <span class="attr">b</span>:<span class="number">2</span>,</span><br><span class="line">    <span class="attr">c</span>:<span class="number">3</span>,</span><br><span class="line">    <span class="attr">d</span>:<span class="number">4</span>,</span><br><span class="line">    <span class="attr">e</span>:<span class="number">5</span>,</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#吐槽：</span><br><span class="line"><span class="keyword">const</span> a = obj.<span class="property">a</span>;</span><br><span class="line"><span class="keyword">const</span> b = obj.<span class="property">b</span>;</span><br><span class="line"><span class="keyword">const</span> c = obj.<span class="property">c</span>;</span><br><span class="line"><span class="keyword">const</span> d = obj.<span class="property">d</span>;</span><br><span class="line"><span class="keyword">const</span> e = obj.<span class="property">e</span>;</span><br><span class="line"></span><br><span class="line">#改进：</span><br><span class="line"><span class="keyword">const</span> &#123;a,b,c,d,e&#125; = obj;</span><br><span class="line"></span><br><span class="line">#其他：</span><br><span class="line"><span class="comment">// 属性名和变量名不一样时</span></span><br><span class="line"><span class="keyword">let</span> &#123; <span class="attr">a</span>: f, <span class="attr">b</span>: l &#125; = obj;</span><br><span class="line">f <span class="comment">// 1</span></span><br><span class="line">l <span class="comment">// 2</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 指定默认值</span></span><br><span class="line"><span class="keyword">let</span> &#123; x = <span class="number">11</span> &#125; = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> [a = <span class="number">66</span>] = [];</span><br><span class="line">x <span class="comment">// 11</span></span><br><span class="line">a <span class="comment">// 66</span></span><br></pre></td></tr></table></figure><p>注意解构的对象不能为<code>undefined</code>、<code>null</code>。否则会报错，故要给被解构的对象一个默认值。</p><p><strong>const {a,b,c,d,e} &#x3D; obj || {};</strong></p><br><h3 id="6、合并数据"><a href="#6、合并数据" class="headerlink" title="6、合并数据"></a><strong><font color='red'>6、合并数据</font></strong></h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line">#吐槽：</span><br><span class="line"><span class="keyword">const</span> a = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> b = [<span class="number">1</span>,<span class="number">5</span>,<span class="number">6</span>];</span><br><span class="line"><span class="keyword">const</span> c = a.<span class="title function_">concat</span>(b);<span class="comment">//[1,2,3,1,5,6]</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj1 = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">33</span> &#125;</span><br><span class="line"><span class="keyword">const</span> obj2 = &#123; <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">66</span> &#125;</span><br><span class="line"><span class="keyword">const</span> obj = <span class="title class_">Object</span>.<span class="title function_">assign</span>(&#123;&#125;, obj1, obj2) <span class="comment">//&#123;a: 1, b: 2, c: 66&#125; 属性名相同最后对象覆盖前面对象</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#改进：</span><br><span class="line"><span class="keyword">const</span> a = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line"><span class="keyword">const</span> b = [<span class="number">1</span>, <span class="number">5</span>, <span class="number">6</span>];</span><br><span class="line"><span class="keyword">const</span> c = [...a, ...b] <span class="comment">//[1,2,3,1,5,6] 不去重</span></span><br><span class="line"><span class="keyword">const</span> c = [...<span class="keyword">new</span> <span class="title class_">Set</span>([...a, ...b])] <span class="comment">//[1,2,3,5,6] 去重</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> obj1 = &#123; <span class="attr">a</span>: <span class="number">1</span>, <span class="attr">b</span>: <span class="number">33</span> &#125;</span><br><span class="line"><span class="keyword">const</span> obj2 = &#123; <span class="attr">b</span>: <span class="number">2</span>, <span class="attr">c</span>: <span class="number">66</span> &#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(&#123;...obj1, ...obj2&#125;); <span class="comment">//&#123;a: 1, b: 2, c: 66&#125;</span></span><br></pre></td></tr></table></figure><br><h3 id="7、关于拼接字符串"><a href="#7、关于拼接字符串" class="headerlink" title="7、关于拼接字符串"></a><strong><font color='red'>7、关于拼接字符串</font></strong></h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> name = <span class="string">&#x27;小明&#x27;</span>;</span><br><span class="line"><span class="keyword">const</span> score = <span class="number">59</span>;</span><br><span class="line"></span><br><span class="line">#吐槽：</span><br><span class="line"><span class="keyword">let</span> result = <span class="string">&#x27;&#x27;</span>;</span><br><span class="line"><span class="keyword">if</span>(score &gt; <span class="number">60</span>)&#123;</span><br><span class="line">  result = <span class="string">`<span class="subst">$&#123;name&#125;</span>的考试成绩及格`</span>; </span><br><span class="line">&#125;<span class="keyword">else</span>&#123;</span><br><span class="line">  result = <span class="string">`<span class="subst">$&#123;name&#125;</span>的考试成绩不及格`</span>; </span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">#改进：</span><br><span class="line"><span class="keyword">const</span> result = <span class="string">`<span class="subst">$&#123;name&#125;</span><span class="subst">$&#123;score &gt; <span class="number">60</span>?<span class="string">&#x27;的考试成绩及格&#x27;</span>:<span class="string">&#x27;的考试成绩不及格&#x27;</span>&#125;</span>`</span>; <span class="comment">// 小明的考试成绩不及格</span></span><br></pre></td></tr></table></figure><br><h3 id="8、扁平化数组"><a href="#8、扁平化数组" class="headerlink" title="8、扁平化数组"></a><strong><font color='red'>8、扁平化数组</font></strong></h3><figure class="highlight js"><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="keyword">const</span> deps = &#123;</span><br><span class="line">    <span class="string">&#x27;采购部&#x27;</span>:[<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>],</span><br><span class="line">    <span class="string">&#x27;人事部&#x27;</span>:[<span class="number">5</span>,<span class="number">8</span>,<span class="number">12</span>],</span><br><span class="line">    <span class="string">&#x27;行政部&#x27;</span>:[<span class="number">5</span>,<span class="number">14</span>,<span class="number">79</span>],</span><br><span class="line">    <span class="string">&#x27;运输部&#x27;</span>:[<span class="number">3</span>,<span class="number">64</span>,<span class="number">105</span>],</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">#吐槽：</span><br><span class="line"><span class="keyword">let</span> member = [];</span><br><span class="line"><span class="keyword">for</span> (<span class="keyword">let</span> item <span class="keyword">in</span> deps)&#123;</span><br><span class="line">    <span class="keyword">const</span> value = deps[item];</span><br><span class="line">    <span class="keyword">if</span>(<span class="title class_">Array</span>.<span class="title function_">isArray</span>(value))&#123;</span><br><span class="line">        member = [...member,...value]</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line">member = [...<span class="keyword">new</span> <span class="title class_">Set</span>(member)]  <span class="comment">// [1, 2, 3, 5, 8, 12, 14, 79, 64, 105]</span></span><br><span class="line"></span><br><span class="line">#改进：</span><br><span class="line"><span class="keyword">let</span> member = <span class="title class_">Object</span>.<span class="title function_">values</span>(deps).<span class="title function_">flat</span>(<span class="title class_">Infinity</span>); <span class="comment">// [1, 2, 3, 5, 8, 12, 14, 79, 64, 105]</span></span><br></pre></td></tr></table></figure><br><h3 id="9、添加对象属性"><a href="#9、添加对象属性" class="headerlink" title="9、添加对象属性"></a><strong><font color='red'>9、添加对象属性</font></strong></h3><figure class="highlight js"><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><br><span class="line"><span class="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> index = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">let</span> key = <span class="string">`topic<span class="subst">$&#123;index&#125;</span>`</span>;</span><br><span class="line">obj[key] = <span class="string">&#x27;话题内容&#x27;</span>;</span><br><span class="line"></span><br><span class="line">#改进：es6</span><br><span class="line"><span class="keyword">let</span> obj = &#123;&#125;;</span><br><span class="line"><span class="keyword">let</span> index = <span class="number">1</span>;</span><br><span class="line">obj[<span class="string">`topic<span class="subst">$&#123;index&#125;</span>`</span>] = <span class="string">&#x27;话题内容&#x27;</span>;</span><br></pre></td></tr></table></figure><br><h3 id="10、非空的判断"><a href="#10、非空的判断" class="headerlink" title="10、非空的判断"></a><strong><font color='red'>10、非空的判断</font></strong></h3><p><strong>空值合并操作符</strong>（<strong><code>??</code></strong>）</p><p>只有当左侧为null和undefined时，才会返回右侧的值</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span>(a === <span class="literal">null</span> || a === <span class="literal">undefined</span>) &#123;    </span><br><span class="line"><span class="title function_">doSomething</span>()</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>也就是如果需要验证一个值是否等于null或者undefined，可以使用null合并操作符来简化上面的代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">a ?? <span class="title function_">doSomething</span>()</span><br></pre></td></tr></table></figure><p>这样，仅当 a 未定义或为空时，才会执行控制合并运算符之后的代码。空合并运算符 ??是一个逻辑运算符，当左侧操作数为 null 或未定义时返回其右侧操作数，否则返回左侧操作数。</p><p>例子：</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> nullValue = <span class="literal">null</span>;</span><br><span class="line"><span class="keyword">const</span> emptyText = <span class="string">&quot;&quot;</span>; <span class="comment">// 空字符串，是一个假值，Boolean(&quot;&quot;) === false</span></span><br><span class="line"><span class="keyword">const</span> someNumber = <span class="number">42</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> valA = nullValue ?? <span class="string">&quot;valA 的默认值&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> valB = emptyText ?? <span class="string">&quot;valB 的默认值&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> valC = someNumber ?? <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(valA); <span class="comment">// &quot;valA 的默认值&quot;</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(valB); <span class="comment">// &quot;&quot;（空字符串虽然是假值，但不是 null 或者 undefined）</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(valC); <span class="comment">// 42</span></span><br></pre></td></tr></table></figure><br><h3 id="11、获取对象属性值"><a href="#11、获取对象属性值" class="headerlink" title="11、获取对象属性值"></a><strong><font color='red'>11、获取对象属性值</font></strong></h3><p>可选链操作符 （?.）</p><figure class="highlight js"><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><br><span class="line"><span class="keyword">const</span> name = obj &amp;&amp; obj.<span class="property">name</span>;</span><br><span class="line"></span><br><span class="line">#改进：es6</span><br><span class="line"><span class="keyword">const</span> name = obj?.<span class="property">name</span>;</span><br></pre></td></tr></table></figure><br><h3 id="12、html编码和解码"><a href="#12、html编码和解码" class="headerlink" title="12、html编码和解码"></a><strong><font color='red'>12、html编码和解码</font></strong></h3><figure class="highlight js"><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="number">1.</span>用浏览器内部转换器实现html编码（转义）</span><br><span class="line"><span class="attr">htmlEncode</span>: <span class="keyword">function</span> (<span class="params">html</span>) &#123;</span><br><span class="line">    <span class="comment">//1.首先动态创建一个容器标签元素，如DIV</span></span><br><span class="line">    <span class="keyword">var</span> temp = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">    <span class="comment">//2.然后将要转换的字符串设置为这个元素的innerText或者textContent</span></span><br><span class="line">    (temp.<span class="property">textContent</span> != <span class="literal">undefined</span>) ? (temp.<span class="property">textContent</span> = html) : (temp.<span class="property">innerText</span> = html);</span><br><span class="line">    <span class="comment">//3.最后返回这个元素的innerHTML，即得到经过HTML编码转换的字符串了</span></span><br><span class="line">    <span class="keyword">var</span> output = temp.<span class="property">innerHTML</span>;</span><br><span class="line">    temp = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> output;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">htmlEncode</span>(<span class="string">&quot;&lt;div class=&#x27;demo&#x27;&gt;测试&lt;/div&gt;&quot;</span>));</span><br><span class="line"></span><br><span class="line">#<span class="number">2.</span>用浏览器内部转换器实现html解码（反转义）</span><br><span class="line"><span class="attr">htmlDecode</span>: <span class="keyword">function</span> (<span class="params">text</span>) &#123;</span><br><span class="line">    <span class="comment">//1.首先动态创建一个容器标签元素，如DIV</span></span><br><span class="line">    <span class="keyword">var</span> temp = <span class="variable language_">document</span>.<span class="title function_">createElement</span>(<span class="string">&quot;div&quot;</span>);</span><br><span class="line">    <span class="comment">//2.然后将要转换的字符串设置为这个元素的innerHTML(ie，火狐，google都支持)</span></span><br><span class="line">    temp.<span class="property">innerHTML</span> = text;</span><br><span class="line">    <span class="comment">//3.最后返回这个元素的innerText或者textContent，即得到经过HTML解码的字符串了。</span></span><br><span class="line">    <span class="keyword">var</span> output = temp.<span class="property">innerText</span> || temp.<span class="property">textContent</span>;</span><br><span class="line">    temp = <span class="literal">null</span>;</span><br><span class="line">    <span class="keyword">return</span> output;</span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">htmlDecode</span>(<span class="string">&quot;&amp;lt;div class=&#x27;demo&#x27;&amp;gt;测试&amp;lt;/div&amp;gt;&quot;</span>));</span><br></pre></td></tr></table></figure><br><h3 id="13、箭头函数"><a href="#13、箭头函数" class="headerlink" title="13、箭头函数"></a><strong><font color='red'>13、箭头函数</font></strong></h3><p>var func &#x3D; () &#x3D;&gt; ({ foo: 1 });  返回的是括号里面的对象</p><figure class="highlight js"><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="keyword">let</span> age = <span class="title function_">prompt</span>(<span class="string">&quot;What is your age?&quot;</span>, <span class="number">18</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> welcome = (age &lt; <span class="number">18</span>) ? <span class="function">() =&gt;</span> <span class="title function_">alert</span>(<span class="string">&#x27;Hello&#x27;</span>) : <span class="function">() =&gt;</span> <span class="title function_">alert</span>(<span class="string">&quot;Greetings!&quot;</span>);</span><br><span class="line"><span class="title function_">welcome</span>();</span><br></pre></td></tr></table></figure><p><strong><font color='red' size="3">重要特性</font></strong></p><p><strong><font color='cornflowerblue' size="3">1）没有arguments </font></strong></p><figure class="highlight js"><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="number">1</span>、普通函数中的<span class="variable language_">arguments</span>正确使用：</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">foo</span>(<span class="params">n</span>) &#123;   </span><br><span class="line"> <span class="comment">// arguments[0] 表示传给foo函数的第一个参数，也就是n    </span></span><br><span class="line">    <span class="keyword">var</span> <span class="title function_">f</span> = (<span class="params"></span>) =&gt; <span class="variable language_">arguments</span>[<span class="number">0</span>] + n;    </span><br><span class="line">    <span class="keyword">return</span> <span class="title function_">f</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">foo</span>(<span class="number">1</span>); <span class="comment">// 2</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">3</span>); <span class="comment">// 6</span></span><br><span class="line"><span class="title function_">foo</span>(<span class="number">3</span>, <span class="number">2</span>);<span class="comment">//6</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"># <span class="number">2</span>、箭头函数中无法使用<span class="variable language_">arguments</span></span><br><span class="line"><span class="comment">// ReferenceError: arguments is not defined</span></span><br><span class="line"><span class="keyword">var</span> <span class="title function_">func</span> = (<span class="params">a, b</span>) =&gt; &#123;<span class="keyword">return</span> <span class="variable language_">arguments</span>[<span class="number">0</span>];&#125;</span><br></pre></td></tr></table></figure><p><strong><font color='cornflowerblue' size="3">2）没有prototype属性 </font></strong></p><figure class="highlight js"><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="keyword">var</span> <span class="title function_">Foo</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title class_">Foo</span>.<span class="property"><span class="keyword">prototype</span></span>); <span class="comment">// undefined</span></span><br></pre></td></tr></table></figure><p><strong><font color='cornflowerblue' size="3">3）不能使用new </font></strong></p><p>箭头函数没有this，不能用作构造函数，也就无法使用 new</p><figure class="highlight js"><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="keyword">var</span> <span class="title function_">Foo</span> = (<span class="params"></span>) =&gt; &#123;&#125;;</span><br><span class="line"><span class="keyword">var</span> foo = <span class="keyword">new</span> <span class="title class_">Foo</span>(); <span class="comment">// TypeError: Foo is not a constructor</span></span><br></pre></td></tr></table></figure><br><h3 id="14、初始化一个数组"><a href="#14、初始化一个数组" class="headerlink" title="14、初始化一个数组"></a><strong><font color='red'>14、初始化一个数组</font></strong></h3><p>如果你想初始化一个指定长度的一维数组并指定默认值，你可以这样做。</p><figure class="highlight js"><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="keyword">const</span> array = <span class="title class_">Array</span>(<span class="number">6</span>).<span class="title function_">fill</span>(<span class="string">&#x27;&#x27;</span>); </span><br><span class="line"><span class="comment">// [&#x27;&#x27;, &#x27;&#x27;, &#x27;&#x27;, &#x27;&#x27;, &#x27;&#x27;, &#x27;&#x27;]</span></span><br></pre></td></tr></table></figure><p>如果你想初始化一个指定长度的二维数组并指定默认值，你可以这样做。</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> matrix = <span class="title class_">Array</span>(<span class="number">6</span>).<span class="title function_">fill</span>(<span class="number">0</span>).<span class="title function_">map</span>(<span class="function">() =&gt;</span> <span class="title class_">Array</span>(<span class="number">5</span>).<span class="title function_">fill</span>(<span class="number">0</span>)); </span><br><span class="line"><span class="comment">// [</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// [0, 0, 0, 0, 0],</span></span><br><span class="line"><span class="comment">// ]</span></span><br></pre></td></tr></table></figure><br><h3 id="15、数组求和-最大值-最小值"><a href="#15、数组求和-最大值-最小值" class="headerlink" title="15、数组求和&#x2F;最大值&#x2F;最小值"></a><strong><font color='red'>15、数组求和&#x2F;最大值&#x2F;最小值</font></strong></h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> array  = [<span class="number">5</span>,<span class="number">4</span>,<span class="number">7</span>,<span class="number">8</span>,<span class="number">9</span>,<span class="number">2</span>];</span><br></pre></td></tr></table></figure><p><font color='orange'>1）求和 </font></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">array.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a,b</span>) =&gt;</span> a+b);</span><br></pre></td></tr></table></figure><p><font color='orange'>2）寻找最大值</font></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">array.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a,b</span>) =&gt;</span> a &gt; b ? a : b);  <span class="title class_">Math</span>.<span class="title function_">max</span>(...array)</span><br></pre></td></tr></table></figure><p><font color='orange'>2）寻找最小值</font></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">array.<span class="title function_">reduce</span>(<span class="function">(<span class="params">a,b</span>) =&gt;</span> a &lt; b ? a : b);  <span class="title class_">Math</span>.<span class="title function_">min</span>(...array)</span><br></pre></td></tr></table></figure><p>请记住：数组的reduce方法可以用来解决很多数组求值问题。</p><br><h3 id="16、滤错误值"><a href="#16、滤错误值" class="headerlink" title="16、滤错误值"></a><strong><font color='red'>16、滤错误值</font></strong></h3><p>如果要过滤数组中的值，例如 false、0、null、undefined 等，可以这样做。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> array = [<span class="number">1</span>, <span class="number">0</span>, <span class="literal">undefined</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="string">&#x27;&#x27;</span>, <span class="literal">false</span>];</span><br><span class="line">array.<span class="title function_">filter</span>(<span class="title class_">Boolean</span>);</span><br><span class="line"><span class="comment">// [1, 6, 7]</span></span><br></pre></td></tr></table></figure><br><h3 id="17、空数组"><a href="#17、空数组" class="headerlink" title="17、空数组"></a><strong><font color='red'>17、空数组</font></strong></h3><p>如果要清空数组，可以将数组的长度设置为 0。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> array = [<span class="string">&quot;A&quot;</span>, <span class="string">&quot;B&quot;</span>, <span class="string">&quot;C&quot;</span>, <span class="string">&quot;D&quot;</span>, <span class="string">&quot;E&quot;</span>, <span class="string">&quot;F&quot;</span>]</span><br><span class="line">array.<span class="property">length</span> = <span class="number">0</span> </span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array)  <span class="comment">// []</span></span><br></pre></td></tr></table></figure><br><h3 id="18、计算性能"><a href="#18、计算性能" class="headerlink" title="18、计算性能"></a><strong><font color='red'>18、计算性能</font></strong></h3><p>以下操作可用于计算代码的性能。</p><figure class="highlight js"><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="keyword">const</span> startTime = performance.<span class="title function_">now</span>(); </span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">1000</span>; i++) &#123;    </span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(i)</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> endTime = performance.<span class="title function_">now</span>();</span><br><span class="line"><span class="keyword">const</span> totaltime = endTime - startTime;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(totaltime); <span class="comment">// 10.3333333</span></span><br></pre></td></tr></table></figure><br><h3 id="19、将数组元素转换为数字"><a href="#19、将数组元素转换为数字" class="headerlink" title="19、将数组元素转换为数字"></a><strong><font color='red'>19、将数组元素转换为数字</font></strong></h3><p>如果有一个数组，并且你想将数组的元素转换为数字，你可以使用 map 方法来完成。</p><figure class="highlight js"><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="keyword">const</span> array = [<span class="string">&#x27;12&#x27;</span>, <span class="string">&#x27;1&#x27;</span>, <span class="string">&#x27;3.1415&#x27;</span>, <span class="string">&#x27;-10.01&#x27;</span>];</span><br><span class="line">array.<span class="title function_">map</span>(<span class="title class_">Number</span>);  <span class="comment">// [12, 1, 3.1415, -10.01]</span></span><br></pre></td></tr></table></figure><p>这样，map 对数组的每个元素执行 Number 构造函数，并在遍历数组时返回结果。</p><br><h3 id="20、将类数组转换为数组"><a href="#20、将类数组转换为数组" class="headerlink" title="20、将类数组转换为数组"></a><strong><font color='red'>20、将类数组转换为数组</font></strong></h3><p>可以使用以下方法将类数组转换为数组。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Array</span>.<span class="property"><span class="keyword">prototype</span></span>.<span class="property">slice</span>.<span class="title function_">call</span>(<span class="variable language_">arguments</span>);</span><br></pre></td></tr></table></figure><p>此外，还可以使用扩展运算符来实现。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[...<span class="variable language_">arguments</span>]</span><br></pre></td></tr></table></figure><br><h3 id="21、对象属性的动态声明"><a href="#21、对象属性的动态声明" class="headerlink" title="21、对象属性的动态声明"></a><strong><font color='red'>21、对象属性的动态声明</font></strong></h3><p>如果你想动态地为一个对象声明属性，你可以这样做。</p><figure class="highlight js"><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="keyword">const</span> dynamic = <span class="string">&#x27;color&#x27;</span>;</span><br><span class="line"><span class="keyword">var</span> item = &#123;    </span><br><span class="line"><span class="attr">brand</span>: <span class="string">&#x27;Ford&#x27;</span>,</span><br><span class="line">    [dynamic]: <span class="string">&#x27;Blue&#x27;</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(item); <span class="comment">// &#123; brand: &quot;Ford&quot;, color: &quot;Blue&quot; &#125;</span></span><br></pre></td></tr></table></figure><br><h3 id="22、缩短console-log"><a href="#22、缩短console-log" class="headerlink" title="22、缩短console.log()"></a><strong><font color='red'>22、缩短console.log()</font></strong></h3><p>每次debug都要写很多console.log()会比较麻烦，可以用下面的形式来简化这段代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> c = <span class="variable language_">console</span>.<span class="property">log</span>.<span class="title function_">bind</span>(<span class="variable language_">document</span>) </span><br><span class="line"><span class="title function_">c</span>(<span class="number">222</span>) </span><br><span class="line"><span class="title function_">c</span>(<span class="string">&quot;hello world&quot;</span>)</span><br></pre></td></tr></table></figure><p>这将每次只执行 c 方法。</p><br><h3 id="23、删除数组元素"><a href="#23、删除数组元素" class="headerlink" title="23、删除数组元素"></a><strong><font color='red'>23、删除数组元素</font></strong></h3><p>如果我们想删除一个数组的元素，可以使用delete来完成，但是删除后元素会变成undefined，不会消失，而且执行会消耗很多时间，所以大部分情况下不会满足我们的需求。所以我们可以使用数组的 slice() 方法来删除数组的元素。</p><figure class="highlight js"><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="keyword">const</span> array = [<span class="string">&quot;a&quot;</span>, <span class="string">&quot;b&quot;</span>, <span class="string">&quot;c&quot;</span>, <span class="string">&quot;d&quot;</span>]</span><br><span class="line"><span class="keyword">const</span> arrayRemoveed = array.<span class="title function_">splice</span>(<span class="number">0</span>, <span class="number">2</span>)</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(array)  <span class="comment">// [&quot;c&quot;, &quot;d&quot;]</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(arrayRemoveed)  <span class="comment">// [&quot;a&quot;, &quot;b&quot;]</span></span><br></pre></td></tr></table></figure><br><h3 id="24、检查对象是否为空"><a href="#24、检查对象是否为空" class="headerlink" title="24、检查对象是否为空"></a><strong><font color='red'>24、检查对象是否为空</font></strong></h3><p>如果我们想检查对象是否为空，我们可以使用以下内容。</p><figure class="highlight js"><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="title class_">Object</span>.<span class="title function_">keys</span>(&#123;&#125;).<span class="property">length</span>  <span class="comment">// 0</span></span><br><span class="line"><span class="title class_">Object</span>.<span class="title function_">keys</span>(&#123;<span class="attr">key</span>: <span class="number">1</span>&#125;).<span class="property">length</span>  <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><p>Object.keys() 方法用于获取对象的键，它将返回一个包含这些键值的数组。如果返回数组的长度为 0，则该对象必须为空。</p><br><h3 id="25、获取数组中的最后一项"><a href="#25、获取数组中的最后一项" class="headerlink" title="25、获取数组中的最后一项"></a><strong><font color='red'>25、获取数组中的最后一项</font></strong></h3><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>];</span><br></pre></td></tr></table></figure><p>1、如果要获取数组中的最后一项，通常会这样编写代码。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arr[arr.<span class="property">length</span> - <span class="number">1</span>]  <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>2、我们也可以使用数组的 slice 方法来获取最后一个元素。</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">arr.<span class="title function_">slice</span>(-<span class="number">1</span>) <span class="comment">// 5</span></span><br></pre></td></tr></table></figure><p>当我们将 slice 方法的参数设置为负值时，它会从数组后面开始截取数组值，如果要截取最后两个值，则传入参数-2。</p><p>3、使用“at方法”读取</p><figure class="highlight js"><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">array.<span class="title function_">at</span>(-<span class="number">1</span>) <span class="comment">// 5</span></span><br><span class="line">array.<span class="title function_">at</span>(<span class="number">0</span>) <span class="comment">// 1</span></span><br></pre></td></tr></table></figure><br><h3 id="26、数组元素的随机排序"><a href="#26、数组元素的随机排序" class="headerlink" title="26、数组元素的随机排序"></a><strong><font color='red'>26、数组元素的随机排序</font></strong></h3><figure class="highlight js"><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="keyword">const</span> arr = [<span class="number">2</span>,<span class="number">3</span>,<span class="number">6</span>,<span class="number">9</span>,<span class="number">7</span>,<span class="number">4</span>,<span class="number">11</span>,<span class="number">99</span>,<span class="number">7</span>,<span class="number">1</span>,<span class="number">6</span>,<span class="number">5</span>,<span class="number">7</span>,]</span><br><span class="line"><span class="keyword">const</span> newArr = arr.<span class="title function_">map</span>(<span class="function"><span class="params">val</span> =&gt;</span> (&#123; <span class="attr">v</span>: val, <span class="attr">r</span>: <span class="title class_">Math</span>.<span class="title function_">random</span>() &#125;));</span><br><span class="line">newArr.<span class="title function_">sort</span>(<span class="function">(<span class="params">a, b</span>) =&gt;</span> a.<span class="property">r</span> - b.<span class="property">r</span>);</span><br><span class="line"><span class="keyword">const</span> showList = newArr.<span class="title function_">map</span>(<span class="function"><span class="params">val</span> =&gt;</span> val.<span class="property">v</span>).<span class="title function_">slice</span>(<span class="number">0</span>, <span class="number">6</span>); <span class="comment">// 截取长度未6的随机数组</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(showList)</span><br></pre></td></tr></table></figure><br><h2 id="四、Vue"><a href="#四、Vue" class="headerlink" title="四、Vue"></a><strong>四、Vue</strong></h2><h3 id="1、computed中使用this？"><a href="#1、computed中使用this？" class="headerlink" title="1、computed中使用this？"></a><strong><font color='red'>1、computed中使用this？</font></strong></h3><p>我们通过this能访问到的数据，在computed的第一个参数上都能结构出来</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line">    <span class="title function_">data</span>(<span class="params"></span>) &#123;</span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">            <span class="attr">address</span>: <span class="string">&#x27;黄山&#x27;</span>,</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">computed</span>: &#123;</span><br><span class="line">        <span class="title function_">haha</span>(<span class="params">&#123; address, $attrs, $route, $store, $listeners, $ref &#125;</span>) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(address) <span class="comment">// 黄山</span></span><br><span class="line">            <span class="keyword">return</span> <span class="number">111</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><br><h3 id="2、避免v-if和v-for一起使用"><a href="#2、避免v-if和v-for一起使用" class="headerlink" title="2、避免v-if和v-for一起使用"></a><strong><font color='red'>2、避免v-if和v-for一起使用</font></strong></h3><p>在vue的源码中有一段代码是对指令的优先级的处理</p><p><strong>不好的</strong></p><figure class="highlight js"><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><br><span class="line"># 这段代码是先处理v-<span class="keyword">for</span>再处理v-<span class="keyword">if</span>的</span><br><span class="line"><span class="comment">// 比如这个列表有一百条数据，再某种情况下，它们都不需要显示，当vue还是会循环这个100条数据显示，再去判断v-if，因此，我们应该避免这种情况的出现。</span></span><br><span class="line"></span><br><span class="line">&lt;h3 v-<span class="keyword">if</span>=<span class="string">&quot;status&quot;</span> v-<span class="keyword">for</span>=<span class="string">&quot;item in 100&quot;</span> :key=<span class="string">&quot;item&quot;</span>&gt;&#123;&#123;item&#125;&#125;&lt;/h3&gt;</span><br><span class="line"></span><br><span class="line">#优化</span><br><span class="line">&lt;template v-<span class="keyword">if</span>=<span class="string">&quot;status&quot;</span> &gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">h3</span> <span class="attr">v-for</span>=<span class="string">&quot;item in 100&quot;</span> <span class="attr">:key</span>=<span class="string">&quot;item&quot;</span>&gt;</span>&#123;&#123;item&#125;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line">&lt;/template&gt;</span><br></pre></td></tr></table></figure><br><h3 id="3、修饰符sync"><a href="#3、修饰符sync" class="headerlink" title="3、修饰符sync"></a><strong><font color='red'>3、修饰符sync</font></strong></h3><p>vue 修饰符sync的功能是：当一个子组件改变了一个 prop 的值时，这个变化也会同步到父组件中所绑定</p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父组件</span></span><br><span class="line">template&gt;</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">Toggle</span> <span class="attr">:show.sync</span> = <span class="string">&#x27;isShow&#x27;</span>&gt;</span><span class="tag">&lt;/<span class="name">Toggle</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">&lt;/template&gt;</span><br><span class="line">&lt;script&gt;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line"><span class="title function_">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">isShow</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;script&gt;  </span><br><span class="line"> </span><br><span class="line"># 等同于上面的写法</span><br><span class="line"></span><br><span class="line"><span class="comment">// 父组件</span></span><br><span class="line">template&gt;</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">Toggle</span> <span class="attr">:show</span> = <span class="string">&#x27;isShow&#x27;</span> @<span class="attr">update:show</span>=<span class="string">&quot;change&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">Toggle</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">&lt;/template&gt;</span><br><span class="line">&lt;script&gt;</span><br><span class="line">    <span class="keyword">export</span> <span class="keyword">default</span> &#123;</span><br><span class="line"><span class="attr">props</span>:&#123;</span><br><span class="line">            <span class="attr">isShow</span>: &#123;</span><br><span class="line">            <span class="attr">type</span>: <span class="title class_">Boolean</span></span><br><span class="line">        &#125;</span><br><span class="line">        &#125;,</span><br><span class="line"><span class="title function_">data</span> () &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">isShow</span>: <span class="literal">true</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">        <span class="attr">method</span>: &#123;</span><br><span class="line">            <span class="title function_">change</span>(<span class="params">val</span>) &#123; <span class="comment">// 子组件传来的false</span></span><br><span class="line">                <span class="variable language_">this</span>.<span class="property">isShow</span> = val</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">&#125;</span><br><span class="line">&lt;script&gt;</span><br></pre></td></tr></table></figure><figure class="highlight js"><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">// 子组件</span></span><br><span class="line">&lt;template&gt;</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">div</span> <span class="attr">v-if</span>=<span class="string">&quot;show&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    展示和隐藏组件</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">button</span> @<span class="attr">click</span>=<span class="string">&quot;test&quot;</span>&gt;</span>隐藏组件<span class="tag">&lt;/<span class="name">button</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">&lt;/template&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">script</span>&gt;</span><span class="language-javascript"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"><span class="keyword">export</span> <span class="keyword">default</span> &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">props</span>:[<span class="string">&#x27;show&#x27;</span>],</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  <span class="attr">methods</span>: &#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    <span class="title function_">test</span>(<span class="params"></span>)&#123;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">      <span class="variable language_">this</span>.$emit(<span class="string">&#x27;update:show&#x27;</span>,<span class="literal">false</span>) <span class="comment">//触发 input 事件，并传入新值</span></span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">    &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">  &#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml">&#125;</span></span></span><br><span class="line"><span class="language-javascript"><span class="language-xml"></span><span class="tag">&lt;/<span class="name">script</span>&gt;</span></span></span><br></pre></td></tr></table></figure><br><h2 id="五、IOS和安卓踩坑"><a href="#五、IOS和安卓踩坑" class="headerlink" title="五、IOS和安卓踩坑"></a><strong>五、IOS和安卓踩坑</strong></h2><h3 id="1、IOS滑动不流畅"><a href="#1、IOS滑动不流畅" class="headerlink" title="1、IOS滑动不流畅"></a><strong><font color='red'>1、IOS滑动不流畅</font></strong></h3><p><strong><font color='#10c300'>表现</font></strong></p><p>上下滑动页面会产生卡顿，手指离开页面，页面立即停止运动。整体表现就是滑动不流畅，没有滑动惯性。</p><br><p><strong><font color='#10c300'>为什么 iOS 的 webview 中 滑动不流畅，它是如何定义的？</font></strong></p><p>原来在 iOS 5.0 以及之后的版本，滑动 -webkit-overflow-scrolling 有定义有两个值 <code>auto</code> 和 <code>touch</code>，默认值为 <code>auto</code></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></pre></td><td class="code"><pre><span class="line">-webkit-<span class="attribute">overflow</span>-scrolling: touch; <span class="comment">/* 当手指从触摸屏上移开，会保持一段时间的滚动 */</span></span><br><span class="line"></span><br><span class="line">-webkit-<span class="attribute">overflow</span>-scrolling: auto; <span class="comment">/* 当手指从触摸屏上移开，滚动会立即停止 */</span></span><br></pre></td></tr></table></figure><br><p><strong><font color='#10c300'>解决方案</font></strong></p><p><strong><font color='cornflowerblue' size="3">1）在 滚动容器上 增加滚动 touch 方法 </font></strong></p><p>将<code>-webkit-overflow-scrolling</code> 值设置为 <code>touch</code></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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.wrapper</span> &#123;</span><br><span class="line">    -webkit-<span class="attribute">overflow</span>-scrolling: touch;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>设置滚动条隐藏： <code>.container ::-webkit-scrollbar &#123;display: none;&#125;</code></p></blockquote><p>可能会导致使用<code>position:fixed;</code> 固定定位的元素，随着页面一起滚动</p><p><strong><font color='cornflowerblue' size="3">2）设置 overflow</font></strong></p><p>设置外部 <code>overflow</code> 为 <code>hidden</code>,设置内容元素 <code>overflow</code> 为 <code>auto</code>。内部元素超出 body 即产生滚动，超出的部分 body 隐藏。</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><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-tag">body</span> &#123;</span><br><span class="line">    <span class="attribute">overflow-y</span>: hidden;</span><br><span class="line">&#125;</span><br><span class="line"><span class="selector-class">.wrapper</span> &#123;</span><br><span class="line">    <span class="attribute">overflow-y</span>: auto;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>两者结合使用更佳！</p><br><h3 id="2、click-点击事件延时与穿透"><a href="#2、click-点击事件延时与穿透" class="headerlink" title="2、click 点击事件延时与穿透"></a><strong><font color='red'>2、click 点击事件延时与穿透</font></strong></h3><p><strong><font color='#10c300'>表现</font></strong></p><p>监听元素 <code>click</code> 事件，点击元素触发时间延迟约 <code>300ms</code>。点击蒙层，蒙层消失后，下层元素点击触发。</p><br><p><strong><font color='#10c300'>为什么会产生 click 延时？</font></strong></p><p>iOS 中的 safari，为了实现双击缩放操作，在单击 300ms 之后，如果未进行第二次点击，则执行 <code>click</code> 单击操作。也就是说来判断用户行为是否为双击产生的。但是，在 App 中，无论是否需要双击缩放这种行为，<code>click</code> 单击都会产生 300ms 延迟。</p><br><p><strong><font color='#10c300'>为什么会产生 click 点击穿透？</font></strong></p><p>双层元素叠加时，在上层元素上绑定 <code>touch</code> 事件，下层元素绑定 <code>click</code> 事件。由于 <code>click</code> 发生在 <code>touch</code> 之后，点击上层元素，元素消失，下层元素会触发 <code>click</code> 事件，由此产生了点击穿透的效果</p><br><p><strong><font color='#10c300'>原理与解决方案</font></strong></p><p><strong><font color='cornflowerblue' size="3">1）使用 touchstart 替换 click</font></strong></p><p>移动设备不仅支持点击还支持触摸事件。那么我们现在基本思路就是用 <code>touch</code> 事件代替<code>click</code> 事件。</p><p>将 <code>click</code> 替换成 <code>touchstart</code> 不仅解决了 <code>click</code> 事件延时问题，还解决了穿透问题。因为穿透问题是在 <code>touch</code> 和 <code>click</code> 混用时产生。</p><p>在原生中使用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">el.addEventListener(&quot;touchstart&quot;, () =&gt; &#123; console.log(&quot;ok&quot;); &#125;, false);</span><br></pre></td></tr></table></figure><p>在 vue 中使用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button @touchstart=&quot;handleTouchstart()&quot;&gt;点击&lt;/button&gt;</span><br></pre></td></tr></table></figure><p>开源解决方案中，也是既提供了 <code>click</code> 事件，又提供了<code>touchstart</code> 事件。如 vant 中的 <code>button</code> 组件</p><p><strong><font color='cornflowerblue' size="3">2）使用 fastclick 库</font></strong></p><p>使用 <code>npm/yarn</code> 安装后使用</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">import FastClick from &#x27;fastclick&#x27;;</span><br><span class="line"></span><br><span class="line">FastClick.attach(document.body, options);</span><br></pre></td></tr></table></figure><p>同样，使用<code>fastclick</code>库后，<code>click</code> 延时和穿透问题都没了</p><br><h3 id="3、软键盘将页面顶起来、收起未回落问题"><a href="#3、软键盘将页面顶起来、收起未回落问题" class="headerlink" title="3、软键盘将页面顶起来、收起未回落问题"></a><strong><font color='red'>3、软键盘将页面顶起来、收起未回落问题</font></strong></h3><p><strong><font color='#10c300'>表现</font></strong></p><p>Android 手机中，点击 <code>input</code> 框时，键盘弹出，将页面顶起来，导致页面样式错乱。</p><p>移开焦点时，键盘收起，键盘区域空白，未回落。</p><br><p><strong><font color='#10c300'>产生原因</font></strong></p><p>我们在app 布局中会有个固定的底部。安卓一些版本中，输入弹窗出来，会将解压 <code>absolute</code> 和 <code>fixed</code> 定位的元素。导致可视区域变小，布局错乱。</p><br><p><strong><font color='#10c300'>原理与解决方案</font></strong></p><p>软键盘将页面顶起来的解决方案，主要是通过监听页面高度变化，强制恢复成弹出前的高度。</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 记录原有的视口高度</span></span><br><span class="line"><span class="keyword">const</span> originalHeight = <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">clientHeight</span> || <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">onresize</span> = <span class="keyword">function</span>(<span class="params"></span>)&#123;</span><br><span class="line">  <span class="keyword">var</span> resizeHeight = <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span> || <span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">clientHeight</span>;</span><br><span class="line">  <span class="keyword">if</span>(resizeHeight &lt; originalHeight )&#123;</span><br><span class="line">    <span class="comment">// 恢复内容区域高度</span></span><br><span class="line">    <span class="comment">// const container = document.getElementById(&quot;container&quot;)</span></span><br><span class="line">    <span class="comment">// 例如 container.style.height = originalHeight;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>键盘不能回落问题出现在 iOS 12+ 和 wechat 6.7.4+ 中，而在微信 H5 开发中是比较常见的 Bug。</p><p>兼容原理，1.判断版本类型 2.更改滚动的可视区域</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> isWechat = <span class="variable language_">window</span>.<span class="property">navigator</span>.<span class="property">userAgent</span>.<span class="title function_">match</span>(<span class="regexp">/MicroMessenger\/([\d\.]+)/i</span>);</span><br><span class="line"><span class="keyword">if</span> (!isWechat) <span class="keyword">return</span>;</span><br><span class="line"><span class="keyword">const</span> wechatVersion = wechatInfo[<span class="number">1</span>];</span><br><span class="line"><span class="keyword">const</span> version = (navigator.<span class="property">appVersion</span>).<span class="title function_">match</span>(<span class="regexp">/OS (\d+)_(\d+)_?(\d+)?/</span>);</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 如果设备类型为iOS 12+ 和wechat 6.7.4+，恢复成原来的视口</span></span><br><span class="line"><span class="keyword">if</span> (+wechatVersion.<span class="title function_">replace</span>(<span class="regexp">/\./g</span>, <span class="string">&#x27;&#x27;</span>) &gt;= <span class="number">674</span> &amp;&amp; +version[<span class="number">1</span>] &gt;= <span class="number">12</span>) &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">scrollTo</span>(<span class="number">0</span>, <span class="title class_">Math</span>.<span class="title function_">max</span>(<span class="variable language_">document</span>.<span class="property">body</span>.<span class="property">clientHeight</span>, <span class="variable language_">document</span>.<span class="property">documentElement</span>.<span class="property">clientHeight</span>));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p><code>window.scrollTo(x-coord, y-coord)</code>，其中<code>window.scrollTo(0, clientHeight)</code>恢复成原来的视口</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、HTML&quot;&gt;&lt;a href=&quot;#一、HTML&quot; class=&quot;headerlink&quot; title=&quot;一、HTML&quot;&gt;&lt;/a&gt;&lt;strong&gt;一、HTML&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;暂无&lt;/p&gt;
&lt;br&gt;

&lt;h2 id=&quot;二、CSS&quot;&gt;&lt;a href=&quot;</summary>
      
    
    
    
    <category term="其他" scheme="http://example.com/categories/%E5%85%B6%E4%BB%96/"/>
    
    
    <category term="css" scheme="http://example.com/tags/css/"/>
    
    <category term="前端" scheme="http://example.com/tags/%E5%89%8D%E7%AB%AF/"/>
    
    <category term="javaScript" scheme="http://example.com/tags/javaScript/"/>
    
    <category term="vue" scheme="http://example.com/tags/vue/"/>
    
    <category term="vue3" scheme="http://example.com/tags/vue3/"/>
    
    <category term="html" scheme="http://example.com/tags/html/"/>
    
  </entry>
  
  <entry>
    <title>Webpack 5 代码分割完全指南 (Code Splitting)</title>
    <link href="http://example.com/posts/59a1b2c3.html"/>
    <id>http://example.com/posts/59a1b2c3.html</id>
    <published>2026-03-04T14:00:00.000Z</published>
    <updated>2026-04-02T08:39:33.923Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>📚 本指南旨在帮助开发者深入掌握 Webpack 5 的代码分割技术，从基础原理到生产环境的高级配置一应俱全。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E3%80%81%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2">为什么需要代码分割</a></li><li><a href="#%E4%BA%8C%E3%80%81%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2%E7%9A%84%E4%B8%89%E7%A7%8D%E6%96%B9%E5%BC%8F">代码分割的三种方式</a></li><li><a href="#%E4%B8%89%E3%80%81%E5%85%A5%E5%8F%A3%E8%B5%B7%E7%82%B9%E5%88%86%E5%89%B2">入口起点分割</a></li><li><a href="#%E5%9B%9B%E3%80%81splitchunksplugin-%E8%AF%A6%E8%A7%A3">SplitChunksPlugin 详解</a></li><li><a href="#%E4%BA%94%E3%80%81%E5%8A%A8%E6%80%81%E5%AF%BC%E5%85%A5dynamic-import">动态导入（Dynamic Import）</a></li><li><a href="#%E5%85%AD%E3%80%81%E9%AD%94%E6%B3%95%E6%B3%A8%E9%87%8Amagic-comments">魔法注释（Magic Comments）</a></li><li><a href="#%E4%B8%83%E3%80%81%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2%E7%BB%9F%E4%B8%80%E5%91%BD%E5%90%8D%E8%A7%84%E8%8C%83">代码分割统一命名规范</a></li><li><a href="#%E5%85%AB%E3%80%81%E9%A2%84%E8%8E%B7%E5%8F%96%E4%B8%8E%E9%A2%84%E5%8A%A0%E8%BD%BD">预获取与预加载</a></li><li><a href="#%E4%B9%9D%E3%80%81tree-shaking-%E4%B8%8E%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2%E7%9A%84%E5%8D%8F%E5%90%8C">Tree Shaking 与代码分割的协同</a></li><li><a href="#%E5%8D%81%E3%80%81%E5%AE%9E%E6%88%98%E5%AE%8C%E6%95%B4%E7%9A%84%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2%E9%85%8D%E7%BD%AE%E6%96%B9%E6%A1%88">实战：完整的代码分割配置方案</a></li><li><a href="#%E5%8D%81%E4%B8%80%E3%80%81%E5%88%86%E6%9E%90%E4%B8%8E%E4%BC%98%E5%8C%96">分析与优化</a></li><li><a href="#%E5%8D%81%E4%BA%8C%E3%80%81%E9%92%88%E5%AF%B9%E4%B8%8D%E5%90%8C%E7%B1%BB%E5%9E%8B%E9%A1%B9%E7%9B%AE%E7%9A%84%E9%85%8D%E7%BD%AE%E5%BB%BA%E8%AE%AE">针对不同类型项目的配置建议</a></li></ol><br><h2 id="一、为什么需要代码分割"><a href="#一、为什么需要代码分割" class="headerlink" title="一、为什么需要代码分割"></a><strong>一、为什么需要代码分割</strong></h2><p>在一个未经优化的 Webpack 项目中，所有代码会被打包成 <strong>一个巨大的 bundle 文件</strong>。这带来几个严重问题：</p><ol><li><strong>首屏加载慢</strong>：用户必须下载全部代码（包括当前页面根本不需要的部分）才能看到页面内容。</li><li><strong>缓存利用率低</strong>：修改任何一行代码，整个 bundle 的 hash 都会变化，用户需要重新下载一切。</li><li><strong>并行加载受限</strong>：浏览器的并发请求能力被浪费，只需下载一个文件却不能利用多连接并行。</li></ol><p><strong>代码分割（Code Splitting）</strong> 的核心思想就是：<strong>把一个大 bundle 拆成多个更小的 chunk，按需加载或并行加载</strong>。</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">┌──────────────────────────────────────────────┐</span><br><span class="line">│                                              │</span><br><span class="line">│        未分割：一个巨大的 bundle.js            │</span><br><span class="line">│        (所有页面、所有库、所有逻辑)             │</span><br><span class="line">│                                              │</span><br><span class="line">└──────────────────────────────────────────────┘</span><br><span class="line">                    ↓ 代码分割后</span><br><span class="line">┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐</span><br><span class="line">│ vendor.js│  │  main.js │  │ pageA.js │  │ pageB.js │</span><br><span class="line">│(第三方库) │  │(公共逻辑) │  │(按需加载) │  │(按需加载) │</span><br><span class="line">└──────────┘  └──────────┘  └──────────┘  └──────────┘</span><br></pre></td></tr></table></figure><hr><br><h2 id="二、代码分割的三种方式"><a href="#二、代码分割的三种方式" class="headerlink" title="二、代码分割的三种方式"></a><strong>二、代码分割的三种方式</strong></h2><p>Webpack 提供了三种方式实现代码分割，它们可以组合使用：</p><table><thead><tr><th>方式</th><th>说明</th><th>适用场景</th></tr></thead><tbody><tr><td><strong>入口起点</strong></td><td>在 <code>entry</code> 中配置多个入口</td><td><strong>多页面应用（MPA）</strong></td></tr><tr><td><strong>SplitChunksPlugin</strong></td><td>自动提取公共依赖和第三方库</td><td>所有项目，尤其是有共享模块时</td></tr><tr><td><strong>动态导入</strong></td><td>使用 <code>import()</code> 语法按需加载</td><td>路由级懒加载、大型功能模块</td></tr></tbody></table><hr><br><h2 id="三、入口起点分割-不常用"><a href="#三、入口起点分割-不常用" class="headerlink" title="三、入口起点分割(不常用)"></a><strong>三、入口起点分割(不常用)</strong></h2><h3 id="3-1-基础多入口配置"><a href="#3-1-基础多入口配置" class="headerlink" title="3.1 基础多入口配置"></a><strong><font color='red'>3.1 基础多入口配置</font></strong></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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ./src/app.js&#x27;，只有一个入口文件，单入口</span></span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="comment">// 有多个文，多入口</span></span><br><span class="line">    <span class="attr">app</span>: <span class="string">&quot;./src/app.js&quot;</span>,</span><br><span class="line">    <span class="attr">admin</span>: <span class="string">&quot;./src/admin.js&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;dist&quot;</span>),</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;[name].[contenthash:8].js&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>这样会生成 <code>app.xxx.js</code> 和 <code>admin.xxx.js</code> 两个独立的 bundle。</p><h3 id="3-2-问题：重复打包"><a href="#3-2-问题：重复打包" class="headerlink" title="3.2 问题：重复打包"></a><strong><font color='red'>3.2 问题：重复打包</font></strong></h3><p>假设 <code>app.js</code> 和 <code>admin.js</code> 都引入了 <code>lodash</code>，那么 <code>lodash</code> 会被 <strong>打包两次</strong>，分别出现在两个 bundle 中。这显然是一种浪费。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/app.js</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&quot;lodash&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(_.<span class="title function_">join</span>([<span class="string">&quot;App&quot;</span>, <span class="string">&quot;Module&quot;</span>], <span class="string">&quot; &quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// src/admin.js</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&quot;lodash&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(_.<span class="title function_">join</span>([<span class="string">&quot;Admin&quot;</span>, <span class="string">&quot;Module&quot;</span>], <span class="string">&quot; &quot;</span>));</span><br></pre></td></tr></table></figure><p><strong>打包结果（未去重时）：</strong></p><figure class="highlight plaintext"><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">app.abcd1234.js    → 包含 lodash + app 逻辑（约 70KB + 1KB）</span><br><span class="line">admin.efgh5678.js  → 包含 lodash + admin 逻辑（约 70KB + 1KB）</span><br></pre></td></tr></table></figure><p><code>lodash</code> 被重复包含了两次！这就需要 <code>SplitChunksPlugin</code> 来解决了。</p><h3 id="3-3-使用-dependOn-共享模块"><a href="#3-3-使用-dependOn-共享模块" class="headerlink" title="3.3 使用 dependOn 共享模块"></a><strong><font color='red'>3.3 使用 dependOn 共享模块</font></strong></h3><p>Webpack 5 提供了 <code>dependOn</code> 选项来手动声明入口间的依赖关系。根据共享模块的数量，有以下几种配置方式：</p><h4 id="1）场景一：共享单个模块"><a href="#1）场景一：共享单个模块" class="headerlink" title="1）场景一：共享单个模块"></a><strong><font color='#10c300'>1）场景一：共享单个模块</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="comment">// 把 lodash 单独声明为一个共享入口</span></span><br><span class="line">    <span class="attr">shared</span>: <span class="string">&quot;lodash&quot;</span>,</span><br><span class="line">    <span class="attr">app</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/app.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;shared&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">admin</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/admin.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;shared&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;[name].[contenthash:8].js&quot;</span>,</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;dist&quot;</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>打包结果（去重后）：</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">shared.xxxx.js  → 只包含 lodash（约 70KB）</span><br><span class="line">app.xxxx.js     → 只包含 app 自身逻辑（约 1KB）</span><br><span class="line">admin.xxxx.js   → 只包含 admin 自身逻辑（约 1KB）</span><br></pre></td></tr></table></figure><h4 id="2）场景二：共享多个模块"><a href="#2）场景二：共享多个模块" class="headerlink" title="2）场景二：共享多个模块"></a><strong><font color='#10c300'>2）场景二：共享多个模块</font></strong></h4><p>如果入口文件共同依赖了多个第三方库（如 <code>lodash</code>, <code>axios</code>, <code>react</code> 等），你可以通过数组组合或拆分多个共享块来实现。</p><p><strong>方案 A：将多个依赖合并为一个公共 Chunk</strong></p><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><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="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="comment">// 将多个常用依赖打包到一个名为 commonVendor 的 chunk 中</span></span><br><span class="line">    <span class="attr">commonVendor</span>: [<span class="string">&quot;lodash&quot;</span>, <span class="string">&quot;axios&quot;</span>, <span class="string">&quot;dayjs&quot;</span>],</span><br><span class="line">    <span class="attr">app</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/app.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;commonVendor&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">admin</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/admin.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;commonVendor&quot;</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><p><strong>方案 B：将依赖拆分为多个公共 Chunk</strong></p><p>如果依赖库职责不同（例如基础 UI 库与工具函数库分开），可以定义多个独立入口，并在 <code>dependOn</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><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">utils</span>: [<span class="string">&quot;lodash&quot;</span>, <span class="string">&quot;axios&quot;</span>],</span><br><span class="line">    <span class="attr">reactVendor</span>: [<span class="string">&quot;react&quot;</span>, <span class="string">&quot;react-dom&quot;</span>],</span><br><span class="line">    <span class="attr">app</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/app.js&quot;</span>,</span><br><span class="line">      <span class="comment">// dependOn 接收数组，表示同时依赖多个共享块</span></span><br><span class="line">      <span class="attr">dependOn</span>: [<span class="string">&quot;utils&quot;</span>, <span class="string">&quot;reactVendor&quot;</span>],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">admin</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/admin.js&quot;</span>,</span><br><span class="line">      <span class="comment">// admin 也许只需要工具库</span></span><br><span class="line">      <span class="attr">dependOn</span>: [<span class="string">&quot;utils&quot;</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><h4 id="3）场景三：共享本地自定义模块"><a href="#3）场景三：共享本地自定义模块" class="headerlink" title="3）场景三：共享本地自定义模块"></a><strong><font color='#10c300'>3）场景三：共享本地自定义模块</font></strong></h4><p><code>dependOn</code> 的底层逻辑是<strong>提取另一个入口 chunk 中的模块</strong>，这种“共享”不局限于 <code>node_modules</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="comment">// 将自己的本地模块作为一个独立的入口配置</span></span><br><span class="line">    <span class="attr">myUtils</span>: <span class="string">&quot;./src/utils/index.js&quot;</span>,</span><br><span class="line">    <span class="attr">app</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/app.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;myUtils&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">admin</span>: &#123;</span><br><span class="line">      <span class="attr">import</span>: <span class="string">&quot;./src/admin.js&quot;</span>,</span><br><span class="line">      <span class="attr">dependOn</span>: <span class="string">&quot;myUtils&quot;</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><p><strong>适用情况：</strong></p><ul><li>你的多个入口应用（如前台和后台）都重度使用了同一个自建组件库或工具集。</li><li>你希望将这些相对稳定的自建库与频繁变动的业务逻辑拆分，以利用浏览器缓存。</li></ul><blockquote><p><strong>注意与释疑</strong>：配置了多个入口（<code>entry</code>），不代表所有入口都会放在<strong>同一个</strong> HTML 页面里。</p><ol><li><strong>多页应用（MPA）场景</strong>：如果是各自独立的 HTML 页面（比如 <code>app.html</code> 只引入 <code>app.js</code>，<code>admin.html</code> 只引入 <code>admin.js</code>），此时每个页面只加载一个入口脚本，运行时不会冲突。</li><li><strong>同一页面加载多入口场景</strong>：而在本例的 <code>dependOn</code> 配置中，<code>shared.js</code> 和 <code>app.js</code> 通常会被同时注入到<strong>同一个</strong> HTML 页面中。这种情况下，必须设置 <code>optimization.runtimeChunk: &#39;single&#39;</code>，提取出一个公共的 Webpack 运行时（Runtime）。否则 <code>shared</code> 和 <code>app</code> 将各自包含一套独立的 Webpack 模块加载与解析逻辑，这会导致全局变量冲突、模块重复实例化等严重的意外问题。</li><li><strong>单页面应用（SPA）适用吗？</strong>：<strong>不适用，也没有必要。</strong> 绝大多数 Vue&#x2F;React 单页面应用通常只有一个核心入口文件（如 <code>src/main.js</code> 或 <code>src/index.js</code>）。既然没有“多个”入口，就不存在“跨入口提取共享模块”的需求。对于 SPA：<ul><li>若想抽离第三方库（如 <code>vue</code>, <code>react</code>, <code>lodash</code>），请使用下一节介绍的 <strong>SplitChunksPlugin</strong>。</li><li>若想实现路由级别的按需加载，请配合使用稍后介绍的<strong>动态导入（Dynamic Import）</strong>。</li></ul></li></ol></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...entry 配置同上</span></span><br><span class="line">  <span class="attr">optimization</span>: &#123;</span><br><span class="line">    <span class="attr">runtimeChunk</span>: <span class="string">&quot;single&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><hr><h2 id="四、SplitChunksPlugin-详解"><a href="#四、SplitChunksPlugin-详解" class="headerlink" title="四、SplitChunksPlugin 详解"></a>四、SplitChunksPlugin 详解</h2><h3 id="4-1-默认行为"><a href="#4-1-默认行为" class="headerlink" title="4.1 默认行为"></a><strong><font color='red'>4.1 默认行为</font></strong></h3><p><code>SplitChunksPlugin</code> 是 Webpack 内置的、也是<strong>最核心</strong>的代码分割工具。它会自动分析模块间的依赖关系，把满足条件的公共模块提取到单独的 chunk 中。</p><p>即使你不做任何配置，Webpack 5 也有一套默认的分割策略：</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><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Webpack 5 的默认配置（等价写法）</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">optimization</span>: &#123;</span><br><span class="line">    <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">      <span class="attr">chunks</span>: <span class="string">&quot;async&quot;</span>, <span class="comment">// 只对异步加载的模块进行分割</span></span><br><span class="line">      <span class="attr">minSize</span>: <span class="number">20000</span>, <span class="comment">// 生成 chunk 的最小体积（约 20KB）</span></span><br><span class="line">      <span class="attr">minRemainingSize</span>: <span class="number">0</span>, <span class="comment">// 分割后剩余 chunk 的最小体积</span></span><br><span class="line">      <span class="attr">minChunks</span>: <span class="number">1</span>, <span class="comment">// 模块至少被引用 1 次才会被分割</span></span><br><span class="line">      <span class="attr">maxAsyncRequests</span>: <span class="number">30</span>, <span class="comment">// 按需加载时最大并行请求数</span></span><br><span class="line">      <span class="attr">maxInitialRequests</span>: <span class="number">30</span>, <span class="comment">// 入口点的最大并行请求数</span></span><br><span class="line">      <span class="attr">enforceSizeThreshold</span>: <span class="number">50000</span>, <span class="comment">// 超过50kb强制分割，忽略其他限制</span></span><br><span class="line">      <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">        <span class="comment">// 组，哪些模块要打包到一个组</span></span><br><span class="line">        <span class="attr">defaultVendors</span>: &#123;</span><br><span class="line">          <span class="comment">// 组名</span></span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>, <span class="comment">// 需要打包到一起的模块</span></span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">10</span>, <span class="comment">// 优先级（越大越高）</span></span><br><span class="line">          <span class="attr">reuseExistingChunk</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">default</span>: &#123;</span><br><span class="line">          <span class="attr">minChunks</span>: <span class="number">2</span>, <span class="comment">// 模块至少被引用 2 次才会被分割</span></span><br><span class="line">          <span class="attr">priority</span>: -<span class="number">20</span>,</span><br><span class="line">          <span class="attr">reuseExistingChunk</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</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><blockquote><p><strong>关键点</strong>：默认 <code>chunks: &#39;async&#39;</code> 意味着<strong>只对动态 <code>import()</code> 导入的模块生效</strong>。如果你也想提取同步引用的第三方库（比如 <code>import lodash from &#39;lodash&#39;</code>），需要改成 <code>&#39;all&#39;</code>。</p></blockquote><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260303222456796.png" alt="image-20260303222456796"></p><h3 id="4-2-chunks-选项详解"><a href="#4-2-chunks-选项详解" class="headerlink" title="4.2 chunks 选项详解"></a><strong><font color='red'>4.2 chunks 选项详解</font></strong></h3><p><code>chunks</code> 是一个非常关键的选项，它决定了哪些 chunk 参与分割优化：</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></pre></td><td class="code"><pre><span class="line"><span class="attr">splitChunks</span>: &#123;</span><br><span class="line">  <span class="comment">// 字符串形式</span></span><br><span class="line">  <span class="attr">chunks</span>: <span class="string">&quot;async&quot;</span>; <span class="comment">// 默认值，只处理异步 chunk（动态 import）</span></span><br><span class="line">  <span class="attr">chunks</span>: <span class="string">&quot;initial&quot;</span>; <span class="comment">// 只处理同步 chunk（静态 import）</span></span><br><span class="line">  <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>; <span class="comment">// 推荐！同步和异步 chunk 都处理</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>三种模式的区别（以 lodash 为例）：</strong></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">// 场景：入口文件同步引用了 lodash</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&quot;lodash&quot;</span>;</span><br></pre></td></tr></table></figure><table><thead><tr><th>chunks 值</th><th>效果</th></tr></thead><tbody><tr><td><code>&#39;async&#39;</code></td><td>lodash <strong>不会</strong>被单独提取（因为它是同步引入的）</td></tr><tr><td><code>&#39;initial&#39;</code></td><td>lodash <strong>会</strong>被提取到 vendor chunk</td></tr><tr><td><code>&#39;all&#39;</code></td><td>lodash <strong>会</strong>被提取到 vendor chunk</td></tr></tbody></table><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">// 场景：代码中动态引用了 lodash</span></span><br><span class="line"><span class="keyword">const</span> _ = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;lodash&quot;</span>);</span><br></pre></td></tr></table></figure><table><thead><tr><th>chunks 值</th><th>效果</th></tr></thead><tbody><tr><td><code>&#39;async&#39;</code></td><td>lodash <strong>会</strong>被提取为独立 chunk</td></tr><tr><td><code>&#39;initial&#39;</code></td><td>lodash <strong>不会</strong>被提取（因为它是异步引入的）</td></tr><tr><td><code>&#39;all&#39;</code></td><td>lodash <strong>会</strong>被提取为独立 chunk</td></tr></tbody></table><p><code>chunks</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></pre></td><td class="code"><pre><span class="line"><span class="attr">splitChunks</span>: &#123;</span><br><span class="line">  <span class="title function_">chunks</span>(<span class="params">chunk</span>) &#123;</span><br><span class="line">    <span class="comment">// 排除名为 &#x27;my-excluded-chunk&#x27; 的 chunk</span></span><br><span class="line">    <span class="keyword">return</span> chunk.<span class="property">name</span> !== <span class="string">&#x27;my-excluded-chunk&#x27;</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="4-3-cacheGroups-缓存组"><a href="#4-3-cacheGroups-缓存组" class="headerlink" title="4.3 cacheGroups 缓存组"></a><strong><font color='red'>4.3 cacheGroups 缓存组</font></strong></h3><p><code>cacheGroups</code>（缓存组）是 <code>SplitChunksPlugin</code> 的灵魂。它允许你自定义分组规则，指定哪些模块应该被归入哪个 chunk。</p><h4 id="1）匹配流程"><a href="#1）匹配流程" class="headerlink" title="1）匹配流程"></a><strong><font color='#10c300'>1）匹配流程</font></strong></h4><figure class="highlight plaintext"><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><br><span class="line">  ↓</span><br><span class="line">遍历所有 cacheGroup 规则</span><br><span class="line">  ↓</span><br><span class="line">模块匹配到多个 group？ → 按 priority 取最高者</span><br><span class="line">  ↓</span><br><span class="line">满足 minSize / minChunks 等条件？</span><br><span class="line">  ↓</span><br><span class="line">是 → 提取到对应 chunk</span><br><span class="line">否 → 留在原始 bundle 中</span><br></pre></td></tr></table></figure><h4 id="2）完整配置示例"><a href="#2）完整配置示例" class="headerlink" title="2）完整配置示例"></a><strong><font color='#10c300'>2）完整配置示例</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">    <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">    <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">      <span class="comment">// 第三方库统一打包</span></span><br><span class="line">      <span class="attr">vendors</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;vendors&#x27;</span>,</span><br><span class="line">        <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">        <span class="attr">priority</span>: <span class="number">10</span></span><br><span class="line">      &#125;,</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 将 React 全家桶单独打包（因为版本稳定，利于长期缓存）</span></span><br><span class="line">      <span class="attr">react</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](react|react-dom|react-router|react-router-dom)[\\/]/</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;react-vendor&#x27;</span>,</span><br><span class="line">        <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">        <span class="attr">priority</span>: <span class="number">20</span>  <span class="comment">// 优先级高于 vendors，所以 React 相关的不会被归到 vendors</span></span><br><span class="line">      &#125;,</span><br><span class="line"></span><br><span class="line">      <span class="comment">// UI 组件库单独打包</span></span><br><span class="line">      <span class="attr">antd</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](antd|@ant-design)[\\/]/</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;antd-vendor&#x27;</span>,</span><br><span class="line">        <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">        <span class="attr">priority</span>: <span class="number">20</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="attr">commons</span>: &#123;</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;commons&#x27;</span>,</span><br><span class="line">        <span class="attr">minChunks</span>: <span class="number">2</span>,     <span class="comment">// 至少被 2 个 chunk 引用</span></span><br><span class="line">        <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">        <span class="attr">priority</span>: <span class="number">5</span>,</span><br><span class="line">        <span class="attr">reuseExistingChunk</span>: <span class="literal">true</span></span><br><span class="line">      &#125;</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><h4 id="3）cacheGroup-的核心属性"><a href="#3）cacheGroup-的核心属性" class="headerlink" title="3）cacheGroup 的核心属性"></a><strong><font color='#10c300'>3）cacheGroup 的核心属性</font></strong></h4><table><thead><tr><th>属性</th><th>类型</th><th>说明</th></tr></thead><tbody><tr><td><code>test</code></td><td>RegExp &#x2F; Function &#x2F; String</td><td>模块匹配规则</td></tr><tr><td><code>name</code></td><td>String &#x2F; Function</td><td>生成的 chunk 名称</td></tr><tr><td><code>priority</code></td><td>Number</td><td>优先级，数值越大越优先匹配</td></tr><tr><td><code>minChunks</code></td><td>Number</td><td>模块最少被多少个 chunk 引用</td></tr><tr><td><code>reuseExistingChunk</code></td><td>Boolean</td><td>如果当前 chunk 包含已从 main bundle 中拆分出来的模块，则复用</td></tr><tr><td><code>enforce</code></td><td>Boolean</td><td><code>true</code> 时忽略 <code>minSize</code>、<code>maxSize</code> 等限制，强制创建 chunk</td></tr></tbody></table><h4 id="4）深度解析：reuseExistingChunk"><a href="#4）深度解析：reuseExistingChunk" class="headerlink" title="4）深度解析：reuseExistingChunk"></a><strong><font color='#10c300'>4）深度解析：reuseExistingChunk</font></strong></h4><p><code>reuseExistingChunk: true</code> 这个配置虽然默认开启，但很多人不理解它的具体作用。</p><p><strong>它的核心思想是：避免重复打包已经被分离出去的模块。</strong></p><p><strong>🤔 场景演示：</strong><br>假设你有三个文件：<code>A.js</code>, <code>B.js</code> 和一个公共工具模块 <code>utils.js</code>。</p><ol><li><code>A.js</code> 引用了 <code>utils.js</code>。</li><li><code>B.js</code> 引用了 <code>A.js</code>，同时也直接引用了 <code>utils.js</code>。</li></ol><p>在配置 <code>SplitChunksPlugin</code> 提取公共模块时：</p><ul><li>Webpack 在处理 <code>A.js</code> 时，决定把 <code>utils.js</code> 提取出来，单独打成一个叫 <code>chunk-utils.js</code> 的文件。</li><li>接着 Webpack 处理 <code>B.js</code>。按照依赖分析，<code>B.js</code> 需要 <code>A.js</code> 和 <code>utils.js</code>。</li><li>此时，Webpack 发现 <code>B.js</code> 需要的 <code>utils.js</code> <strong>已经</strong>在这个打包流程中被别人（<code>A.js</code>）提取成了一个独立的 Chunk (<code>chunk-utils.js</code>) 了。</li></ul><p><strong>如果不开启 <code>reuseExistingChunk: false</code></strong>：<br>Webpack 可能会比较笨地再为 <code>B.js</code> 把 <code>utils.js</code> 重新打包一次（或者打包进 <code>B.js</code> 内部，或者生成一个新的重复 chunk）。</p><p><strong>如果开启 <code>reuseExistingChunk: true</code>（推荐）</strong>：<br>Webpack 会很聪明地说：“哦，我已经有一个包含 <code>utils.js</code> 的 Chunk 了，那 <code>B.js</code> 直接去复用那个建立好的 <code>chunk-utils.js</code> 就行了，没必要再单独创建一份。”这样就极大地减小了打包体积，也提高了打包效率。</p><h4 id="5）name-的函数用法"><a href="#5）name-的函数用法" class="headerlink" title="5）name 的函数用法"></a><strong><font color='#10c300'>5）name 的函数用法</font></strong></h4><p><code>name</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">  <span class="attr">vendors</span>: &#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">    <span class="comment">// 根据模块路径生成 chunk 名称</span></span><br><span class="line">    <span class="title function_">name</span>(<span class="params"><span class="variable language_">module</span></span>) &#123;</span><br><span class="line">      <span class="comment">// 获取包名，例如 node_modules/lodash/lodash.js → lodash</span></span><br><span class="line">      <span class="keyword">const</span> packageName = <span class="variable language_">module</span>.<span class="property">context</span>.<span class="title function_">match</span>(</span><br><span class="line">        <span class="regexp">/[\\/]node_modules[\\/](.*?)([\\/]|$)/</span></span><br><span class="line">      )[<span class="number">1</span>]</span><br><span class="line">      <span class="comment">// npm 包名允许有 @ 前缀（如 @babel/core），但 @ 在 URL 中不安全</span></span><br><span class="line">      <span class="keyword">return</span> <span class="string">`vendor.<span class="subst">$&#123;packageName.replace(<span class="string">&#x27;@&#x27;</span>, <span class="string">&#x27;&#x27;</span>)&#125;</span>`</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><blockquote><p><strong>⚠️ 注意</strong>：在生产环境中不建议使用函数形式的 <code>name</code>，因为每个包一个 chunk 会导致 HTTP 请求过多。这里仅作演示。</p></blockquote><h3 id="4-4-体积相关的关键参数"><a href="#4-4-体积相关的关键参数" class="headerlink" title="4.4 体积相关的关键参数"></a><strong><font color='red'>4.4 体积相关的关键参数</font></strong></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="attr">splitChunks</span>: &#123;</span><br><span class="line">  <span class="attr">minSize</span>: <span class="number">20000</span>,            <span class="comment">// chunk 最小体积 20KB，低于则不分割</span></span><br><span class="line">  <span class="attr">maxSize</span>: <span class="number">250000</span>,           <span class="comment">// chunk 建议最大 250KB，超过则尝试继续拆分</span></span><br><span class="line">  <span class="attr">maxAsyncRequests</span>: <span class="number">30</span>,      <span class="comment">// 异步加载时的最大并行请求数</span></span><br><span class="line">  <span class="attr">maxInitialRequests</span>: <span class="number">30</span>,    <span class="comment">// 入口的最大并行请求数</span></span><br><span class="line">  <span class="attr">enforceSizeThreshold</span>: <span class="number">50000</span> <span class="comment">// 强制分割阈值：超过 50KB 一定分割</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>maxSize 的工作方式：</strong></p><p><code>maxSize</code> 并不是一个硬限制（一个 chunk 可能因为无法继续拆分而超过此值），但 Webpack 会 <strong>尽量</strong> 把超出的 chunk 再次拆分。优先级关系为：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">minSize（保底下限）&gt; maxSize（建议上限）&gt; maxInitialRequests/maxAsyncRequests（请求数上限）</span><br></pre></td></tr></table></figure><p>也就是说，Webpack 绝不会因为 <code>maxSize</code> 而创建小于 <code>minSize</code> 的 chunk。</p><h3 id="4-5-runtimeChunk-运行时代码提取"><a href="#4-5-runtimeChunk-运行时代码提取" class="headerlink" title="4.5 runtimeChunk 运行时代码提取"></a><strong><font color='red'>4.5 runtimeChunk 运行时代码提取</font></strong></h3><p>Webpack 的 <strong>runtime（运行时）</strong> 是指模块加载、解析、执行的基础代码。把它提取为单独文件可以避免在业务代码变化时导致 runtime 所在的 chunk hash 变化：</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="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="comment">// 方式一：所有入口共享一个 runtime chunk</span></span><br><span class="line">  <span class="attr">runtimeChunk</span>: <span class="string">&quot;single&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 方式二：每个入口各自拥有 runtime chunk</span></span><br><span class="line">  <span class="comment">// runtimeChunk: true</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 方式三：自定义名称</span></span><br><span class="line">  <span class="comment">// runtimeChunk: &#123;</span></span><br><span class="line">  <span class="comment">//   name: (entrypoint) =&gt; `runtime-$&#123;entrypoint.name&#125;`</span></span><br><span class="line">  <span class="comment">// &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>推荐使用 <code>&#39;方式一和方式三&#39;</code></strong>，特别是对于单页面应用。</p><h4 id="深度解析：为什么需要提取-runtimeChunk？"><a href="#深度解析：为什么需要提取-runtimeChunk？" class="headerlink" title="深度解析：为什么需要提取 runtimeChunk？"></a><strong><font color='#10c300'>深度解析：为什么需要提取 runtimeChunk？</font></strong></h4><p><strong>🤔 场景演示：</strong><br>假设你的项目有两个文件：入口文件 <code>main.js</code> 和一个按需加载的 <code>About.js</code>（动态导入）。</p><ol><li><p><strong>如果不提取（默认情况）</strong>：</p><ul><li>Webpack 会把“模块映射关系”（即 runtime）打包进 <code>main.js</code>。</li><li>如果你修改了 <code>About.js</code> 的内容，<code>About.js</code> 的 hash 肯定会变。</li><li><strong>关键点</strong>：由于 <code>main.js</code> 内部记录了 <code>About.js</code> 的新文件名，导致 <code>main.js</code> 的内容也发生了微小变化，因此 <code>main.js</code> 的 hash 也会被迫改变。</li><li><strong>结果</strong>：用户本来只需要重新下载最新的 <code>About.js</code>，现在却因为 runtime 的变动，不得不重新下载几百 KB 的 <code>main.js</code>（即使业务逻辑没变）。</li></ul></li><li><p><strong>如果提取（runtimeChunk: ‘single’）</strong>：</p><ul><li>Webpack 会产生一个微小的 <code>runtime.js</code>（专门存放映射关系）。</li><li>如果你修改了 <code>About.js</code>，此时只有 <code>runtime.js</code>（记录了新映射）和 <code>About.js</code> 本身的 hash 会变。</li><li><strong>结果</strong>：主逻辑 <code>main.js</code> 的内容保持绝对不变，它的 hash 也不会变。这意味着浏览器可以直接从本地缓存读取 <code>main.js</code>，大大提升了二次访问速度。</li></ul></li></ol><blockquote><p>[!TIP]<br><strong>结论</strong>：在追求“长效缓存（Long-term Caching）”的项目中，提取 <code>runtime</code> 是标配操作。它通过牺牲一次微小的 HTTP 请求（<code>runtime.js</code> 通常只有几 KB），换取了大型业务 chunk 的缓存稳定性。</p></blockquote><h4 id="runtime-js-内部到底保存了什么？"><a href="#runtime-js-内部到底保存了什么？" class="headerlink" title="runtime.js 内部到底保存了什么？"></a><strong><font color='#10c300'>runtime.js 内部到底保存了什么？</font></strong></h4><p>简单来说，<code>runtime.js</code> 是 Webpack 在浏览器中的 <strong>“总调度指挥部”</strong>。它的核心内容通常包括：</p><ol><li><strong>模块映射表（Manifest）</strong>：<br>这是最重要的部分。它记录了所有模块 ID 与其对应的文件 Hash 之间的映射关系。<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">&#123;</span><br><span class="line">  <span class="string">&quot;about&quot;</span>: <span class="string">&quot;about.8f2d1a3b.chunk.js&quot;</span>,</span><br><span class="line">  <span class="string">&quot;home&quot;</span>: <span class="string">&quot;home.c5e6d7f8.chunk.js&quot;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure></li><li><strong>核心加载逻辑</strong>：<br>包含 Webpack 的模块加载函数（如 <code>__webpack_require__</code>）。它负责管理模块的初始化、缓存以及如何处理模块之间的循环依赖。</li><li><strong>异步分包加载指令</strong>：<br>提供处理动态 <code>import()</code> 的底层代码。例如，它包含了如何动态创建 <code>&lt;script&gt;</code> 标签、如何监听下载进度、以及如何在脚本下载报错时进行处理的逻辑。</li><li><strong>环境支撑脚本</strong>：<br>如果是开发环境，它还包含了支持 <strong>热更新（HMR）</strong> 的通信逻辑。</li></ol><h4 id="Hash-稳定性的精髓：间接引用"><a href="#Hash-稳定性的精髓：间接引用" class="headerlink" title="Hash 稳定性的精髓：间接引用"></a><strong><font color='#10c300'>Hash 稳定性的精髓：间接引用</font></strong></h4><figure class="highlight plaintext"><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">// 伪代码示例：映射表 `runtime.js` </span><br><span class="line">&#123;</span><br><span class="line">  &quot;about&quot;: &quot;about.8f2d1a3b.chunk.js&quot;,</span><br><span class="line">  &quot;home&quot;: &quot;home.c5e6d7f8.chunk.js&quot;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>我们可以用 <strong>“查字典”</strong> 来类比这个过程：</p><ul><li><strong><code>main.js</code>（读者）</strong>：它手里拿的是<strong>模块 ID</strong>（索引）。比如它知道自己需要 <code>about</code> 这个模块，代码里写的是 <code>__webpack_require__.e(&quot;about&quot;)</code>。</li><li><strong><code>runtime.js</code>（字典）</strong>：它保存了最新的<strong>页码映射</strong>（映射表）。它告诉读者：“你要找的 <code>about</code>，现在在 <code>about.8f2d1a3b.js</code> 这一页。”</li></ul><p><strong>为什么这样就能保护缓存？</strong></p><ol><li><strong>解耦</strong>：<code>main.js</code> 不再直接写死对方带 Hash 的完整文件名，而是通过一个<strong>永远不变的 ID</strong>（如数字 ID 或具名 ID）去询问 <code>runtime.js</code>。</li><li><strong>局部更新</strong>：当你修改了 <code>about.js</code>，它的 Hash 变了（变成 <code>8f2d1a3b</code> -&gt; <code>9ec2b10a</code>），字典（<code>runtime.js</code>）会更新这一行记录。</li><li><strong>缓存命中</strong>：因为 <code>main.js</code> 里的代码 <code>__webpack_require__.e(&quot;about&quot;)</code> <strong>一个字都没改</strong>，所以它的 Hash 也不会变。浏览器发现 <code>main.js</code> 没变，就会直接命中磁盘缓存，不需要重新下载！</li></ol><blockquote><p>[!IMPORTANT]<br><strong>一句话总结</strong>：<code>runtime.js</code> 充当了 Hash 变动的**“缓冲区”**。它吸收了所有因依赖版本更新而产生的 Hash 波动，从而保护了上层业务逻辑（<code>main.js</code>）的缓存有效性。</p></blockquote><hr><br><h2 id="五、动态导入（Dynamic-Import）"><a href="#五、动态导入（Dynamic-Import）" class="headerlink" title="五、动态导入（Dynamic Import）"></a><strong>五、动态导入（Dynamic Import）</strong></h2><h3 id="5-1-基础用法"><a href="#5-1-基础用法" class="headerlink" title="5.1 基础用法"></a><strong><font color='red'>5.1 基础用法</font></strong></h3><p>动态导入是实现<strong>按需加载</strong>的核心手段。使用 ES 标准的 <code>import()</code> 语法, Webpack 会自动将目标模块拆分为独立的 chunk，并在运行时按需加载。</p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 点击按钮时才加载并使用 lodash</span></span><br><span class="line"><span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;btn&quot;</span>).<span class="title function_">addEventListener</span>(<span class="string">&quot;click&quot;</span>, <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="comment">// import() 返回一个 Promise</span></span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">default</span>: _ &#125; = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;lodash&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> result = _.<span class="title function_">join</span>([<span class="string">&quot;Hello&quot;</span>, <span class="string">&quot;webpack&quot;</span>], <span class="string">&quot; &quot;</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(result);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p><code>import(&#39;lodash&#39;)</code> 做了两件事：</p><ol><li>在打包阶段，Webpack 会把 <code>lodash</code> 及其依赖从主 bundle 中<strong>拆分</strong>出来，生成一个独立的 chunk 文件。</li><li>在运行阶段，当代码执行到 <code>import(&#39;lodash&#39;)</code> 时，才会通过 <code>&lt;script&gt;</code> 标签<strong>动态下载</strong>并执行这个 chunk。</li></ol><h3 id="5-2-路由级懒加载（React）"><a href="#5-2-路由级懒加载（React）" class="headerlink" title="5.2 路由级懒加载（React）"></a><strong><font color='red'>5.2 路由级懒加载（React）</font></strong></h3><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><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; lazy, <span class="title class_">Suspense</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">BrowserRouter</span>, <span class="title class_">Routes</span>, <span class="title class_">Route</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 lazy + import() 实现路由级代码分割</span></span><br><span class="line"><span class="comment">// 每个路由组件都会被打包为独立的 chunk</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Home</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./pages/Home&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">About</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./pages/About&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Dashboard</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./pages/Dashboard&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">UserProfile</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./pages/UserProfile&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">App</span> = (<span class="params"></span>) =&gt; &#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">BrowserRouter</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* Suspense 提供加载过渡 UI */&#125;</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">div</span>&gt;</span>页面加载中...<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Routes</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&quot;/&quot;</span> <span class="attr">element</span>=<span class="string">&#123;</span>&lt;<span class="attr">Home</span> /&gt;</span>&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&quot;/about&quot;</span> <span class="attr">element</span>=<span class="string">&#123;</span>&lt;<span class="attr">About</span> /&gt;</span>&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&quot;/dashboard&quot;</span> <span class="attr">element</span>=<span class="string">&#123;</span>&lt;<span class="attr">Dashboard</span> /&gt;</span>&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">Route</span> <span class="attr">path</span>=<span class="string">&quot;/user/:id&quot;</span> <span class="attr">element</span>=<span class="string">&#123;</span>&lt;<span class="attr">UserProfile</span> /&gt;</span>&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Routes</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">BrowserRouter</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="5-3-路由级懒加载（Vue）"><a href="#5-3-路由级懒加载（Vue）" class="headerlink" title="5.3 路由级懒加载（Vue）"></a><strong><font color='red'>5.3 路由级懒加载（Vue）</font></strong></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><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="comment">// router/index.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createRouter, createWebHistory &#125; <span class="keyword">from</span> <span class="string">&quot;vue-router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;Home&quot;</span>,</span><br><span class="line">    <span class="comment">// Vue 天生支持异步组件，直接使用 import() 即可</span></span><br><span class="line">    <span class="attr">component</span>: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../views/Home.vue&quot;</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/about&quot;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;About&quot;</span>,</span><br><span class="line">    <span class="attr">component</span>: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../views/About.vue&quot;</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/dashboard&quot;</span>,</span><br><span class="line">    <span class="attr">name</span>: <span class="string">&quot;Dashboard&quot;</span>,</span><br><span class="line">    <span class="attr">component</span>: <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../views/Dashboard.vue&quot;</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">createRouter</span>(&#123;</span><br><span class="line">  <span class="attr">history</span>: <span class="title function_">createWebHistory</span>(),</span><br><span class="line">  routes,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="5-4-条件性按需加载"><a href="#5-4-条件性按需加载" class="headerlink" title="5.4 条件性按需加载"></a><strong><font color='red'>5.4 条件性按需加载</font></strong></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><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="comment">// 场景一：用户触发了某个功能后才加载对应的库</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">handleExport</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="comment">// xlsx 库体积很大，只有用户点击&quot;导出&quot;时才加载</span></span><br><span class="line">  <span class="keyword">const</span> <span class="variable constant_">XLSX</span> = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;xlsx&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> worksheet = <span class="variable constant_">XLSX</span>.<span class="property">utils</span>.<span class="title function_">json_to_sheet</span>(data);</span><br><span class="line">  <span class="keyword">const</span> workbook = <span class="variable constant_">XLSX</span>.<span class="property">utils</span>.<span class="title function_">book_new</span>();</span><br><span class="line">  <span class="variable constant_">XLSX</span>.<span class="property">utils</span>.<span class="title function_">book_append_sheet</span>(workbook, worksheet, <span class="string">&quot;Sheet1&quot;</span>);</span><br><span class="line">  <span class="variable constant_">XLSX</span>.<span class="title function_">writeFile</span>(workbook, <span class="string">&quot;export.xlsx&quot;</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="keyword">const</span> <span class="title function_">loadEditor</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="title function_">isMobile</span>()) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">import</span>(<span class="string">&quot;./editors/MobileEditor&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">import</span>(<span class="string">&quot;./editors/DesktopEditor&quot;</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="keyword">const</span> <span class="title function_">renderChart</span> = <span class="keyword">async</span> (<span class="params">container, data</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> echarts = <span class="keyword">await</span> <span class="keyword">import</span>(<span class="string">&quot;echarts&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> chart = echarts.<span class="title function_">init</span>(container);</span><br><span class="line">  chart.<span class="title function_">setOption</span>(&#123;</span><br><span class="line">    <span class="comment">/* ... */</span></span><br><span class="line">  &#125;);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="5-5-output-chunkFilename-—-非入口-chunk-的命名模板"><a href="#5-5-output-chunkFilename-—-非入口-chunk-的命名模板" class="headerlink" title="5.5 output.chunkFilename — 非入口 chunk 的命名模板"></a><strong><font color='red'>5.5 output.chunkFilename — 非入口 chunk 的命名模板</font></strong></h3><p>配合下，Webpack 会产出大量<strong>非入口 chunk</strong>。<code>output.chunkFilename</code> 就是专门控制这些文件命名格式的配置项，它<strong>可以独立使用，不依赖任何魔法注释</strong>。</p><h4 id="1）filename-vs-chunkFilename-的分工"><a href="#1）filename-vs-chunkFilename-的分工" class="headerlink" title="1）filename vs chunkFilename 的分工"></a><strong><font color='#10c300'>1）filename vs chunkFilename 的分工</font></strong></h4><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="attr">output</span>: &#123;</span><br><span class="line">  <span class="comment">// filename —— 只管【入口 chunk】的文件名（entry 直接对应的输出文件）</span></span><br><span class="line">  <span class="attr">filename</span>: <span class="string">&#x27;[name].[contenthash:8].js&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// chunkFilename —— 只管【非入口 chunk】的文件名</span></span><br><span class="line">  <span class="comment">//   包括：SplitChunksPlugin 拆出来的 + 动态 import() 产生的</span></span><br><span class="line">  <span class="attr">chunkFilename</span>: <span class="string">&#x27;[name].[contenthash:8].chunk.js&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><table><thead><tr><th>配置项</th><th>作用对象</th><th>输出示例</th></tr></thead><tbody><tr><td><code>filename</code></td><td><strong>入口 chunk</strong>（entry 直接对应的）</td><td><code>app.a1b2c3d4.js</code></td></tr><tr><td><code>chunkFilename</code></td><td><strong>非入口 chunk</strong>（SplitChunksPlugin 拆的 + 动态 import 的）</td><td><code>vendors.e5f6g7h8.chunk.js</code></td></tr></tbody></table><h4 id="2）-name-占位符的值由谁决定？"><a href="#2）-name-占位符的值由谁决定？" class="headerlink" title="2）[name] 占位符的值由谁决定？"></a><strong><font color='#10c300'>2）[name] 占位符的值由谁决定？</font></strong></h4><p><code>chunkFilename</code> 模板中最关键的占位符是 <code>[name]</code>。它的值<strong>并非固定</strong>，而是取决于 chunk 的来源：</p><table><thead><tr><th>chunk 来源</th><th><code>[name]</code> 的值</th><th>文件名示例</th></tr></thead><tbody><tr><td>SplitChunksPlugin 的 cacheGroup</td><td>cacheGroup 中配置的 <code>name</code> 属性值</td><td><code>vendors.abcd1234.chunk.js</code></td></tr><tr><td>动态 <code>import()</code> + <code>webpackChunkName</code> 注释</td><td>注释中指定的名称</td><td><code>about-page.abcd1234.chunk.js</code></td></tr><tr><td>动态 <code>import()</code> <strong>无任何注释</strong></td><td>Webpack 自动分配的数字 ID</td><td><code>543.abcd1234.chunk.js</code></td></tr></tbody></table><figure class="highlight plaintext"><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">chunkFilename: &#x27;[name].[contenthash:8].chunk.js&#x27;</span><br><span class="line">                  ↑</span><br><span class="line">            ┌─────┴──────────────────────────────────┐</span><br><span class="line">            │  SplitChunksPlugin 的 cacheGroup       │ → name 属性值（如 &#x27;vendors&#x27;）</span><br><span class="line">            │  动态 import() + webpackChunkName 注释  │ → 注释指定的名称</span><br><span class="line">            │  动态 import() 无任何注释                │ → Webpack 自动分配的数字 ID</span><br><span class="line">            └────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><h4 id="3）不配置-chunkFilename-会怎样？"><a href="#3）不配置-chunkFilename-会怎样？" class="headerlink" title="3）不配置 chunkFilename 会怎样？"></a><strong><font color='#10c300'>3）不配置 chunkFilename 会怎样？</font></strong></h4><p>如果你不显式设置 <code>chunkFilename</code>，Webpack 会<strong>回退到 <code>filename</code> 的值</strong>作为所有 chunk 的命名模板。这意味着入口文件和非入口 chunk 使用完全相同的命名规则，虽然功能上没有问题，但你会丧失通过文件名一眼区分”入口文件”和”拆分 chunk”的能力。</p><p><strong>推荐做法</strong>：给 <code>chunkFilename</code> 加上 <code>.chunk</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></pre></td><td class="code"><pre><span class="line"><span class="attr">output</span>: &#123;</span><br><span class="line">  <span class="attr">filename</span>: <span class="string">&#x27;js/[name].[contenthash:8].js&#x27;</span>,           <span class="comment">// 入口文件：app.a1b2c3d4.js</span></span><br><span class="line">  <span class="attr">chunkFilename</span>: <span class="string">&#x27;js/[name].[contenthash:8].chunk.js&#x27;</span>  <span class="comment">// 非入口：vendors.e5f6g7h8.chunk.js</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><br><h2 id="六、魔法注释（Magic-Comments）"><a href="#六、魔法注释（Magic-Comments）" class="headerlink" title="六、魔法注释（Magic Comments）"></a><strong>六、魔法注释（Magic Comments）</strong></h2><p>Webpack 在 <code>import()</code> 中支持一系列特殊注释（魔法注释），用来精确控制分割行为。</p><h3 id="6-1-webpackChunkName-—-自定义-chunk-名称"><a href="#6-1-webpackChunkName-—-自定义-chunk-名称" class="headerlink" title="6.1 webpackChunkName — 自定义 chunk 名称"></a><strong><font color='red'>6.1 webpackChunkName — 自定义 chunk 名称</font></strong></h3><p>默认情况下，动态 <code>import()</code> 产生的 chunk 会被分配一个纯数字 ID 作为名称（如 <code>543</code>）。通过 <code>webpackChunkName</code> 注释，你可以将其替换为有意义的可读名称，这个名称会填入上一节介绍的 <code>chunkFilename</code> 模板中的 <code>[name]</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 不加注释：[name] = 数字 ID → 生成 543.abcd1234.chunk.js</span></span><br><span class="line"><span class="keyword">import</span>(<span class="string">&quot;./pages/About&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加注释后：[name] = &#x27;about-page&#x27; → 生成 about-page.abcd1234.chunk.js</span></span><br><span class="line"><span class="keyword">import</span>(<span class="comment">/* webpackChunkName: &quot;about-page&quot; */</span> <span class="string">&quot;./pages/About&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="6-2-webpackMode-—-控制动态导入模式"><a href="#6-2-webpackMode-—-控制动态导入模式" class="headerlink" title="6.2 webpackMode — 控制动态导入模式"></a><strong><font color='red'>6.2 webpackMode — 控制动态导入模式</font></strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// lazy：默认模式，为每个 import() 调用生成一个可延迟加载的 chunk</span></span><br><span class="line"><span class="keyword">import</span>(<span class="comment">/* webpackMode: &quot;lazy&quot; */</span> <span class="string">`./locales/<span class="subst">$&#123;language&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// lazy-once：只生成一个 chunk，适用于可能被请求多次的动态表达式</span></span><br><span class="line"><span class="comment">// 所有可能匹配到的模块都打包到同一个 chunk 中</span></span><br><span class="line"><span class="keyword">import</span>(<span class="comment">/* webpackMode: &quot;lazy-once&quot; */</span> <span class="string">`./locales/<span class="subst">$&#123;language&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// eager：不生成额外 chunk，模块被包含在当前 chunk 中</span></span><br><span class="line"><span class="comment">// 但仍然返回 Promise，适用于条件导入但不想多一次网络请求</span></span><br><span class="line"><span class="keyword">import</span>(<span class="comment">/* webpackMode: &quot;eager&quot; */</span> <span class="string">`./locales/<span class="subst">$&#123;language&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// weak：如果模块已经被其他方式加载了则使用它，否则 Promise 会 reject</span></span><br><span class="line"><span class="comment">// 适用于服务端渲染（SSR），避免客户端额外请求</span></span><br><span class="line"><span class="keyword">import</span>(<span class="comment">/* webpackMode: &quot;weak&quot; */</span> <span class="string">`./locales/<span class="subst">$&#123;language&#125;</span>`</span>);</span><br></pre></td></tr></table></figure><h3 id="6-3-webpackInclude-webpackExclude-—-过滤动态路径"><a href="#6-3-webpackInclude-webpackExclude-—-过滤动态路径" class="headerlink" title="6.3 webpackInclude &#x2F; webpackExclude — 过滤动态路径"></a><strong><font color='red'>6.3 webpackInclude &#x2F; webpackExclude — 过滤动态路径</font></strong></h3><p>当 <code>import()</code> 的路径包含变量时，Webpack 会把目录下所有可能匹配的文件都打包。用这两个注释可以缩小范围：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 假设 ./locales/ 目录下有 zh.js, en.js, fr.js, de.js, README.md 等文件</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 不过滤：所有文件都会被打包为可能的 chunk（包括 README.md）</span></span><br><span class="line"><span class="keyword">import</span>(<span class="string">`./locales/<span class="subst">$&#123;lang&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 过滤后：只打包 .js 结尾的文件</span></span><br><span class="line"><span class="keyword">import</span>(</span><br><span class="line">  <span class="comment">/* webpackInclude: /\.js$/ */</span></span><br><span class="line">  <span class="string">`./locales/<span class="subst">$&#123;lang&#125;</span>`</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 排除指定文件</span></span><br><span class="line"><span class="keyword">import</span>(</span><br><span class="line">  <span class="comment">/* webpackExclude: /\.test\.js$/ */</span></span><br><span class="line">  <span class="string">`./locales/<span class="subst">$&#123;lang&#125;</span>`</span></span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 组合使用</span></span><br><span class="line"><span class="keyword">import</span>(</span><br><span class="line">  <span class="comment">/* webpackInclude: /\.json$/ */</span></span><br><span class="line">  <span class="comment">/* webpackExclude: /deprecated/ */</span></span><br><span class="line">  <span class="string">`./data/<span class="subst">$&#123;filename&#125;</span>`</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="6-4-webpackExports-—-只导出指定成员"><a href="#6-4-webpackExports-—-只导出指定成员" class="headerlink" title="6.4 webpackExports — 只导出指定成员"></a><strong><font color='red'>6.4 webpackExports — 只导出指定成员</font></strong></h3><p>当你只使用模块的部分导出时，这个注释可以帮助 Webpack 做更激进的 Tree Shaking：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 只使用 lodash-es 的 debounce 和 throttle</span></span><br><span class="line"><span class="comment">// Webpack 在打包时可以排除其他不相关的导出</span></span><br><span class="line"><span class="keyword">import</span>(</span><br><span class="line">  <span class="comment">/* webpackExports: [&quot;debounce&quot;, &quot;throttle&quot;] */</span></span><br><span class="line">  <span class="string">&quot;lodash-es&quot;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><h3 id="6-5-完整示例：组合使用魔法注释"><a href="#6-5-完整示例：组合使用魔法注释" class="headerlink" title="6.5 完整示例：组合使用魔法注释"></a><strong><font color='red'>6.5 完整示例：组合使用魔法注释</font></strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 一个路由级懒加载的典型配置</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AdminDashboard</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span></span><br><span class="line">    <span class="keyword">import</span>(</span><br><span class="line">      <span class="comment">/* webpackChunkName: &quot;admin-dashboard&quot; */</span></span><br><span class="line">      <span class="comment">/* webpackPrefetch: true */</span></span><br><span class="line">      <span class="string">&quot;./pages/AdminDashboard&quot;</span></span><br><span class="line">    ),</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 国际化包的按需加载</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">loadLocale</span> = (<span class="params">lang</span>) =&gt;</span><br><span class="line">  <span class="keyword">import</span>(</span><br><span class="line">    <span class="comment">/* webpackChunkName: &quot;locale-[request]&quot; */</span></span><br><span class="line">    <span class="comment">/* webpackMode: &quot;lazy-once&quot; */</span></span><br><span class="line">    <span class="comment">/* webpackInclude: /\/(zh|en|ja)\.json$/ */</span></span><br><span class="line">    <span class="string">`./locales/<span class="subst">$&#123;lang&#125;</span>.json`</span></span><br><span class="line">  );</span><br></pre></td></tr></table></figure><hr><br><h2 id="七、代码分割统一命名规范"><a href="#七、代码分割统一命名规范" class="headerlink" title="七、代码分割统一命名规范"></a><strong>七、代码分割统一命名规范</strong></h2><h3 id="7-1-JS-文件命名"><a href="#7-1-JS-文件命名" class="headerlink" title="7.1 JS 文件命名"></a><strong><font color='red'>7.1 JS 文件命名</font></strong></h3><p>bundle 拆成多个文件。为了在构建产物中<strong>一眼区分主文件和分割出来的 chunk 文件</strong>，我们需要为 JS 和 CSS 分别制定统一的命名规范。</p><p>在 <code>output</code> 中，<code>filename</code> 和 <code>chunkFilename</code> 分别控制入口文件和分割 chunk 的命名：</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="attr">output</span>: &#123;</span><br><span class="line">  <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&#x27;dist&#x27;</span>),</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 入口文件（entry 直接对应的输出）</span></span><br><span class="line">  <span class="attr">filename</span>: <span class="string">&#x27;js/[name].[contenthash:8].js&#x27;</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 非入口 chunk（SplitChunksPlugin 拆的 + 动态 import 产生的）</span></span><br><span class="line">  <span class="comment">// 通过 .chunk 后缀与入口文件区分</span></span><br><span class="line">  <span class="attr">chunkFilename</span>: <span class="string">&#x27;js/[name].[contenthash:8].chunk.js&#x27;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>构建产物对比：</strong></p><figure class="highlight plaintext"><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">dist/js/</span><br><span class="line">├── app.a1b2c3d4.js              ← 入口文件（filename 控制）</span><br><span class="line">├── runtime.e5f6g7h8.js          ← 运行时（filename 控制）</span><br><span class="line">├── vendors.i9j0k1l2.chunk.js    ← 第三方库 chunk（chunkFilename 控制）</span><br><span class="line">├── commons.m3n4o5p6.chunk.js    ← 公共模块 chunk（chunkFilename 控制）</span><br><span class="line">└── about-page.q7r8s9t0.chunk.js ← 动态导入 chunk（chunkFilename 控制）</span><br></pre></td></tr></table></figure><p>看到 <code>.chunk.js</code> 后缀就知道这是代码分割产生的文件，没有 <code>.chunk</code> 的就是入口主文件。</p><h3 id="7-2-CSS-文件命名"><a href="#7-2-CSS-文件命名" class="headerlink" title="7.2 CSS 文件命名"></a><strong><font color='red'>7.2 CSS 文件命名</font></strong></h3><p>使用 <code>MiniCssExtractPlugin</code> 提取 CSS 时，同样存在 <code>filename</code> 和 <code>chunkFilename</code> 两个配置，逻辑与 JS 完全一致：</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">const</span> <span class="title class_">MiniCssExtractPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;mini-css-extract-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="attr">plugins</span>: [</span><br><span class="line">  <span class="keyword">new</span> <span class="title class_">MiniCssExtractPlugin</span>(&#123;</span><br><span class="line">    <span class="comment">// 入口 chunk 中提取的 CSS（如全局样式、主样式文件）</span></span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;css/[name].[contenthash:8].css&quot;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 非入口 chunk 中提取的 CSS（如动态导入的路由页面自带的样式）</span></span><br><span class="line">    <span class="comment">// 同样通过 .chunk 后缀与主 CSS 区分</span></span><br><span class="line">    <span class="attr">chunkFilename</span>: <span class="string">&quot;css/[name].[contenthash:8].chunk.css&quot;</span>,</span><br><span class="line">  &#125;),</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p><strong>构建产物对比：</strong></p><figure class="highlight plaintext"><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">dist/css/</span><br><span class="line">├── app.a1b2c3d4.css              ← 入口样式（filename 控制）</span><br><span class="line">├── about-page.e5f6g7h8.chunk.css ← 动态导入页面的样式（chunkFilename 控制）</span><br><span class="line">└── dashboard.i9j0k1l2.chunk.css  ← 动态导入页面的样式（chunkFilename 控制）</span><br></pre></td></tr></table></figure><blockquote><p><strong>为什么 CSS 也会被分割？</strong><br>当你使用动态 <code>import()</code> 加载一个路由页面时，如果该页面组件中引用了 CSS（例如 <code>import &#39;./About.css&#39;</code>），<code>MiniCssExtractPlugin</code> 会自动将这部分 CSS 也拆分为独立文件，与对应的 JS chunk 配对加载。所以 CSS 也需要 <code>chunkFilename</code> 来命名这些分割出来的样式文件。</p></blockquote><h3 id="7-3-完整命名规范汇总"><a href="#7-3-完整命名规范汇总" class="headerlink" title="7.3 完整命名规范汇总"></a><strong><font color='red'>7.3 完整命名规范汇总</font></strong></h3><table><thead><tr><th>资源类型</th><th>配置项</th><th>作用对象</th><th>推荐命名模板</th><th>产出示例</th></tr></thead><tbody><tr><td>JS</td><td><code>output.filename</code></td><td>入口文件</td><td><code>js/[name].[contenthash:8].js</code></td><td><code>app.a1b2c3d4.js</code></td></tr><tr><td>JS</td><td><code>output.chunkFilename</code></td><td>分割 chunk</td><td><code>js/[name].[contenthash:8].chunk.js</code></td><td><code>vendors.e5f6g7h8.chunk.js</code></td></tr><tr><td>CSS</td><td><code>MiniCssExtractPlugin.filename</code></td><td>入口样式</td><td><code>css/[name].[contenthash:8].css</code></td><td><code>app.a1b2c3d4.css</code></td></tr><tr><td>CSS</td><td><code>MiniCssExtractPlugin.chunkFilename</code></td><td>分割 chunk 样式</td><td><code>css/[name].[contenthash:8].chunk.css</code></td><td><code>about-page.e5f6g7h8.chunk.css</code></td></tr></tbody></table><blockquote><p><strong>核心原则</strong>：所有被代码分割拆出来的文件，统一加上 <code>.chunk</code> 后缀，构建产物一目了然。</p></blockquote><hr><br><h2 id="八、预获取与预加载"><a href="#八、预获取与预加载" class="headerlink" title="八、预获取与预加载"></a><strong>八、预获取与预加载</strong></h2><p>我们前面已经做了代码分割，同时会使用 import 动态导入语法来进行代码按需加载（我们也叫懒加载，比如路由懒加载就是这样实现的）。</p><p>但是加载速度还不够好，比如：是用户点击按钮时才加载这个资源的，如果资源体积很大，那么用户会感觉到明显卡顿效果。</p><p>我们想在浏览器空闲时间，加载后续需要使用的资源。我们就需要用上 <code>Preload</code> 或 <code>Prefetch</code> 技术。</p><h3 id="8-1-Prefetch（预获取）"><a href="#8-1-Prefetch（预获取）" class="headerlink" title="8.1 Prefetch（预获取）"></a><strong><font color='red'>8.1 Prefetch（预获取）</font></strong></h3><p><strong>含义</strong>：告诉浏览器 “这个资源将来可能会用到，在空闲时提前下载”。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 当用户在首页时，浏览器会在空闲时提前下载 login-page chunk</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">LoginPage</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span></span><br><span class="line">    <span class="keyword">import</span>(</span><br><span class="line">      <span class="comment">/* webpackPrefetch: true */</span></span><br><span class="line">      <span class="comment">/* webpackChunkName: &quot;login-page&quot; */</span></span><br><span class="line">      <span class="string">&quot;./pages/LoginPage&quot;</span></span><br><span class="line">    ),</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>Webpack 会在 HTML 的 <code>&lt;head&gt;</code> 中注入：</strong></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;prefetch&quot;</span> <span class="attr">href</span>=<span class="string">&quot;login-page.xxxx.chunk.js&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p><strong>行为特点：</strong></p><ul><li>浏览器在 <strong>空闲时</strong> 下载该资源</li><li>下载优先级 <strong>很低</strong>，不会影响当前页面的关键资源</li><li>资源会被缓存，当真正需要时直接从缓存中获取</li></ul><h3 id="8-2-Preload（预加载）"><a href="#8-2-Preload（预加载）" class="headerlink" title="8.2 Preload（预加载）"></a><strong><font color='red'>8.2 Preload（预加载）</font></strong></h3><p><strong>含义</strong>：告诉浏览器 “这个资源当前页面一定会用到，请立即开始下载”。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ChartComponent 是当前页面马上就要渲染的内容</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ChartComponent</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span></span><br><span class="line">    <span class="keyword">import</span>(</span><br><span class="line">      <span class="comment">/* webpackPreload: true */</span></span><br><span class="line">      <span class="comment">/* webpackChunkName: &quot;chart&quot; */</span></span><br><span class="line">      <span class="string">&quot;./components/Chart&quot;</span></span><br><span class="line">    ),</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>Webpack 会在 HTML 的 <code>&lt;head&gt;</code> 中注入：</strong></p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">link</span> <span class="attr">rel</span>=<span class="string">&quot;preload&quot;</span> <span class="attr">as</span>=<span class="string">&quot;script&quot;</span> <span class="attr">href</span>=<span class="string">&quot;chart.xxxx.chunk.js&quot;</span> /&gt;</span></span><br></pre></td></tr></table></figure><p><strong>行为特点：</strong></p><ul><li>浏览器 <strong>立即</strong> 开始下载（与父 chunk 并行）</li><li>下载优先级 <strong>较高</strong></li><li>如果资源在 3 秒内未被使用，控制台会发出警告</li></ul><h3 id="8-3-Prefetch-vs-Preload-对比"><a href="#8-3-Prefetch-vs-Preload-对比" class="headerlink" title="8.3 Prefetch vs Preload 对比"></a><strong><font color='red'>8.3 Prefetch vs Preload 对比</font></strong></h3><table><thead><tr><th>特性</th><th>Prefetch</th><th>Preload</th></tr></thead><tbody><tr><td><strong>下载时机</strong></td><td>浏览器空闲时</td><td>立即并行下载</td></tr><tr><td><strong>优先级</strong></td><td>低</td><td>高</td></tr><tr><td><strong>适用场景</strong></td><td>未来可能需要的资源</td><td>当前页面即将使用的资源</td></tr><tr><td><strong>与父 chunk 的关系</strong></td><td>父 chunk 加载完成后才开始</td><td>与父 chunk 并行下载</td></tr><tr><td><strong>典型应用</strong></td><td>下一页路由、不常用功能</td><td>首屏必需的异步模块</td></tr></tbody></table><figure class="highlight plaintext"><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">Prefetch 时间线：</span><br><span class="line">├── 主 chunk 下载 ──┤</span><br><span class="line">                     ├── 空闲... ──┤</span><br><span class="line">                                    ├── prefetch chunk 下载 ──┤</span><br><span class="line"></span><br><span class="line">Preload 时间线：</span><br><span class="line">├── 主 chunk 下载 ──────────┤</span><br><span class="line">├── preload chunk 下载 ──┤    ← 同时进行！</span><br></pre></td></tr></table></figure><h3 id="8-4-实际使用建议"><a href="#8-4-实际使用建议" class="headerlink" title="8.4 实际使用建议"></a><strong><font color='red'>8.4 实际使用建议</font></strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ✅ 适合 Prefetch 的场景</span></span><br><span class="line"><span class="comment">// 用户很可能会访问但当前不需要的页面/功能</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Settings</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="comment">/* webpackPrefetch: true */</span> <span class="string">&quot;./pages/Settings&quot;</span>),</span><br><span class="line">);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HelpCenter</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="comment">/* webpackPrefetch: true */</span> <span class="string">&quot;./pages/HelpCenter&quot;</span>),</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 适合 Preload 的场景</span></span><br><span class="line"><span class="comment">// 当前页面立即需要，但因为是动态引入而无法被一起打包的模块</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HeroAnimation</span> = <span class="title function_">lazy</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="comment">/* webpackPreload: true */</span> <span class="string">&quot;./components/HeroAnimation&quot;</span>),</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ 不要滥用 Preload</span></span><br><span class="line"><span class="comment">// 如果 preload 了太多资源，反而会与当前页面的关键资源争抢带宽</span></span><br></pre></td></tr></table></figure><h3 id="8-5-还需要额外插件吗？（如-vue-preload-webpack-plugin）"><a href="#8-5-还需要额外插件吗？（如-vue-preload-webpack-plugin）" class="headerlink" title="8.5 还需要额外插件吗？（如 @vue&#x2F;preload-webpack-plugin）"></a><strong><font color='red'>8.5 还需要额外插件吗？（如 @vue&#x2F;preload-webpack-plugin）</font></strong></h3><p>在 Webpack 5 中，<strong>不再建议</strong>使用 <code>@vue/preload-webpack-plugin</code> 或类似的第三方插件。</p><ul><li><strong>理由</strong>：Webpack 4.6.0+ 已经原生支持了 <code>prefetch</code> 和 <code>preload</code> 魔法注释。当你使用这些注释时，Webpack 会在运行时利用其内置的脚本加载机制，自动在 HTML 的 <code>&lt;head&gt;</code> 中生成并维护对应的 <code>&lt;link&gt;</code> 标签。</li><li><strong>优点</strong>：直接使用魔法注释减少了配置复杂度和依赖项。除非你有“全局自动化注入所有异步 Chunk”这种极特殊的批量处理需求，否则魔法注释是目前最标准、最推荐的做法。</li></ul><hr><br><h2 id="九、Tree-Shaking-与代码分割的协同"><a href="#九、Tree-Shaking-与代码分割的协同" class="headerlink" title="九、Tree Shaking 与代码分割的协同"></a><strong>九、Tree Shaking 与代码分割的协同</strong></h2><p>Tree Shaking 和代码分割通常被认为是两个独立的优化方向，但在 Webpack 5 中，它们是深度耦合、互相成就的。</p><ul><li><strong>代码分割（Code Splitting）</strong>：解决 <strong>“什么时候加载”</strong> 的问题。它按文件、按路由将代码拆散。</li><li><strong>Tree Shaking</strong>：解决 <strong>“加载多少内容”</strong> 的问题。它从文件内部剔除没用的导出成员。</li></ul><h3 id="9-1-sideEffects：Tree-Shaking-的“免检开关”"><a href="#9-1-sideEffects：Tree-Shaking-的“免检开关”" class="headerlink" title="9.1 sideEffects：Tree Shaking 的“免检开关”"></a><strong><font color='red'>9.1 sideEffects：Tree Shaking 的“免检开关”</font></strong></h3><p>这是最容易产生误区的地方。<code>sideEffects</code> 的核心作用是告诉 Webpack：<strong>若本模块的导出未被使用，是否可以连带其内部的副作用逻辑一并“物理抹除”。</strong></p><h4 id="1）深入对比：设置-false-vs-不设置"><a href="#1）深入对比：设置-false-vs-不设置" class="headerlink" title="1）深入对比：设置 false vs 不设置"></a><strong><font color='#10c300'>1）深入对比：设置 false vs 不设置</font></strong></h4><p>假设有模块 <code>math.js</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// math.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">add</span> = (<span class="params">a, b</span>) =&gt; a + b;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;math 模块被加载了&quot;</span>); <span class="comment">// 这就是一个副作用（Side Effect）</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">mathLoaded</span> = <span class="literal">true</span>; <span class="comment">// 这也是一个副作用</span></span><br></pre></td></tr></table></figure><table><thead><tr><th align="left">配置状态</th><th align="left">场景：<code>import &#123; add &#125; from &#39;./math&#39;</code> 但<strong>未使用</strong> <code>add</code></th><th align="left">场景：使用 <code>add</code></th></tr></thead><tbody><tr><td align="left"><strong>未设置</strong></td><td align="left"><code>add</code> 会被剔除，但 <strong><code>console.log</code> 和 <code>window</code> 赋值会保留</strong>。Webpack 不敢删执行代码。</td><td align="left"><code>add</code> 保留，副作用逻辑也保留。</td></tr><tr><td align="left"><strong>sideEffects: false</strong></td><td align="left"><strong>整个 <code>math.js</code> 被物理删除</strong>。哪怕里面有 <code>console.log</code> 或 <code>window</code> 赋值也统统抹除。</td><td align="left"><code>add</code> 保留，副作用逻辑也保留。</td></tr></tbody></table><blockquote><p>[!CAUTION]<br><strong>风险提示</strong>：如果你的代码依赖“只需 import 就能生效”的逻辑（如：CSS、全局变量初始化、Polyfill），必须在 <code>package.json</code> 中配置数组来排除它们：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;sideEffects&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;*.css&quot;</span><span class="punctuation">,</span> <span class="string">&quot;./src/init-polyfill.js&quot;</span><span class="punctuation">]</span></span><br></pre></td></tr></table></figure></blockquote><h3 id="9-2-三种导入姿势对-Tree-Shaking-的影响"><a href="#9-2-三种导入姿势对-Tree-Shaking-的影响" class="headerlink" title="9.2 三种导入姿势对 Tree Shaking 的影响"></a><strong><font color='red'>9.2 三种导入姿势对 Tree Shaking 的影响</font></strong></h3><p>为了配合代码分割和 Tree Shaking 达到最佳体积，导入方式至关重要：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 方案 A：全量导入（极其不推荐）</span></span><br><span class="line"><span class="comment">// 这种方式会导致整个 lodash 被包进来，除非 lodash 本身支持并配置了 sideEffects</span></span><br><span class="line"><span class="keyword">import</span> _ <span class="keyword">from</span> <span class="string">&quot;lodash&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 方案 B：具名导入（Tree Shaking 友好）</span></span><br><span class="line"><span class="comment">// Webpack 会识别出你只用了 debounce，从而 shake 掉其他成员</span></span><br><span class="line"><span class="keyword">import</span> &#123; debounce &#125; <span class="keyword">from</span> <span class="string">&quot;lodash-es&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 方案 C：子路径导入（物理级 Tree Shaking）</span></span><br><span class="line"><span class="comment">// 这种方式最直接，打包器只会关注 debounce.js 及其依赖，根本不看 lodash 其他部分</span></span><br><span class="line"><span class="keyword">import</span> debounce <span class="keyword">from</span> <span class="string">&quot;lodash/debounce&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="9-3-当-Tree-Shaking-遇到-SplitChunks"><a href="#9-3-当-Tree-Shaking-遇到-SplitChunks" class="headerlink" title="9.3 当 Tree Shaking 遇到 SplitChunks"></a><strong><font color='red'>9.3 当 Tree Shaking 遇到 SplitChunks</font></strong></h3><p>这是一个高级协同场景。当你配置了 <code>optimization.splitChunks</code> 时，Tree Shaking 的结果会直接影响公共块的提取。</p><p><strong>协同逻辑：</strong></p><ol><li>Webpack 首先对每个入口进行 <strong>Tree Shaking</strong> 标记（哪些成员没用）。</li><li>在进行 <strong>SplitChunks</strong> 公共模块提取时，Webpack 提取的是 <strong>“经过 Shake 之后的残余模块”</strong>。</li><li>如果一个公共模块 <code>utils.js</code> 在入口 A 里用了 <code>a()</code>，在入口 B 里用了 <code>b()</code>，那么最后提取出来的 <code>commons.chunk.js</code> 只会包含 <code>a</code> 和 <code>b</code> 的代码。</li></ol><h3 id="9-4-结合动态导入与-webpackExports"><a href="#9-4-结合动态导入与-webpackExports" class="headerlink" title="9.4 结合动态导入与 webpackExports"></a><strong><font color='red'>9.4 结合动态导入与 webpackExports</font></strong></h3><p>这是 Webpack 5 提供的极致优化工具。如果你只想动态加载一个巨大模块中的一小部分成员：</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">// 场景：只想动态加载模块中名为 &#x27;specificExport&#x27; 的成员</span></span><br><span class="line"><span class="keyword">const</span> member = <span class="keyword">await</span> <span class="keyword">import</span>(</span><br><span class="line">  <span class="comment">/* webpackExports: [&quot;specificExport&quot;] */</span></span><br><span class="line">  <span class="string">&quot;large-library&quot;</span></span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>底层协同效果</strong>：</p><ul><li>Webpack 会为这个特殊的 <code>import()</code> 创建一个极小的 Chunk。</li><li>它会绕过整个 <code>large-library</code> 的其他导出成员，利用 Tree Shaking 标记并仅提取 <code>specificExport</code> 及其必要依赖。</li></ul><h3 id="9-5-协同优化检查表"><a href="#9-5-协同优化检查表" class="headerlink" title="9.5 协同优化检查表"></a><strong><font color='red'>9.5 协同优化检查表</font></strong></h3><ul><li><input disabled="" type="checkbox"> <strong>项目源码</strong>：<code>package.json</code> 里的 <code>sideEffects</code> 是否已正确设置（包含排除 CSS）。</li><li><input disabled="" type="checkbox"> <strong>第三方库</strong>：是否优先使用了支持 ESM 协议的包（如 <code>lodash-es</code> 优于 <code>lodash</code>）。</li><li><input disabled="" type="checkbox"> <strong>导出模式</strong>：是否使用了 <code>export default &#123; ... &#125;</code>？（<strong>不要这样做</strong>，对象形式的默认导出不利于 Tree Shaking，请使用具名 <code>export</code>）。</li><li><input disabled="" type="checkbox"> <strong>CSS 处理</strong>：是否使用了 <code>MiniCssExtractPlugin</code>？（它能配合代码分割，在 Shake 掉 JS 的同时，也由 Webpack 自动分析并 Shake 掉多余的 CSS）。</li></ul><hr><br><h2 id="十、实战：完整的代码分割配置方案"><a href="#十、实战：完整的代码分割配置方案" class="headerlink" title="十、实战：完整的代码分割配置方案"></a><strong>十、实战：完整的代码分割配置方案</strong></h2><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;html-webpack-plugin&quot;</span>);</span><br><span class="line"></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">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line"></span><br><span class="line">  <span class="attr">entry</span>: &#123;</span><br><span class="line">    <span class="attr">app</span>: <span class="string">&quot;./src/index.js&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;dist&quot;</span>),</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;js/[name].[contenthash:8].js&quot;</span>,</span><br><span class="line">    <span class="attr">chunkFilename</span>: <span class="string">&quot;js/[name].[contenthash:8].chunk.js&quot;</span>,</span><br><span class="line">    <span class="attr">clean</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">optimization</span>: &#123;</span><br><span class="line">    <span class="comment">// 提取运行时代码</span></span><br><span class="line">    <span class="attr">runtimeChunk</span>: <span class="string">&quot;single&quot;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 模块 ID 使用确定性算法（利于长期缓存）</span></span><br><span class="line">    <span class="attr">moduleIds</span>: <span class="string">&quot;deterministic&quot;</span>,</span><br><span class="line"></span><br><span class="line">    <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">      <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">      <span class="attr">minSize</span>: <span class="number">20000</span>,</span><br><span class="line">      <span class="attr">maxSize</span>: <span class="number">250000</span>,</span><br><span class="line"></span><br><span class="line">      <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">        <span class="comment">// 框架核心（React / Vue）—— 版本极少变动，长期缓存</span></span><br><span class="line">        <span class="attr">framework</span>: &#123;</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](react|react-dom|vue|vue-router|pinia)[\\/]/</span>,</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&quot;framework&quot;</span>,</span><br><span class="line">          <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">          <span class="attr">priority</span>: <span class="number">40</span>,</span><br><span class="line">          <span class="attr">enforce</span>: <span class="literal">true</span>,</span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// UI 组件库 —— 体积大，单独分离</span></span><br><span class="line">        <span class="attr">ui</span>: &#123;</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](antd|@ant-design|element-plus|@element-plus)[\\/]/</span>,</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&quot;ui-lib&quot;</span>,</span><br><span class="line">          <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">          <span class="attr">priority</span>: <span class="number">30</span>,</span><br><span class="line">        &#125;,</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 工具库（lodash、dayjs、axios 等）—— 使用频率高</span></span><br><span class="line">        <span class="attr">utils</span>: &#123;</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/](lodash|lodash-es|dayjs|axios|qs)[\\/]/</span>,</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&quot;utils&quot;</span>,</span><br><span class="line">          <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">          <span class="attr">priority</span>: <span class="number">25</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="attr">vendors</span>: &#123;</span><br><span class="line">          <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&quot;vendors&quot;</span>,</span><br><span class="line">          <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">          <span class="attr">priority</span>: <span class="number">10</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="attr">commons</span>: &#123;</span><br><span class="line">          <span class="attr">name</span>: <span class="string">&quot;commons&quot;</span>,</span><br><span class="line">          <span class="attr">minChunks</span>: <span class="number">2</span>,</span><br><span class="line">          <span class="attr">chunks</span>: <span class="string">&quot;all&quot;</span>,</span><br><span class="line">          <span class="attr">priority</span>: <span class="number">5</span>,</span><br><span class="line">          <span class="attr">reuseExistingChunk</span>: <span class="literal">true</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><br><span class="line">    <span class="attr">minimize</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">template</span>: <span class="string">&quot;./public/index.html&quot;</span>,</span><br><span class="line">      <span class="attr">minify</span>: &#123;</span><br><span class="line">        <span class="attr">collapseWhitespace</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">removeComments</span>: <span class="literal">true</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>产出文件结构预览：</strong></p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">dist/</span><br><span class="line">├── index.html</span><br><span class="line">└── js/</span><br><span class="line">    ├── runtime.a1b2c3d4.js          ← Webpack 运行时（~2KB）</span><br><span class="line">    ├── framework.e5f6g7h8.js        ← React/Vue 核心（~40KB gzip）</span><br><span class="line">    ├── ui-lib.i9j0k1l2.chunk.js     ← UI 组件库（按需加载）</span><br><span class="line">    ├── utils.m3n4o5p6.chunk.js      ← 工具库（~15KB gzip）</span><br><span class="line">    ├── vendors.q7r8s9t0.chunk.js    ← 其他第三方（~30KB gzip）</span><br><span class="line">    ├── commons.u1v2w3x4.chunk.js    ← 公共模块</span><br><span class="line">    ├── app.y5z6a7b8.js              ← 入口主逻辑</span><br><span class="line">    ├── home-page.c9d0e1f2.chunk.js  ← 首页（动态导入）</span><br><span class="line">    ├── about-page.g3h4i5j6.chunk.js ← 关于页（动态导入）</span><br><span class="line">    └── ...</span><br></pre></td></tr></table></figure><hr><br><h2 id="十一、分析与优化"><a href="#十一、分析与优化" class="headerlink" title="十一、分析与优化"></a><strong>十一、分析与优化</strong></h2><h3 id="11-1-使用-webpack-bundle-analyzer"><a href="#11-1-使用-webpack-bundle-analyzer" class="headerlink" title="11.1 使用 webpack-bundle-analyzer"></a><strong><font color='red'>11.1 使用 webpack-bundle-analyzer</font></strong></h3><p>安装并配置 <code>webpack-bundle-analyzer</code>，可以直观地看到每个 chunk 的组成和体积：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install --save-dev webpack-bundle-analyzer</span><br></pre></td></tr></table></figure><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">const</span> &#123; <span class="title class_">BundleAnalyzerPlugin</span> &#125; = <span class="built_in">require</span>(<span class="string">&quot;webpack-bundle-analyzer&quot;</span>);</span><br><span class="line"></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">plugins</span>: [</span><br><span class="line">    <span class="comment">// 只在需要分析时开启</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">BundleAnalyzerPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">analyzerMode</span>: <span class="string">&quot;static&quot;</span>, <span class="comment">// 生成静态 HTML 报告</span></span><br><span class="line">      <span class="attr">reportFilename</span>: <span class="string">&quot;report.html&quot;</span>,</span><br><span class="line">      <span class="attr">openAnalyzer</span>: <span class="literal">false</span>, <span class="comment">// 不自动打开浏览器</span></span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>通过分析报告，你可以发现：</p><ul><li>哪些包体积过大，需要找替代方案或按需导入</li><li>哪些包被重复打包到了多个 chunk 中</li><li>哪些不必要的代码被打包进来了</li></ul><h3 id="11-2-使用-stats-json-进行离线分析"><a href="#11-2-使用-stats-json-进行离线分析" class="headerlink" title="11.2 使用 stats.json 进行离线分析"></a><strong><font color='red'>11.2 使用 stats.json 进行离线分析</font></strong></h3><figure class="highlight bash"><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"># 生成 stats.json</span></span><br><span class="line">npx webpack --profile --json &gt; stats.json</span><br></pre></td></tr></table></figure><p>将 <code>stats.json</code> 上传到以下在线工具进行分析：</p><ul><li><a href="https://webpack.github.io/analyse/">Webpack Analyse</a></li><li><a href="https://chrisbateman.github.io/webpack-visualizer/">Webpack Visualizer</a></li></ul><h3 id="11-3-实用的优化检查清单"><a href="#11-3-实用的优化检查清单" class="headerlink" title="11.3 实用的优化检查清单"></a><strong><font color='red'>11.3 实用的优化检查清单</font></strong></h3><p>在完成代码分割配置后，建议逐项检查以下内容：</p><ul><li><input disabled="" type="checkbox"> <strong><code>chunks</code> 是否设为 <code>&#39;all&#39;</code></strong>：确保同步和异步模块都能被优化</li><li><input disabled="" type="checkbox"> <strong>框架核心是否单独分包</strong>：React&#x2F;Vue 版本稳定，应该有独立的长期缓存</li><li><input disabled="" type="checkbox"> <strong>大型第三方库是否按需导入</strong>：如 <code>antd</code>、<code>lodash</code>、<code>echarts</code> 不应全量引入</li><li><input disabled="" type="checkbox"> <strong>路由是否使用了懒加载</strong>：除首页外的所有路由页面都应该动态 <code>import()</code></li><li><input disabled="" type="checkbox"> <strong>contenthash 是否被使用</strong>：文件名使用 <code>[contenthash]</code> 以实现浏览器长期缓存</li><li><input disabled="" type="checkbox"> <strong>runtimeChunk 是否被提取</strong>：避免运行时代码嵌入到业务 chunk 中</li><li><input disabled="" type="checkbox"> <strong>是否配置了 moduleIds: ‘deterministic’</strong>：保证未变更模块的 hash 稳定</li><li><input disabled="" type="checkbox"> <strong>prefetch 是否用在了下一步操作</strong>：如登录后的首页可以 prefetch</li><li><input disabled="" type="checkbox"> <strong>是否运行过 bundle analyzer</strong>：确认没有意外的大包或重复包</li></ul><hr><br><h2 id="十二、针对不同类型项目的配置建议"><a href="#十二、针对不同类型项目的配置建议" class="headerlink" title="十二、针对不同类型项目的配置建议"></a><strong>十二、针对不同类型项目的配置建议</strong></h2><p>在实际的商业项目中，配置通常会根据项目规模（代码量、包数量）在“缓存命中率”和“首屏加载速度”之间做权衡。</p><h3 id="12-1-“开发效率型”配置（适用于中小型项目）"><a href="#12-1-“开发效率型”配置（适用于中小型项目）" class="headerlink" title="12.1 “开发效率型”配置（适用于中小型项目）"></a><strong><font color='red'>12.1 “开发效率型”配置（适用于中小型项目）</font></strong></h3><p>这种配置主打“简单、稳定”。它不过度拆分，而是将所有第三方库收纳在一起，减少 HTTP 请求数。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">    <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">    <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">      <span class="attr">vendors</span>: &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/[\\/]node_modules[\\/]/</span>,</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&#x27;vendors&#x27;</span>,</span><br><span class="line">        <span class="attr">priority</span>: -<span class="number">10</span></span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">default</span>: &#123;</span><br><span class="line">        <span class="attr">minChunks</span>: <span class="number">2</span>,</span><br><span class="line">        <span class="attr">priority</span>: -<span class="number">20</span>,</span><br><span class="line">        <span class="attr">reuseExistingChunk</span>: <span class="literal">true</span></span><br><span class="line">      &#125;</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="12-2-“平衡性能型”配置（适用于大多数主流项目）"><a href="#12-2-“平衡性能型”配置（适用于大多数主流项目）" class="headerlink" title="12.2 “平衡性能型”配置（适用于大多数主流项目）"></a><strong><font color='red'>12.2 “平衡性能型”配置（适用于大多数主流项目）</font></strong></h3><p>我们在<a href="#%E5%8D%81%E5%AE%9E%E6%88%98%E5%AE%8C%E6%95%B4%E7%9A%84%E4%BB%A3%E7%A0%81%E5%88%86%E5%89%B2%E9%85%8D%E7%BD%AE%E6%96%B9%E6%A1%88">第十章</a>中提供的配置即为此类型。它的核心逻辑是：</p><ul><li><strong>核心框架单独分包</strong>（如 React&#x2F;Vue），利用长期缓存。</li><li><strong>大型 UI 库单独分包</strong>（如 AntD&#x2F;Element），避免阻塞首屏。</li><li><strong>剩余第三方库收纳到 vendors</strong>。</li></ul><h3 id="12-3-“极致优化型”配置（针对巨大型项目）"><a href="#12-3-“极致优化型”配置（针对巨大型项目）" class="headerlink" title="12.3 “极致优化型”配置（针对巨大型项目）"></a><strong><font color='red'>12.3 “极致优化型”配置（针对巨大型项目）</font></strong></h3><p>如果你的项目 <code>node_modules</code> 极其臃肿，可以使用 Webpack 5 的 <code>maxSize</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></pre></td><td class="code"><pre><span class="line"><span class="attr">optimization</span>: &#123;</span><br><span class="line">  <span class="attr">splitChunks</span>: &#123;</span><br><span class="line">    <span class="attr">chunks</span>: <span class="string">&#x27;all&#x27;</span>,</span><br><span class="line">    <span class="comment">// 强制把超过 100KB 的 chunk 尝试继续拆分成更小的，利用多请求并行下载</span></span><br><span class="line">    <span class="attr">maxSize</span>: <span class="number">100000</span>,</span><br><span class="line">    <span class="attr">minSize</span>: <span class="number">20000</span>,</span><br><span class="line">    <span class="attr">cacheGroups</span>: &#123;</span><br><span class="line">      <span class="comment">// 这里的逻辑可以配合第十章的 cacheGroups 使用</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="12-4-生产级-sideEffects-标准排除清单"><a href="#12-4-生产级-sideEffects-标准排除清单" class="headerlink" title="12.4 生产级 sideEffects 标准排除清单"></a><strong><font color='red'>12.4 生产级 sideEffects 标准排除清单</font></strong></h3><p>这是解决 Tree Shaking 误删问题的标准化 <code>package.json</code> 配置，建议直接作为模板使用：</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;sideEffects&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;*.css&quot;</span><span class="punctuation">,</span> <span class="comment">// 确保 CSS 不被误删</span></span><br><span class="line">    <span class="string">&quot;*.less&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;*.scss&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;./src/polyfills.js&quot;</span><span class="punctuation">,</span> <span class="comment">// Polyfill 全局初始化</span></span><br><span class="line">    <span class="string">&quot;./src/global.js&quot;</span><span class="punctuation">,</span> <span class="comment">// 全局变量或插件初始化</span></span><br><span class="line">    <span class="string">&quot;**/plugins/*.js&quot;</span> <span class="comment">// 通用插件目录</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p>[!WARNING]<br><strong>数组配置的“潜规则”</strong>：<br>当你使用数组列出有副作用的文件时，<strong>没有出现在清单中的所有其他 JS 文件都会被 Webpack 默认为“绝对纯净”</strong>（即 <code>sideEffects: false</code>）。</p><p><strong>这意味着</strong>：如果一个 JS 文件不在清单里，即使它内部写了 <code>console.log</code> 或修改了全局变量，只要它的 <code>export</code> 成员未被其他地方使用，Webpack 就会<strong>直接跳过并物理删除整个文件</strong>，其中的副作用逻辑也会随之消失。</p><p><strong>建议</strong>：</p><ol><li>养成<strong>纯净模块化</strong>习惯：JS 文件应只负责导出功能，不应在顶层执行逻辑。</li><li>如果某些历史遗留文件确实有“只需加载即生效”的代码，请务必将其加入上述清单。</li></ol></blockquote><h3 id="12-5-关于框架的选择"><a href="#12-5-关于框架的选择" class="headerlink" title="12.5 关于框架的选择"></a><strong><font color='red'>12.5 关于框架的选择</font></strong></h3><ul><li><strong>React 项目</strong>：<ul><li>核心：<code>react</code>, <code>react-dom</code>, <code>react-router-dom</code> 必须放在一个 <code>priority</code> 最高的组。</li><li>特点：React 自身不提供按需加载，强烈建议配合 <code>React.lazy</code> 使用。</li></ul></li><li><strong>Vue 项目</strong>：<ul><li>核心：<code>vue</code>, <code>vue-router</code>, <code>pinia</code> 放在一个组。</li><li>特点：Vue 的异步组件原生支持良好，建议在 <code>router/index.js</code> 中全程使用 <code>component: () =&gt; import(...)</code>。</li></ul></li></ul><hr><blockquote><p><strong>结语</strong>：没有完美的配置，只有最适合当前项目业务场景的权衡方案。建议每隔一到两个月，运行一次 <code>webpack-bundle-analyzer</code> 来巡检你的打包结果。</p></blockquote><h2 id="十三、如何编写-Tree-Shaking-友好的代码（避坑指南）"><a href="#十三、如何编写-Tree-Shaking-友好的代码（避坑指南）" class="headerlink" title="十三、如何编写 Tree-Shaking 友好的代码（避坑指南）"></a>十三、如何编写 Tree-Shaking 友好的代码（避坑指南）</h2><p>正如我们在第十二章提到的 <code>sideEffects</code> 机制，如果不规范编码，很容易在项目维护后期掉入“副作用丢失”的陷阱。</p><h3 id="1-致命诱因：混合模块（Hybrid-Modules）"><a href="#1-致命诱因：混合模块（Hybrid-Modules）" class="headerlink" title="1. 致命诱因：混合模块（Hybrid Modules）"></a><strong><span style='color:red'>1. 致命诱因：混合模块（Hybrid Modules）</span></strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 极其危险的写法 (utils.js)</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">count</span> = (<span class="params">x</span>) =&gt; x + <span class="number">11</span>; <span class="comment">// 纯函数</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 这是一个隐蔽的副作用，可能被全局某个地方引用</span></span><br><span class="line"><span class="variable language_">window</span>.<span class="property">globalConfig</span> = &#123; <span class="attr">theme</span>: <span class="string">&quot;dark&quot;</span> &#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;项目核心配置已初始化&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>为什么这很危险？</strong></p><ol><li>假设现在 <code>count</code> 函数还在用，项目运行完美。</li><li>几个月后，同事小张重构代码，发现 <code>count</code> 没用了，于是删除了对 <code>count</code> 的所有引用。</li><li>如果项目设置了 <code>sideEffects: false</code> 且没把 <code>utils.js</code> 写进例外清单。</li><li><strong>结局</strong>：下次打包线上版时，Webpack 会认为 <code>utils.js</code> 没用，直接<strong>物理抹除</strong>。导致 <code>window.globalConfig</code> 变成了 <code>undefined</code>，线上项目直接白屏。</li></ol><h3 id="2-避坑准则一：物理分离副作用"><a href="#2-避坑准则一：物理分离副作用" class="headerlink" title="2. 避坑准则一：物理分离副作用"></a><strong><span style='color:red'>2. 避坑准则一：物理分离副作用</span></strong></h3><p><strong>绝对不要将 utility（工具类）和 initialization（初始化类）逻辑写在一起。</strong></p><figure class="highlight bash"><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"># 推荐的目录结构</span></span><br><span class="line">src/</span><br><span class="line">  ├── utils/          <span class="comment"># 纯函数目录（sideEffects: false 的安全区）</span></span><br><span class="line">  │     └── math.js</span><br><span class="line">  └── init/           <span class="comment"># 副作用目录（必须全部写进 sideEffects 例外清单）</span></span><br><span class="line">        └── global-setup.js</span><br></pre></td></tr></table></figure><h3 id="3-避坑准则二：拒绝“隐蔽”导入"><a href="#3-避坑准则二：拒绝“隐蔽”导入" class="headerlink" title="3. 避坑准则二：拒绝“隐蔽”导入"></a><strong><span style='color:red'>3. 避坑准则二：拒绝“隐蔽”导入</span></strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="comment">// ❌ 不要指望通过导入 utils 来顺便初始化全局变量</span></span><br><span class="line"><span class="comment">// import &#123; someUtil &#125; from &#x27;./utils.js&#x27;;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 应该显式地、独立地导入初始化模块</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;./init/global-setup.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; someUtil &#125; <span class="keyword">from</span> <span class="string">&quot;./utils/math.js&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="4-避坑准则三：使用具名导出（Named-Exports）"><a href="#4-避坑准则三：使用具名导出（Named-Exports）" class="headerlink" title="4. 避坑准则三：使用具名导出（Named Exports）"></a><strong><span style='color:red'>4. 避坑准则三：使用具名导出（Named Exports）</span></strong></h3><p>对于对象类型的导出，Tree Shaking 的效果往往不佳。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 不利于 Tree Shaking</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">add</span>: <span class="function">(<span class="params">a, b</span>) =&gt;</span> a + b,</span><br><span class="line">  <span class="attr">sub</span>: <span class="function">(<span class="params">a, b</span>) =&gt;</span> a - b,</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="keyword">export</span> <span class="keyword">const</span> <span class="title function_">add</span> = (<span class="params">a, b</span>) =&gt; a + b;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">sub</span> = (<span class="params">a, b</span>) =&gt; a - b;</span><br></pre></td></tr></table></figure><blockquote><p><strong>总结</strong>：Tree Shaking 的最终效果，50% 取决于 Webpack 的配置，另外 <strong>50% 取决于你的编码习惯</strong>。一个优秀的 Webpack 工程师，必然也是一个深谙“关注点分离”的架构师。</p></blockquote>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;📚 本指南旨在帮助开发者深入掌握 Webpack 5 的代码分割技术，从基础原理到生产环境的高级配置一应俱全。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;目录&quot;&gt;&lt;a href=&quot;#目录&quot; class=&quot;headerlink&quot;</summary>
      
    
    
    
    <category term="工程化与质量" scheme="http://example.com/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96%E4%B8%8E%E8%B4%A8%E9%87%8F/"/>
    
    
    <category term="webpack5" scheme="http://example.com/tags/webpack5/"/>
    
    <category term="性能优化" scheme="http://example.com/tags/%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96/"/>
    
  </entry>
  
  <entry>
    <title>Babel Runtime 依赖图谱与模块化 Polyfill 方案</title>
    <link href="http://example.com/posts/44f179c0b9.html"/>
    <id>http://example.com/posts/44f179c0b9.html</id>
    <published>2026-03-02T11:34:59.000Z</published>
    <updated>2026-04-02T08:39:33.923Z</updated>
    
    <content type="html"><![CDATA[<p>在使用 Babel 进行 JavaScript 代码编译时，为了优化打包体积并处理新增 API，我们经常会用到 <code>@babel/plugin-transform-runtime</code> 以及它的两个核心依赖库。本文档将详细梳理它们的作用、关系以及最佳实践配置。</p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>需要用到辅助函数和Polyfill垫片，是因为当前浏览器不支持js新语法，因此才会用到。</p><p>像现在的现代浏览器大部分都支持这写新语法，因此要测试你配置的是否生效，可将浏览器范围修改到ie9（<code>&quot;browserslist&quot;: [&quot;ie 9&quot;]</code>）,设置 ie 9 只是为了”逼”Babel 进行语法降级转译，从而产生辅助函数，方便我们验证 @babel&#x2F;runtime 是否生效。这是临时的实验手段。</p><p>测试的时候，由于本地开发时的特殊性，每次构建之间需要清除先缓存。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 清除 babel-loader 和 webpack 的缓存</span></span><br><span class="line">Remove-Item -Recurse -Force ./node_modules/.cache</span><br><span class="line">npm run build</span><br></pre></td></tr></table></figure><p>生产不存在这样情况</p><figure class="highlight bash"><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"># 每次 CI 流水线都是全新的干净环境</span></span><br><span class="line">git <span class="built_in">clone</span> 项目代码</span><br><span class="line">npm install       <span class="comment"># 全新安装，没有 .cache</span></span><br><span class="line">npm run build     <span class="comment"># 第一次 build，没有任何缓存可命中</span></span><br></pre></td></tr></table></figure><p>生产代码都是经过压缩混淆的 ，可以在 babel-loader 的缓存文件查看（<strong>未压缩的 Babel 编译输出</strong>）</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260301180329607.png" alt="image-20260301180329607"></p><h2 id="1-核心包概览"><a href="#1-核心包概览" class="headerlink" title="1. 核心包概览"></a>1. 核心包概览</h2><table><thead><tr><th align="left">包名</th><th align="left">类型</th><th align="left">核心作用</th></tr></thead><tbody><tr><td align="left"><strong><code>@babel/plugin-transform-runtime</code></strong></td><td align="left"><strong>开发依赖</strong> (<code>-D</code>)</td><td align="left"><strong>“搬运工” &#x2F; “指挥官”</strong>。在编译时工作，自动将代码中内联的 Babel 辅助函数替换为从 <code>runtime</code> 库中统一引入。</td></tr><tr><td align="left"><strong><code>@babel/runtime</code></strong></td><td align="left"><strong>运行时依赖</strong> (<code>-S</code>)</td><td align="left"><strong>“基础弹药库”</strong>。提供 Babel 辅助函数（helpers）的实际运行的时代码。<strong>不包含</strong> Polyfill。</td></tr><tr><td align="left"><strong><code>@babel/runtime-corejs3</code></strong></td><td align="left"><strong>运行时依赖</strong> (<code>-S</code>)</td><td align="left"><strong>“高级弹药库”</strong>。是上面的增强版，提供 辅助函数（helpers）的同时，额外包含了基于 <code>core-js@3</code> 的 <strong>非全局污染式</strong> Polyfill。</td></tr><tr><td align="left"><strong><code>core-js@3</code></strong></td><td align="left"><strong>运行时依赖</strong> (<code>-S</code>)</td><td align="left"><strong>“标准库垫片基石”</strong>。提供最新 ES 规范（API 层面）的底层实现，是默认通过<strong>全局污染</strong>方式生效的Polyfill，常配合 <code>preset-env</code> 使用。</td></tr></tbody></table><hr><h2 id="2-关系图解与工作原理"><a href="#2-关系图解与工作原理" class="headerlink" title="2. 关系图解与工作原理"></a>2. 关系图解与工作原理</h2><h3 id="工作流依赖关系"><a href="#工作流依赖关系" class="headerlink" title="工作流依赖关系"></a>工作流依赖关系</h3><figure class="highlight text"><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">@babel/plugin-transform-runtime (编译时插件)</span><br><span class="line">        │</span><br><span class="line">        ├─发现内联的 Helper 函数 ─&gt; 替换为引入  ─&gt; @babel/runtime 或 @babel/runtime-corejs3</span><br><span class="line">        │</span><br><span class="line">        └─发现需要 Polyfill 的 API ─&gt; 替换为引入  ─&gt; @babel/runtime-corejs3</span><br></pre></td></tr></table></figure><h3 id="痛点与解决方式"><a href="#痛点与解决方式" class="headerlink" title="痛点与解决方式"></a>痛点与解决方式</h3><p>Babel 在转译 <code>class</code> 或 <code>async/await</code> 等新语法时，默认会在<strong>每个文件顶部</strong>注入辅助函数。</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="comment">// 【转换前】没有 plugin-transform-runtime 时，每个文件都会冗余内联 helper</span></span><br><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">_classCallCheck</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">/* 重复代码 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">_classCallCheck</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">/* 重复代码 */</span></span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> &#123;&#125;</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260301155319309.png" alt="image-20260301155319309"></p><p>这会导致打包体积剧增。<strong><code>@babel/plugin-transform-runtime</code> 的出现就是为了解决这个问题：</strong></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">// 【转换后】使用 plugin-transform-runtime 后，统一从模块按需引入</span></span><br><span class="line"><span class="comment">// a.js</span></span><br><span class="line"><span class="keyword">import</span> _classCallCheck <span class="keyword">from</span> <span class="string">&quot;@babel/runtime/helpers/classCallCheck&quot;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">A</span> &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// b.js</span></span><br><span class="line"><span class="keyword">import</span> _classCallCheck <span class="keyword">from</span> <span class="string">&quot;@babel/runtime/helpers/classCallCheck&quot;</span>; <span class="comment">// 完美复用</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">B</span> &#123;&#125;</span><br></pre></td></tr></table></figure><hr><h2 id="3-详细解析"><a href="#3-详细解析" class="headerlink" title="3. 详细解析"></a>3. 详细解析</h2><h3 id="3-1-babel-plugin-transform-runtime"><a href="#3-1-babel-plugin-transform-runtime" class="headerlink" title="3.1 @babel/plugin-transform-runtime"></a>3.1 <code>@babel/plugin-transform-runtime</code></h3><ul><li><strong>定位</strong>：编译时的工具（安装在 <code>devDependencies</code>）。</li><li><strong>职责 1</strong>：提取runtime库中 Babel 辅助函数，防止重复注入，减小打包体积。</li><li><strong>职责 2</strong>：配合 <code>corejs</code> 配置，提供沙箱式的环境，防止 Polyfill 污染全局作用域（Global Scope）。</li></ul><h3 id="3-2-babel-runtime"><a href="#3-2-babel-runtime" class="headerlink" title="3.2 @babel/runtime"></a>3.2 <code>@babel/runtime</code></h3><ul><li><strong>定位</strong>：运行时的代码库（必须安装在 <code>dependencies</code>）。</li><li><strong>内容</strong>：只包含 helpers 函数实现（如 <code>_classCallCheck</code>）和 <code>regenerator-runtime</code>（用于 async&#x2F;await）。</li><li><strong>限制</strong>：<strong>不包含</strong>像 <code>Promise</code>、<code>Array.prototype.includes</code> 等现代 API 的 Polyfill垫片。</li></ul><h3 id="3-3-babel-runtime-corejs3"><a href="#3-3-babel-runtime-corejs3" class="headerlink" title="3.3 @babel/runtime-corejs3"></a>3.3 <code>@babel/runtime-corejs3</code></h3><ul><li><strong>定位</strong>：运行时的代码库，<code>@babel/runtime</code> 的替代升级版（必须安装在 <code>dependencies</code>）。</li><li><strong>内容</strong>：包含 helpers + <code>core-js@3</code> 的局部化 Polyfill。</li><li><strong>优势</strong>：当你在代码中使用 <code>new Promise()</code> 时，它会将其转换为局部引入（如 <code>import _Promise from &quot;@babel/runtime-corejs3/core-js-stable/promise&quot;</code>），而不会去修改浏览器全局的 <code>window.Promise</code>。</li></ul><h3 id="3-4-core-js-3"><a href="#3-4-core-js-3" class="headerlink" title="3.4 core-js@3"></a>3.4 <code>core-js@3</code></h3><ul><li><strong>定位</strong>：运行时的核心底层代码库，Babel 生态实现 API Polyfill 的真正基石（安装在 <code>dependencies</code> 中）。</li><li><strong>内容</strong>：包含了几乎所有的现代 JavaScript 新原生 API（例如 <code>Promise</code>、<code>Map</code>、<code>Set</code>、<code>Array.prototype.includes</code> 等）的实现。</li><li><strong>特点</strong>：它是**全局污染式（Global Scope）**垫片（会直接修改浏览器全局对象或原型链）。在开发普通 Web 业务应用时，这是不可或缺的核心库，通常与 <code>@babel/preset-env</code> 搭配让其按需引入。</li></ul><hr><h2 id="4-全局污染式与非全局污染式的-Polyfill"><a href="#4-全局污染式与非全局污染式的-Polyfill" class="headerlink" title="4. 全局污染式与非全局污染式的 Polyfill"></a>4. 全局污染式与非全局污染式的 Polyfill</h2><h3 id="4-1-全局污染式"><a href="#4-1-全局污染式" class="headerlink" title="4.1. 全局污染式"></a>4.1. 全局污染式</h3><p><strong>代表组合</strong>：<code>@babel/preset-env</code> (配置 <code>usage</code>) + <code>core-js@3</code></p><p><strong>工作原理</strong>：<br>它相当于直接给浏览器“打补丁”。Babel 会在当前文件中按需引入缺失的 API，例如当你使用了 <code>Promise</code> 时，它会注入 <code>import &quot;core-js/modules/es.promise.js&quot;</code>。<br>这行代码执行时，如果发现当前浏览器环境（比如老旧版本的 IE）没有原生的 <code>Promise</code> 支持，就会<strong>直接修改浏览器的全局对象（即挂载到 <code>window.Promise</code> 上）或全局原型链（如 <code>Array.prototype.includes</code>）</strong>。</p><p><strong>特点与适用场景</strong>：</p><ul><li><strong>优点（全局生效）</strong>：项目中任何地方（包括没有用 Babel 编译过的第三方老旧脚本）都能沾光用到这个新特性。</li><li><strong>缺点（环境冲突）</strong>：如果你的代码作为第三方库被别人引入，你<strong>粗暴地修改了使用者的浏览器全局变量</strong>，可能会覆盖他们自己定义的同名方法，或是与他们使用的其它 Polyfill 产生冲突。</li><li><strong>适用场景</strong>：<strong>开发普通的 Web 业务项目</strong>。由于你作为开发者掌控着整个应用的运行环境，全局打补丁不仅最省事，也能确保代码的行为一致。</li></ul><h3 id="4-2-非全局污染式"><a href="#4-2-非全局污染式" class="headerlink" title="4.2. 非全局污染式"></a>4.2. 非全局污染式</h3><p><strong>代表组合</strong>：<code>@babel/plugin-transform-runtime</code> + <code>@babel/runtime-corejs3</code></p><p><strong>工作原理</strong>：<br>它相当于给你提供了一个完整的“工具箱&#x2F;沙箱”。Babel 发现你使用了 <code>Promise</code> 后，会悄悄把你代码中的 <code>Promise</code> 替换成一个私有变量 <code>_Promise</code>，然后从库里将其导入：</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="keyword">import</span> _Promise <span class="keyword">from</span> <span class="string">&quot;@babel/runtime-corejs3/core-js-stable/promise&quot;</span>;</span><br><span class="line"><span class="keyword">new</span> <span class="title function_">_Promise</span>();</span><br></pre></td></tr></table></figure><p><strong>它不会修改浏览器的 <code>window.Promise</code></strong>，只是替换了你当前模块里的相关标识符。</p><p><strong>特点与适用场景</strong>：</p><ul><li><strong>优点（互不干扰）</strong>：它生成的 Polyfill 是局部、私有的，完全不会影响宿主环境（浏览器环境或其他执行环境）的全局变量。</li><li><strong>缺点（存在盲区与冗余）</strong>：<ol><li><strong>无法 Polyfill 实例方法</strong>：像 <code>&#39;abc&#39;.includes(&#39;a&#39;)</code> 这种挂载在原型链上的实例方法（Array、String 的原型方法等），因为 Babel 无法在编译时准确判断 <code>&#39;abc&#39;</code> 的类型，所以<strong>无法对实例方法提供按需的局部 Polyfill</strong>。</li><li><strong>冗余打包</strong>：如果一个项目引入了 10 个使用了无污染 Polyfill 的 NPM 包，这 10 个包可能各自打包了一份私有的 <code>_Promise</code>，导致最终产物体积增大。</li></ol></li><li><strong>适用场景</strong>：<strong>开发 NPM 第三方依赖包、组件库或工具库</strong>。第三方库绝不能随意修改引入者运行环境下的全局变量，这势必引发不可预知的 Bug，因此不论缺点如何，第三方库都必须使用无污染的独立 Polyfill。</li></ul><h2 id="5-选型指南与常见套路"><a href="#5-选型指南与常见套路" class="headerlink" title="5. 选型指南与常见套路"></a>5. 选型指南与常见套路</h2><h3 id="如何选择依赖搭配？"><a href="#如何选择依赖搭配？" class="headerlink" title="如何选择依赖搭配？"></a>如何选择依赖搭配？</h3><table><thead><tr><th align="left">开发场景</th><th align="left">核心需求</th><th align="left">黄金搭配</th></tr></thead><tbody><tr><td align="left"><strong>纯语法转译</strong></td><td align="left">只需要转译箭头函数、class等，不需要 Polyfill</td><td align="left"><code>@babel/plugin-transform-runtime</code> <br> + <code>@babel/runtime</code></td></tr><tr><td align="left"><strong>开发第三方库 &#x2F; NPM组件包</strong></td><td align="left">需要 Polyfill，但<strong>绝对不能污染全局变量</strong>，以免影响宿主环境</td><td align="left"><code>@babel/plugin-transform-runtime</code> <br> + <code>@babel/runtime-corejs3</code></td></tr><tr><td align="left"><strong>开发普通 Web 业务项目</strong> (🌟最推荐)</td><td align="left">需要按需 Polyfill，且允许全局修改。同时需要减少辅助函数体积</td><td align="left"><code>@babel/preset-env</code> (配置 <code>usage</code>) <br> + <code>core-js@3</code> <br> + <code>@babel/plugin-transform-runtime</code> <br> + <code>@babel/runtime</code></td></tr></tbody></table><hr><h2 id="6-安装与配置示例"><a href="#6-安装与配置示例" class="headerlink" title="6. 安装与配置示例"></a>6. 安装与配置示例</h2><h3 id="场景-A：开发普通的-Web-业务项目-Vue-React-应用"><a href="#场景-A：开发普通的-Web-业务项目-Vue-React-应用" class="headerlink" title="场景 A：开发普通的 Web 业务项目 (Vue&#x2F;React 应用)"></a>场景 A：开发普通的 Web 业务项目 (Vue&#x2F;React 应用)</h3><p>让 <code>preset-env</code> 负责全局按需 Polyfill，让 <code>transform-runtime</code> 专职提取辅助函数。</p><p><strong>1. 安装依赖：</strong></p><figure class="highlight bash"><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"># 核心 polyfill 与基础仓库</span></span><br><span class="line">npm install core-js@3 @babel/runtime -S</span><br><span class="line"><span class="comment"># 编译插件与预设</span></span><br><span class="line">npm install @babel/preset-env @babel/plugin-transform-runtime -D</span><br></pre></td></tr></table></figure><p><strong>2. Babel 配置 (<code>babel.config.json</code>):</strong> json文件不能有注释 要不然配置不生效</p><p>如果启用了 <code>useBuiltIns: &quot;usage&quot;</code>，你需要运行 <code>npm install --save core-js@3</code>（Polyfill 垫片库）来安装这个库，否则打包时可能会报错找不到模块。</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line"></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&quot;</span><span class="punctuation">,</span> <span class="comment">// 按使用情况注入（Polyfill 垫片）</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3&quot;</span> <span class="comment">// Polyfill 交给 preset-env，按需全局注入</span></span><br><span class="line">        <span class="comment">// &quot;modules&quot;: false // 让 Babel 别管 ES6 Module 的 `import/export`，把这个能力保留给 Webpack/Vite 这种打包工具，以便它们做无用代码修剪（Tree-shaking）。</span></span><br><span class="line"></span><br><span class="line">        <span class="comment">// &quot;targets&quot;: &#123; // 建议将其放到 `package.json` 的 `browserslist` 字段中统一定义，这里可以直接不写。</span></span><br><span class="line">        <span class="comment">//   &quot;edge&quot;: &quot;17&quot;,</span></span><br><span class="line">        <span class="comment">//   &quot;firefox&quot;: &quot;60&quot;,</span></span><br><span class="line">        <span class="comment">//   &quot;chrome&quot;: &quot;67&quot;,</span></span><br><span class="line">        <span class="comment">//   &quot;safari&quot;: &quot;11.1&quot;</span></span><br><span class="line">        <span class="comment">// &#125;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span> <span class="comment">// 关键：设为 false，表示只提取 helpers，不处理 Polyfill</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><code>&quot;useBuiltIns&quot;: &quot;usage&quot;</code> 堪称 Babel 配置中<strong>最核心、最实用</strong>的属性之一。它的字面意思是**“按使用情况注入（Polyfill 垫片）”<strong>，也就是我们常说的</strong>“按需引入”**。</p></blockquote><h3 id="场景-B：开发工具库-NPM-依赖包"><a href="#场景-B：开发工具库-NPM-依赖包" class="headerlink" title="场景 B：开发工具库 &#x2F; NPM 依赖包"></a>场景 B：开发工具库 &#x2F; NPM 依赖包</h3><p>一切交给 <code>transform-runtime</code>，实现零全局污染的局部 Polyfill。</p><p><strong>1. 安装依赖：</strong></p><figure class="highlight bash"><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"># 包含无污染 polyfill 的高级仓库</span></span><br><span class="line">npm install @babel/runtime-corejs3 -S</span><br><span class="line"><span class="comment"># 编译插件</span></span><br><span class="line">npm install @babel/preset-env @babel/plugin-transform-runtime -D</span><br></pre></td></tr></table></figure><p><strong>2. Babel 配置 (<code>babel.config.json</code>):</strong></p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="number">3</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>说明</strong>：<code>corejs: 3</code> 启用 corejs3 沙箱模式（无全局污染的局部 Polyfill），需提前安装 <code>@babel/runtime-corejs3</code>。</p></blockquote><h2 id="7-知识拓展：babel核心配置属性"><a href="#7-知识拓展：babel核心配置属性" class="headerlink" title="7. 知识拓展：babel核心配置属性"></a>7. 知识拓展：babel核心配置属性</h2><p><code>&quot;useBuiltIns&quot;: &quot;usage&quot;</code> 堪称 Babel 配置中<strong>最核心、最实用</strong>的属性之一。它的字面意思是**“按使用情况注入（Polyfill 垫片）”<strong>，也就是我们常说的</strong>“按需引入”**。</p><p>为了让你更容易理解，我们先来看看如果没有它会发生什么，以及它到底解决了什么痛点。</p><h3 id="1-它是用来解决什么问题的？"><a href="#1-它是用来解决什么问题的？" class="headerlink" title="1. 它是用来解决什么问题的？"></a>1. 它是用来解决什么问题的？</h3><p>Babel 的主要工作有两部分：</p><ol><li><strong>语法转换</strong>：把箭头函数 <code>() =&gt; &#123;&#125;</code> 变成 <code>function() &#123;&#125;</code>，把 <code>let/const</code> 变成 <code>var</code> 等。这部分目标浏览器不支持的<strong>新语法</strong>，Babel 会直接帮你转译。</li><li><strong>API 垫片 (Polyfill)</strong>：把诸如 <code>Promise</code>、<code>Set</code>、<code>Map</code>、<code>Array.prototype.includes</code> 这种目标浏览器不支持的<strong>新 API (全局对象或原型方法)</strong> 补充进去。Babel 默认<strong>不会</strong>转换这些新 API。</li></ol><p>为了让老浏览器支持这些新 API，我们需要引入 <code>core-js</code>（一个包含了各种新 API 具体实现的库，俗称 Polyfill）。</p><h3 id="2-useBuiltIns-的三种模式对比"><a href="#2-useBuiltIns-的三种模式对比" class="headerlink" title="2. &quot;useBuiltIns&quot; 的三种模式对比"></a>2. <code>&quot;useBuiltIns&quot;</code> 的三种模式对比</h3><p>配置如何引入 Polyfill，完全由 <code>&quot;useBuiltIns&quot;</code> 决定，它有三个值：</p><h4 id="❌-模式一：false-默认值"><a href="#❌-模式一：false-默认值" class="headerlink" title="❌ 模式一：false (默认值)"></a>❌ 模式一：<code>false</code> (默认值)</h4><ul><li><strong>含义</strong>：Babel 什么都不做。它只负责转换语法，不管你的 API 是否兼容。</li><li><strong>结果</strong>：如果你在代码里写了 <code>new Promise()</code>，打包后的代码依然是 <code>new Promise()</code>。如果低级浏览器不支持，代码就直接报错白屏了。如果你想兼容，必须自己手动在入口文件引入<strong>完整</strong>的 <code>core-js</code>。</li></ul><figure class="highlight js"><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">// src/main.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js&quot;</span>; <span class="comment">// 手动引入完整的包，体积非常巨大！</span></span><br></pre></td></tr></table></figure><h4 id="🟡-模式二：-entry-入口全局注入"><a href="#🟡-模式二：-entry-入口全局注入" class="headerlink" title="🟡 模式二：&quot;entry&quot; (入口全局注入)"></a>🟡 模式二：<code>&quot;entry&quot;</code> (入口全局注入)</h4><ul><li><p><strong>含义</strong>：需要在入口文件手动引入一次包。Babel 会根据你的目标浏览器环境（<code>.browserslistrc</code>），把那些目标浏览器不支持的 API 的 Polyfill <strong>全部替换注入进来</strong>。</p></li><li><p>效果</p><figure class="highlight js"><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">// 你的源码 (src/main.js)</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js/stable&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Babel 编译后会变成这样拆分开的很多包：</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js/modules/es.array.unscopables.flat&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js/modules/es.array.unscopables.flat-map&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js/modules/es.object.assign&quot;</span>;</span><br><span class="line"><span class="comment">// ... 塞入一堆目标浏览器不支持的 API，不管你代码里有没有用到它。</span></span><br></pre></td></tr></table></figure></li><li><p><strong>痛点</strong>：它不管你代码里实际写没写 <code>Promise</code> 或者 <code>Map</code>，只要目标浏览器不支持，它就全盘塞进去。虽然比手动引入完整的 <code>core-js</code> 小了一点，但依然会有大量<strong>你根本没用到的代码被打包进去了，严重拖慢网页加载速度。</strong></p></li></ul><h4 id="🟢-模式三：-usage-按需注入-✨-最佳实践-✨"><a href="#🟢-模式三：-usage-按需注入-✨-最佳实践-✨" class="headerlink" title="🟢 模式三：&quot;usage&quot; (按需注入 - ✨ 最佳实践 ✨)"></a>🟢 模式三：<code>&quot;usage&quot;</code> (按需注入 - ✨ 最佳实践 ✨)</h4><ul><li><p><strong>含义</strong>：Babel 会变得非常智能。它会<strong>逐行扫描你写的每个 JS 文件</strong>，看看你<strong>到底用到了哪些新 API</strong>，然后再结合目标浏览器环境，<strong>只把你用到的并且浏览器不支持的 API 的 Polyfill 悄悄地塞到文件顶部</strong>。</p></li><li><p><strong>前提条件</strong>：不需要你手动在入口 <code>import &#39;core-js&#39;</code>。</p></li><li><p>效果演示： 假设你只写了一句代码：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 你的源码 A.js</span><br><span class="line">const arr = [1, 2, 3].includes(2);</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>Babel 扫描发现：哟，你用了 <code>includes</code>！再一看你的目标配置说要兼容 IE11。IE11 不支持 <code>includes</code>。那么 Babel 就会帮你把编译结果偷偷变成这样：</p><figure class="highlight js"><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">// Babel 编译后的 A.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;core-js/modules/es.array.includes.js&quot;</span>; <span class="comment">// 【只为你按需引入这一个文件！】</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> arr = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>].<span class="title function_">includes</span>(<span class="number">2</span>);</span><br></pre></td></tr></table></figure></li></ul><h3 id="🎯-总结"><a href="#🎯-总结" class="headerlink" title="🎯 总结"></a>🎯 总结</h3><p>配置了 <code>&quot;useBuiltIns&quot;: &quot;usage&quot;</code> 和 <code>&quot;corejs&quot;: &quot;3&quot;</code> 后，你得到了完美的开发体验：</p><ol><li><strong>省心</strong>：再也不用管什么目标浏览器支不支持这个 API 了，也不用手动引入完整的 <code>core-js</code>，随便放开手脚写最新的 ES6+ 代码。</li><li><strong>极致的打包体积</strong>：Babel 像个精确的手术刀一样，你用什么它引什么，一行多余的废代码都不打进最后的包里。它极大程度上优化了前端项目的首屏加载性能。</li></ol><h2 id="8-一句话总结"><a href="#8-一句话总结" class="headerlink" title="8. 一句话总结"></a>8. 一句话总结</h2><ol><li><code>@babel/plugin-transform-runtime</code> 是编译时的 <strong>“搬运工”</strong>，负责修改你的代码并添加 <code>import</code> 引用。</li><li><code>@babel/runtime</code> 和 <code>@babel/runtime-corejs3</code> 是运行时的 <strong>“功能仓库”</strong>，承载了实际要运行的代码逻辑。</li><li>三者配合使用的终极目标是：<strong>减少打包体积</strong>、<strong>按需引入 Polyfill</strong>、<strong>避免全局作用域污染</strong>。****</li></ol>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;在使用 Babel 进行 JavaScript 代码编译时，为了优化打包体积并处理新增 API，我们经常会用到 &lt;code&gt;@babel/plugin-transform-runtime&lt;/code&gt; 以及它的两个核心依赖库。本文档将详细梳理它们的作用、关系以及最佳实践配置</summary>
      
    
    
    
    <category term="工程化与质量" scheme="http://example.com/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96%E4%B8%8E%E8%B4%A8%E9%87%8F/"/>
    
    
    <category term="webpack" scheme="http://example.com/tags/webpack/"/>
    
    <category term="Babel" scheme="http://example.com/tags/Babel/"/>
    
    <category term="前端构建" scheme="http://example.com/tags/%E5%89%8D%E7%AB%AF%E6%9E%84%E5%BB%BA/"/>
    
  </entry>
  
  <entry>
    <title>Sentry集成与hidden-source-map源码溯源指南</title>
    <link href="http://example.com/posts/f8a2c3d9xr.html"/>
    <id>http://example.com/posts/f8a2c3d9xr.html</id>
    <published>2026-03-01T23:50:00.000Z</published>
    <updated>2026-04-02T08:39:33.923Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>📚 本方案将指导你如何在 Webpack 5 项目中集成 Sentry，实现生产环境的自动错误捕获和源码还原。</p></blockquote><hr><h2 id="一、背景知识"><a href="#一、背景知识" class="headerlink" title="一、背景知识"></a>一、背景知识</h2><h3 id="1-1、什么是-SourceMap？"><a href="#1-1、什么是-SourceMap？" class="headerlink" title="1.1、什么是 SourceMap？"></a><strong><font color='red'>1.1、什么是 SourceMap？</font></strong></h3><p>SourceMap（源代码映射）是一个用来生成源代码与构建后代码一一映射的文件的方案。</p><p>它会生成一个 <code>xxx.map</code> 文件，里面包含源代码和构建后代码每一行、每一列的映射关系。当构建后代码出错了，会通过 <code>xxx.map</code> 文件，从构建后代码出错位置找到映射后源代码出错位置，从而让浏览器提示源代码文件出错位置，帮助我们更快的找到错误根源。</p><hr><h3 id="1-2、开发环境的-SourceMap-配置"><a href="#1-2、开发环境的-SourceMap-配置" class="headerlink" title="1.2、开发环境的 SourceMap 配置"></a><strong><font color='red'>1.2、开发环境的 SourceMap 配置</font></strong></h3><p>开发环境推荐使用 <code>cheap-module-source-map</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.dev.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">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="string">&quot;cheap-module-source-map&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><table><thead><tr><th>特点</th><th>说明</th></tr></thead><tbody><tr><td>✅ 优点</td><td>打包编译速度快，只包含行映射，可直接查看源码</td></tr><tr><td>❌ 缺点</td><td>没有列映射（不影响开发调试）</td></tr></tbody></table><hr><h3 id="1-3、生产环境的-SourceMap-配置"><a href="#1-3、生产环境的-SourceMap-配置" class="headerlink" title="1.3、生产环境的 SourceMap 配置"></a><strong><font color='red'>1.3、生产环境的 SourceMap 配置</font></strong></h3><p>在生产环境中，是否配置 SourceMap 取决于<strong>公司对性能、安全和调试便利性</strong>的权衡。目前主流的做法有以下几种：</p><h4 id="方案一：配置-source-map（最常见）"><a href="#方案一：配置-source-map（最常见）" class="headerlink" title="方案一：配置 source-map（最常见）"></a>方案一：配置 <code>source-map</code>（最常见）</h4><p>大多数中大型项目的 <code>webpack.prod.js</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.prod.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">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="string">&quot;source-map&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>特点：</strong></p><ul><li>打包后会生成主 JS 文件（如 <code>main.js</code>）和一个对应的 <code>.map</code> 文件</li><li>打包文件末尾会添加 <code>//# sourceMappingURL=xxx.js.map</code> 注释</li></ul><table><thead><tr><th>优点</th><th>缺点</th></tr></thead><tbody><tr><td>✅ 错误溯源方便，监控系统可利用 <code>.map</code> 文件还原源码行数</td><td>❌ 打包编译速度慢</td></tr><tr><td>✅ 主包体积不受影响，<code>.map</code> 文件独立，用户访问时不会下载</td><td>❌ 源码暴露风险，懂行的用户可通过控制台查看源码</td></tr></tbody></table><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228165327466.png" alt="image-20260228165327466"></p><blockquote><p><code>//# sourceMappingURL</code> 的核心作用：<strong>告诉浏览器去哪里下载对应的 SourceMap 文件，从而实现生产代码到开发源码的精准映射。</strong></p></blockquote><h4 id="方案二：配置-hidden-source-map（推荐-Sentry）"><a href="#方案二：配置-hidden-source-map（推荐-Sentry）" class="headerlink" title="方案二：配置 hidden-source-map（推荐 + Sentry）"></a>方案二：配置 <code>hidden-source-map</code>（推荐 + Sentry）</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.prod.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">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="string">&quot;hidden-source-map&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>特点：</strong></p><ul><li>✅ 会生成 <code>.map</code> 文件</li><li>❌ 打包后的 JS 文件结尾<strong>不会产生</strong> <code>//# sourceMappingURL=...</code> 的引用注释</li><li>🔒 浏览器控制台不会自动关联源码，报错只显示压缩后的行数</li><li>📤 可将 <code>.map</code> 文件上传到 Sentry 等私有监控平台，实现内部调试</li></ul><table><thead><tr><th>优势</th><th>说明</th></tr></thead><tbody><tr><td>🔒 <strong>安全性</strong></td><td>生成的 <code>.map</code> 文件不会在代码中引用，用户无法在浏览器控制台直接查看源码</td></tr><tr><td>🔐 <strong>隐私性</strong></td><td>防止源码通过浏览器开发者工具被轻易获取，保护业务逻辑</td></tr><tr><td>🎯 <strong>精准定位</strong></td><td>Sentry 服务器持有 SourceMap，可以精准还原错误位置</td></tr><tr><td>⚖️ <strong>平衡性</strong></td><td>既保证了线上代码的安全性，又能在 Sentry 后台看到详细的错误堆栈</td></tr></tbody></table><h4 id="方案三：不配置-SourceMap（极致安全）"><a href="#方案三：不配置-SourceMap（极致安全）" class="headerlink" title="方案三：不配置 SourceMap（极致安全）"></a>方案三：不配置 SourceMap（极致安全）</h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.prod.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">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="literal">false</span>,  <span class="comment">// 或不写此配置</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>特点：</strong></p><ul><li>❌ 不生成任何映射文件</li><li>❌ 压缩后的代码完全无法调试</li><li>⚠️ 线上出 bug 只能通过”盲猜”或在本地模拟数据来复现</li></ul><hr><h3 id="1-4、生产环境配置总结"><a href="#1-4、生产环境配置总结" class="headerlink" title="1.4、生产环境配置总结"></a><strong><font color='red'>1.4、生产环境配置总结</font></strong></h3><table><thead><tr><th>项目类型</th><th>推荐配置</th><th>说明</th></tr></thead><tbody><tr><td>中小型项目&#x2F;后台管理系统</td><td><code>source-map</code></td><td>调试效率第一</td></tr><tr><td>大型互联网产品&#x2F;C端应用</td><td><code>hidden-source-map</code> + SentrySentry&#x2F;私有监控</td><td>兼顾安全与调试</td></tr><tr><td>金融&#x2F;保密项目</td><td>不配置</td><td>极致安全</td></tr><tr><td>个人练习项目</td><td><code>source-map</code></td><td>方便学习调试</td></tr></tbody></table><hr><h3 id="1-5、hidden-source-map-Sentry-工作原理"><a href="#1-5、hidden-source-map-Sentry-工作原理" class="headerlink" title="1.5、hidden-source-map + Sentry 工作原理"></a><strong><font color='red'>1.5、hidden-source-map + Sentry 工作原理</font></strong></h3><figure class="highlight plaintext"><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><br><span class="line">│                      构建阶段                                </span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│  源码 (main.js)  ──编译压缩──&gt;  bundle.js + bundle.js.map </span><br><span class="line">│                                                            </span><br><span class="line">│  hidden-source-map 模式：                                   </span><br><span class="line">│  ✅ 生成 bundle.js.map 文件                                 </span><br><span class="line">│  ❌ 不在 bundle.js 末尾添加 sourceMappingURL 注释           </span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                            │</span><br><span class="line">                            ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      部署阶段                                </span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│  bundle.js      ──上传到服务器──&gt;  用户访问（无源码暴露）    </span><br><span class="line">│  bundle.js.map  ──上传到 Sentry──&gt; 错误追踪时还原源码       </span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br><span class="line">                            │</span><br><span class="line">                            ▼</span><br><span class="line">┌─────────────────────────────────────────────────────────────┐</span><br><span class="line">│                      错误发生时                              </span><br><span class="line">├─────────────────────────────────────────────────────────────┤</span><br><span class="line">│  用户浏览器：只能看到 bundle.js:1:23456                      </span><br><span class="line">│  Sentry 后台：通过 .map 文件还原为 main.js:42:15            </span><br><span class="line">└─────────────────────────────────────────────────────────────┘</span><br></pre></td></tr></table></figure><hr><h2 id="二、准备工作"><a href="#二、准备工作" class="headerlink" title="二、准备工作"></a>二、准备工作</h2><h3 id="2-1、注册-Sentry-账号"><a href="#2-1、注册-Sentry-账号" class="headerlink" title="2.1、注册 Sentry 账号"></a><strong><font color='red'>2.1、注册 Sentry 账号</font></strong></h3><p>访问 <a href="https://sentry.io/">Sentry.io</a> 注册账号</p><h3 id="2-2、创建项目（Project）"><a href="#2-2、创建项目（Project）" class="headerlink" title="2.2、创建项目（Project）"></a><strong><font color='red'>2.2、创建项目（Project）</font></strong></h3><p>选择 Browser JavaScript（根据自己项目来）</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228151819711.png" alt="image-20260228151819711"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228152001432.png" alt="image-20260228152001432"></p><h3 id="2-3、设置-Sentry-SDK"><a href="#2-3、设置-Sentry-SDK" class="headerlink" title="2.3、设置 Sentry SDK"></a><strong><font color='red'>2.3、设置 Sentry SDK</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228152054610.png" alt="image-20260228152054610"></p><h3 id="2-4、获取-DSN"><a href="#2-4、获取-DSN" class="headerlink" title="2.4、获取 DSN"></a><strong><font color='red'>2.4、获取 DSN</font></strong></h3><p>格式类似 <code>https://xxxxxx@sentry.io/12345</code>。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228152309729.png" alt="image-20260228152309729"></p><p><strong>或者选择项目后，再鼠标放置设置图标上：</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228155828037.png" alt="image-20260228155828037"></p><hr><h2 id="三、安装依赖"><a href="#三、安装依赖" class="headerlink" title="三、安装依赖"></a>三、安装依赖</h2><p>在项目根目录下执行：</p><figure class="highlight bash"><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">npm install @sentry/browser</span><br><span class="line">npm install @sentry/webpack-plugin --save-dev</span><br></pre></td></tr></table></figure><hr><h2 id="四、代码集成"><a href="#四、代码集成" class="headerlink" title="四、代码集成"></a>四、代码集成</h2><h3 id="4-1、SDK-初始化"><a href="#4-1、SDK-初始化" class="headerlink" title="4.1、SDK 初始化"></a><strong><font color='red'>4.1、SDK 初始化</font></strong></h3><p>在入口文件（如 <code>src/main.js</code>）的顶部引入并初始化：</p><p><strong><code>main.js</code></strong></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> * <span class="keyword">as</span> <span class="title class_">Sentry</span> <span class="keyword">from</span> <span class="string">&quot;@sentry/browser&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">Sentry</span>.<span class="title function_">init</span>(&#123;</span><br><span class="line">  <span class="attr">dsn</span>: <span class="string">&quot;https://5fef42a12263056007b7df042b39a53b@o4510962147393536.ingest.de.sentry.io/4510962153619536&quot;</span>,</span><br><span class="line">  <span class="attr">integrations</span>: [</span><br><span class="line">    <span class="title class_">Sentry</span>.<span class="title function_">browserTracingIntegration</span>(),</span><br><span class="line">    <span class="title class_">Sentry</span>.<span class="title function_">replayIntegration</span>(),</span><br><span class="line">  ],</span><br><span class="line">  <span class="comment">// 性能监控采样率</span></span><br><span class="line">  <span class="attr">tracesSampleRate</span>: <span class="number">1.0</span>,</span><br><span class="line">  <span class="comment">// Session Replay 采样率</span></span><br><span class="line">  <span class="attr">replaysSessionSampleRate</span>: <span class="number">0.1</span>,</span><br><span class="line">  <span class="attr">replaysOnErrorSampleRate</span>: <span class="number">1.0</span>,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr><h2 id="五、Webpack-配置自动上传-SourceMap"><a href="#五、Webpack-配置自动上传-SourceMap" class="headerlink" title="五、Webpack 配置自动上传 SourceMap"></a>五、Webpack 配置自动上传 SourceMap</h2><p>这是生产环境最重要的步骤，确保 Sentry 能根据混淆代码还原源码。</p><p><strong><code>webpack.prod.js</code></strong></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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> &#123; sentryWebpackPlugin &#125; = <span class="built_in">require</span>(<span class="string">&quot;@sentry/webpack-plugin&quot;</span>);</span><br><span class="line"></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">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">  <span class="attr">devtool</span>: <span class="string">&quot;hidden-source-map&quot;</span>, <span class="comment">// ✨ 安全推荐：生成 map 但不添加关联注释，防止浏览器控制台泄露源码</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">// ... 其他插件</span></span><br><span class="line">    <span class="title function_">sentryWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="attr">authToken</span>: <span class="string">&quot;你的 Sentry Token&quot;</span>,</span><br><span class="line">      <span class="attr">org</span>: <span class="string">&quot;你的组织名&quot;</span>,</span><br><span class="line">      <span class="attr">project</span>: <span class="string">&quot;你的项目名&quot;</span>,</span><br><span class="line">      <span class="attr">sourcemaps</span>: &#123;</span><br><span class="line">        <span class="attr">assets</span>: [<span class="string">&quot;./dist/**&quot;</span>],</span><br><span class="line">        <span class="attr">ignore</span>: [<span class="string">&quot;node_modules&quot;</span>, <span class="string">&quot;config&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="5-1、authToken"><a href="#5-1、authToken" class="headerlink" title="5.1、authToken"></a><strong><font color='red'>5.1、authToken</font></strong></h3><p><strong>1. 推荐使用：Personal Tokens（个人令牌）</strong></p><p>这是最通用的选择，也是 Sentry 引导页面通常推荐的方式。</p><ul><li><strong>优点</strong>：配置最简单。在 <code>sentryWebpackPlugin</code> 配置中，你只需要提供一个 <code>authToken</code> 即可。它代表的是”你”这个开发者在 Sentry 里的权限。</li><li><strong>权限要求</strong>：创建时至少勾选 <code>project:write</code>、<code>project:releases</code> 和 <code>org:read</code>。</li></ul><p><strong>2. 企业级选择：Organization Tokens（组织令牌）</strong></p><p>这通常用于公司层面的 CI&#x2F;CD 自动化流水线。</p><ul><li><strong>优点</strong>：与具体的个人账号解耦。即使某个员工离职了，只要组织令牌还在，自动化部署就不会挂掉。</li><li><strong>配置差异</strong>：使用组织令牌时，有时在较老的插件版本中需要明确指定组织范围，但在最新的 <code>sentryWebpackPlugin</code> (v2&#x2F;v5) 中，它的使用方式和 Personal Token 基本一致。</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228155519933.png" alt="image-20260228155519933"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228154656332.png" alt="image-20260228154656332"></p><h3 id="5-2、org-组织名"><a href="#5-2、org-组织名" class="headerlink" title="5.2、org 组织名"></a><strong><font color='red'>5.2、org 组织名</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228155436973.png" alt="image-20260228155436973"></p><h3 id="5-3、project-项目名"><a href="#5-3、project-项目名" class="headerlink" title="5.3、project 项目名"></a><strong><font color='red'>5.3、project 项目名</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228155646709.png" alt="image-20260228155646709"></p><hr><h2 id="六、验证"><a href="#六、验证" class="headerlink" title="六、验证"></a>六、验证</h2><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">sum</span> = (<span class="params">...args</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> args.<span class="title function_">reduce</span>(<span class="function">(<span class="params">pre, cur</span>) =&gt;</span> pre + cur, <span class="number">0</span>)();</span><br><span class="line">&#125;;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>));</span><br></pre></td></tr></table></figure><p>执行 <code>npm run build</code> 打包并运行代码。片刻后，你将在 Sentry 后台看到这个错误，并且它能精准定位到 <code>main.js</code> 的具体行数。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228160213461.png" alt="image-20260228160213461"></p><p>配置 <code>devtool: &quot;hidden-source-map&quot;</code> 后，浏览器控制台定位不到是哪个文件哪一行报错的。</p><p>Sentry 中则可以详细看到那个文件、那一行报错：</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228160425665.png" alt="image-20260228160425665"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;📚 本方案将指导你如何在 Webpack 5 项目中集成 Sentry，实现生产环境的自动错误捕获和源码还原。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;一、背景知识&quot;&gt;&lt;a href=&quot;#一、背景知识&quot; class=&quot;hea</summary>
      
    
    
    
    <category term="工程化与质量" scheme="http://example.com/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96%E4%B8%8E%E8%B4%A8%E9%87%8F/"/>
    
    
    <category term="Webpack" scheme="http://example.com/tags/Webpack/"/>
    
    <category term="Sentry" scheme="http://example.com/tags/Sentry/"/>
    
    <category term="SourceMap" scheme="http://example.com/tags/SourceMap/"/>
    
  </entry>
  
  <entry>
    <title>Webpack 5 基础完全指南</title>
    <link href="http://example.com/posts/w57b4c3d.html"/>
    <id>http://example.com/posts/w57b4c3d.html</id>
    <published>2026-02-27T22:31:00.000Z</published>
    <updated>2026-04-02T08:39:33.923Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>📚 本指南旨在帮助开发者从零开始掌握 Webpack 5 的核心概念与基础配置，涵盖样式、资源、JS 处理及生产环境优化等全方位知识。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E3%80%81%E5%89%8D%E8%A8%80">前言</a></li><li><a href="#%E4%BA%8C%E3%80%81%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8">基本使用</a></li><li><a href="#%E4%B8%89%E3%80%81Webpack%E5%9F%BA%E6%9C%AC%E9%85%8D%E7%BD%AE">Webpack基本配置</a></li><li><a href="#%E5%9B%9B%E3%80%81%E5%BC%80%E5%8F%91%E6%A8%A1%E5%BC%8F">开发模式</a></li><li><a href="#%E4%BA%94%E3%80%81%E6%A0%B7%E5%BC%8F%E8%B5%84%E6%BA%90%E5%A4%84%E7%90%86">样式资源处理</a></li><li><a href="#%E5%85%AD%E3%80%81%E5%85%B6%E4%BB%96%E8%B5%84%E6%BA%90%E5%A4%84%E7%90%86">其他资源处理</a></li><li><a href="#%E4%B8%83%E3%80%81%E5%A4%84%E7%90%86JavaScript%E8%B5%84%E6%BA%90">处理JavaScript资源</a></li><li><a href="#%E5%85%AB%E3%80%81Loader%E7%9A%84%E5%A4%9A%E7%A7%8D%E5%86%99%E6%B3%95">Loader的多种写法</a></li><li><a href="#%E4%B9%9D%E3%80%81%E5%A4%84%E7%90%86-Html-%E8%B5%84%E6%BA%90">处理 Html 资源</a></li><li><a href="#%E5%8D%81%E3%80%81%E5%BC%80%E5%8F%91%E6%9C%8D%E5%8A%A1%E5%99%A8&%E8%87%AA%E5%8A%A8%E5%8C%96">开发服务器&amp;自动化</a></li><li><a href="#%E5%8D%81%E4%B8%80%E3%80%81%E7%94%9F%E4%BA%A7%E6%A8%A1%E5%BC%8F">生产模式</a></li><li><a href="#%E5%8D%81%E4%BA%8C%E3%80%81CSS%E8%BF%9B%E9%98%B6%E4%BC%98%E5%8C%96">CSS进阶优化</a></li></ol><br><h2 id="一、前言"><a href="#一、前言" class="headerlink" title="一、前言"></a><strong>一、前言</strong></h2><h3 id="1-1、为什么需要打包工具？"><a href="#1-1、为什么需要打包工具？" class="headerlink" title="1.1、为什么需要打包工具？"></a><strong>1.1、为什么需要打包工具？</strong></h3><p>开发时，我们会使用框架（React、Vue），ES6 模块化语法，Less&#x2F;Sass 等 css 预处理器等语法进行开发。</p><p>这样的代码要想在浏览器运行必须经过编译成浏览器能识别的 JS、Css 等语法，才能运行。</p><p>所以我们需要打包工具帮我们做完这些事。</p><p>除此之外，打包工具还能压缩代码、做兼容性处理、提升代码性能等。</p><h3 id="1-2、有哪些打包工具？"><a href="#1-2、有哪些打包工具？" class="headerlink" title="1.2、有哪些打包工具？"></a><strong>1.2、有哪些打包工具？</strong></h3><ul><li>Grunt</li><li>Gulp</li><li>Parcel</li><li>Webpack</li><li>Rollup</li><li>Vite</li><li>…</li></ul><p>目前市面上最流量的是 Webpack，所以我们主要以 Webpack 来介绍使用打包工具</p><hr><h2 id="二、基本使用"><a href="#二、基本使用" class="headerlink" title="二、基本使用"></a><strong>二、基本使用</strong></h2><p><strong><code>Webpack</code> 是一个静态资源打包工具。</strong></p><p>它会以一个或多个文件作为打包的入口，将我们整个项目所有文件编译组合成一个或多个文件输出出去。</p><p>输出的文件就是编译好的文件，就可以在浏览器段运行了。</p><p>我们将 <code>Webpack</code> 输出的文件叫做 <code>bundle</code>。</p><h3 id="2-1、-功能介绍"><a href="#2-1、-功能介绍" class="headerlink" title="2.1、 功能介绍"></a><strong>2.1、 功能介绍</strong></h3><p>Webpack 本身功能是有限的:</p><ul><li>开发模式：仅能编译 JS 中的 <code>ES Module</code> 语法</li><li>生产模式：能编译 JS 中的 <code>ES Module</code> 语法，还能压缩 JS 代码</li></ul><p>默认情况下webpack：</p><ul><li>它能做什么： 当 Webpack 遇到你的 JS 文件里写了 import xxx from ‘.&#x2F;xxx’ 或是 export default xxx（甚至是 CommonJS 的 require）时，它能看懂！它知道这些文件之间有依赖关系，然后把它们“拼合（打包）”成一个或多个浏览器能直接运行的 JS 压缩包。</li><li>它不能做什么： 除此之外的其他所有现代 JS 高级语法（比如 箭头函数、解构赋值、Class 类、Promise、async&#x2F;await 等），Webpack 原生是完全不管的。你写的是箭头函数，它打包出来依然是箭头函数。</li></ul><h3 id="2-2、项目搭建"><a href="#2-2、项目搭建" class="headerlink" title="2.2、项目搭建"></a><strong>2.2、项目搭建</strong></h3><h4 id="1）初始化项目"><a href="#1）初始化项目" class="headerlink" title="1）初始化项目"></a><strong><span style='color:red'>1）初始化项目</span></strong></h4><p>会在当前文件夹生成一个基础的 <code>package.json</code> 文件。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm init -y</span><br></pre></td></tr></table></figure><p><strong>注意点1：</strong> <code>package.json</code> 中 <code>name</code> 字段不能叫做 <code>webpack</code>, 否则下一步会报错</p><p>**注意点2：**删除<code>package.json</code>中的<code>type&quot;: &quot;commonjs</code>否则使用ESM语法会报错<a href="https://zhbcloud.github.io/posts/w41d92f5.html">详情</a></p><h4 id="2）下载依赖"><a href="#2）下载依赖" class="headerlink" title="2）下载依赖"></a><strong><span style='color:red'>2）下载依赖</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i webpack webpack-cli -D</span><br></pre></td></tr></table></figure><h4 id="3）创建文件"><a href="#3）创建文件" class="headerlink" title="3）创建文件"></a><strong><span style='color:red'>3）创建文件</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// count.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">count</span>(<span class="params">x, y</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> x - y;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// sum.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">sum</span>(<span class="params">...args</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> args.<span class="title function_">reduce</span>(<span class="function">(<span class="params">p, c</span>) =&gt;</span> p + c, <span class="number">0</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> count <span class="keyword">from</span> <span class="string">&quot;./js/count&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> sum <span class="keyword">from</span> <span class="string">&quot;./js/sum&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">count</span>(<span class="number">2</span>, <span class="number">1</span>));</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>));</span><br></pre></td></tr></table></figure><h4 id="4）目录结构"><a href="#4）目录结构" class="headerlink" title="4）目录结构"></a><strong><span style='color:red'>4）目录结构</span></strong></h4><figure class="highlight text"><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">webpack_code # 项目根目录（所有指令必须在这个目录运行）</span><br><span class="line">    └── src # 项目源码目录</span><br><span class="line">    │   ├── js # js文件目录</span><br><span class="line">    │   │   ├── count.js</span><br><span class="line">    │   │   └── sum.js</span><br><span class="line">    │   └── main.js # 项目主文件</span><br><span class="line">    └── package.json</span><br></pre></td></tr></table></figure><h4 id="5）启用-Webpack"><a href="#5）启用-Webpack" class="headerlink" title="5）启用 Webpack"></a><strong><span style='color:red'>5）启用 Webpack</span></strong></h4><ul><li>开发模式</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack ./src/main.js --mode=development</span><br></pre></td></tr></table></figure><ul><li>生产模式</li></ul><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack ./src/main.js --mode=production</span><br></pre></td></tr></table></figure><p><code>npx webpack</code>: 是用来运行本地安装 <code>Webpack</code> 包的。</p><p><code>./src/main.js</code>: 指定 <code>Webpack</code> 从 <code>main.js</code> 文件开始打包，不但会打包 <code>main.js</code>，还会将其依赖也一起打包进来。</p><p><code>--mode=xxx</code>：指定模式（环境）。</p><h4 id="6）观察输出文件"><a href="#6）观察输出文件" class="headerlink" title="6）观察输出文件"></a><strong><span style='color:red'>6）观察输出文件</span></strong></h4><p>默认 <code>Webpack</code> 会将文件打包输出到 <code>dist</code> 目录下，我们查看 <code>dist</code> 目录下文件情况就好了</p><h3 id="2-3、小结"><a href="#2-3、小结" class="headerlink" title="2.3、小结"></a><strong>2.3、小结</strong></h3><p><code>Webpack</code> 本身功能比较少，只能处理 <code>js</code> 资源，一旦遇到 <code>css</code> 等其他资源就会报错。所以我们学习 <code>Webpack</code>，就是主要学习如何处理其他资源。</p><hr><h2 id="三、Webpack基本配置"><a href="#三、Webpack基本配置" class="headerlink" title="三、Webpack基本配置"></a><strong>三、Webpack基本配置</strong></h2><p>在开始使用 <code>Webpack</code> 之前，我们需要对 <code>Webpack</code> 的配置有一定的认识。</p><h3 id="3-1、五大核心概念"><a href="#3-1、五大核心概念" class="headerlink" title="3.1、五大核心概念"></a><strong>3.1、五大核心概念</strong></h3><h4 id="1）entry（入口）"><a href="#1）entry（入口）" class="headerlink" title="1）entry（入口）"></a><strong><span style='color:red'>1）entry（入口）</span></strong></h4><p>指示 Webpack 从哪个文件开始打包</p><h4 id="2）utput（输出）"><a href="#2）utput（输出）" class="headerlink" title="2）utput（输出）"></a><strong><span style='color:red'>2）utput（输出）</span></strong></h4><p>指示 Webpack 打包完的文件输出到哪里去，如何命名等</p><h4 id="3）loader（加载器）"><a href="#3）loader（加载器）" class="headerlink" title="3）loader（加载器）"></a><strong><span style='color:red'>3）loader（加载器）</span></strong></h4><p>webpack 本身只能处理 js、json 等资源，其他资源需要借助 loader，Webpack 才能解析</p><h4 id="4）plugins（插件）"><a href="#4）plugins（插件）" class="headerlink" title="4）plugins（插件）"></a><strong><span style='color:red'>4）plugins（插件）</span></strong></h4><p>loader 用于转换某些类型的模块，而插件则可以用于执行范围更广的任务。包括：打包优化，资源管理，注入环境变量。</p><h4 id="5）mode（模式）"><a href="#5）mode（模式）" class="headerlink" title="5）mode（模式）"></a><strong><span style='color:red'>5）mode（模式）</span></strong></h4><ul><li>开发模式：development</li><li>生产模式：production</li></ul><h3 id="3-2、准备Webpack配置文件"><a href="#3-2、准备Webpack配置文件" class="headerlink" title="3.2、准备Webpack配置文件"></a><strong>3.2、准备Webpack配置文件</strong></h3><h4 id="1）创建配置文件"><a href="#1）创建配置文件" class="headerlink" title="1）创建配置文件"></a><strong><span style='color:red'>1）创建配置文件</span></strong></h4><p>在项目<strong>根目录</strong>下新建文件：<code>webpack.config.js</code></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// 入口文件</span></span><br><span class="line">  <span class="attr">entry</span>: <span class="string">&quot;./src/main.js&quot;</span>,</span><br><span class="line">  <span class="comment">// 出口文件</span></span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;[name].js&quot;</span>,</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;dist&quot;</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// loader加载器</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// plugin插件</span></span><br><span class="line">  <span class="attr">plugins</span>: [],</span><br><span class="line">  <span class="comment">// mode模式</span></span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>输出命名解析</strong></p><p><code>[name].js</code> &#x3D; <code>[文件名].js</code></p><p><strong>为什么入口文件路径使用相对路径，而输出文件路径是绝对路径</strong></p><ul><li><strong><code>entry</code></strong> 的值是交给 <strong>Webpack 的模块解析器（Module Resolver）</strong> 来处理的。Webpack 的模块解析器本身就被设计为能够识别并处理相对路径，它会将相对路径解析为<strong>相对于当前工作目录</strong> 。</li><li><strong><code>output.path</code></strong> 是交给操作系统的<strong>文件系统 API</strong> 来处理的（用于写入磁盘）。<strong>Webpack 官方明确要求这里必须传入绝对路径</strong>，传入相对路径会报错或行为不可预测。</li></ul><h4 id="2、运行指令"><a href="#2、运行指令" class="headerlink" title="2、运行指令"></a><strong><span style='color:red'>2、运行指令</span></strong></h4><p>配置了<code>webpack.config.js</code>，运行指令会自动去查询webpack的配置文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack</span><br></pre></td></tr></table></figure><p>此时功能和之前一样，也不能处理样式资源</p><h3 id="3-3、小结"><a href="#3-3、小结" class="headerlink" title="3.3、小结"></a><strong>3.3、小结</strong></h3><p>Webpack 将来都通过 <code>webpack.config.js</code> 文件进行配置，来增强 Webpack 的功能。我们后面会以两个模式来分别搭建 Webpack 的配置，先进行开发模式，再完成生产模式。</p><hr><h2 id="四、开发模式"><a href="#四、开发模式" class="headerlink" title="四、开发模式"></a><strong>四、开发模式</strong></h2><p>开发模式顾名思义就是我们开发代码时使用的模式。</p><p>这个模式下我们主要做两件事：</p><p> <strong>1、编译代码，使浏览器能识别运行</strong></p><p>开发时我们有样式资源、字体图标、图片资源、html 资源等，webpack 默认都不能处理这些资源，所以我们要加载配置来编译这些资源</p><p> <strong>2、代码质量检查，树立代码规范</strong></p><p>提前检查代码的一些隐患，让代码运行时能更加健壮。</p><p>提前检查代码规范和格式，统一团队编码风格，让代码更优雅美观。</p><hr><h2 id="五、样式资源处理"><a href="#五、样式资源处理" class="headerlink" title="五、样式资源处理"></a><strong>五、样式资源处理</strong></h2><p>Webpack 本身是不能识别样式资源的，所以我们需要借助 Loader 来帮助 Webpack 解析样式资源</p><p>我们找 Loader 都应该去官方文档中找到对应的 Loader，然后使用。</p><p><a href="https://www.webpackjs.com/loaders/">Webpack 官方 Loader 中文文档</a></p><p><a href="#%E5%8D%81%E4%BA%8C%E3%80%81CSS%E8%BF%9B%E9%98%B6%E4%BC%98%E5%8C%96">CSS进阶优化</a></p><h3 id="5-1、处理CSS资源"><a href="#5-1、处理CSS资源" class="headerlink" title="5.1、处理CSS资源"></a><strong>5.1、处理CSS资源</strong></h3><h4 id="css-loader-style-loader"><a href="#css-loader-style-loader" class="headerlink" title="css-loader &amp; style-loader"></a>css-loader &amp; style-loader</h4><h4 id="1）安装包"><a href="#1）安装包" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i css-loader style-loader -D</span><br></pre></td></tr></table></figure><h4 id="2）功能介绍"><a href="#2）功能介绍" class="headerlink" title="2）功能介绍"></a><strong><span style='color:red'>2）功能介绍</span></strong></h4><ul><li><code>css-loader</code>：读取 <code>.css</code> 文件内容，将其<strong>转换为 JS 模块</strong>（<code>export default &quot;css字符串&quot;</code>），使 CSS 成为 JS 依赖图的一部分，从而被打包进 bundle</li><li><code>style-loader</code>：接收 <code>css-loader</code> 输出的 JS 模块，在浏览器<strong>运行时</strong>动态创建 <code>&lt;style&gt;</code> 标签并插入DOM中</li></ul><h4 id="3）配置"><a href="#3）配置" class="headerlink" title="3）配置"></a><strong><span style='color:red'>3）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 用来匹配 .css 结尾的文件</span></span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="comment">// use 数组里面 Loader 执行顺序是从右到左，先执行css-loader再执行style-loader</span></span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="4）测试验证"><a href="#4）测试验证" class="headerlink" title="4）测试验证"></a><strong><span style='color:red'>4）测试验证</span></strong></h4><figure class="highlight text"><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></pre></td><td class="code"><pre><span class="line">webpac5_code/                     # 项目根目录（所有指令必须在这个目录运行）</span><br><span class="line">    ├── dist/                   # 打包输出目录（由 Webpack 自动生成）</span><br><span class="line">    ├── node_modules/           # 第三方依赖包目录（由 npm 自动生成）</span><br><span class="line">    ├── public/                 # 静态资源目录</span><br><span class="line">    │   └── index.html          # HTML 模板文件</span><br><span class="line">    ├── src/                    # 项目源码目录</span><br><span class="line">    │   ├── css/                # 样式文件目录</span><br><span class="line">    │   │   └── index.css</span><br><span class="line">    │   ├── js/                 # JS 文件目录（当前为空）</span><br><span class="line">    │   └── main.js             # 项目主文件（入口文件）</span><br><span class="line">    ├── .editorconfig           # 编辑器统一配置</span><br><span class="line">    ├── .gitignore              # Git 忽略规则</span><br><span class="line">    ├── package.json            # 项目依赖与脚本配置</span><br><span class="line">    ├── package-lock.json       # 依赖版本锁定文件</span><br><span class="line">    └── webpack.config.js       # Webpack 基础配置（或通用配置）</span><br></pre></td></tr></table></figure><p><strong><code>src\css\index.css</code></strong></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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong><code>src\main.js</code></strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;./css/index.css&quot;</span>;</span><br></pre></td></tr></table></figure><p><strong><code>public\index.html</code></strong></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></pre></td><td class="code"><pre><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;en&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 class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 引入打包后的js文件，才能看到效果 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;../dist/main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</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><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 准备一个使用样式的 DOM 容器 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">div</span> <span class="attr">class</span>=<span class="string">&quot;box&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><p><strong><code>运行打包指令</code></strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack    // 打开 index.html 页面查看效果</span><br></pre></td></tr></table></figure><h4 id="5）使用css加载器前后对比"><a href="#5）使用css加载器前后对比" class="headerlink" title="5）使用css加载器前后对比"></a><strong><span style='color:red'>5）使用css加载器前后对比</span></strong></h4><ul><li>**使用前：**直接报错</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260305104254181.png" alt="image-20260305104254181"></p><ul><li>**使用后：**会生成一个style标签</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260305104729097.png" alt="image-20260305104729097"></p><h3 id="5-2、处理-Less-资源"><a href="#5-2、处理-Less-资源" class="headerlink" title="5.2、处理 Less 资源"></a><strong>5.2、处理 Less 资源</strong></h3><h4 id="less-loader-less"><a href="#less-loader-less" class="headerlink" title="less-loader &amp; less"></a>less-loader &amp; less</h4><h4 id="1）安装包-1"><a href="#1）安装包-1" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i less-loader less -D</span><br></pre></td></tr></table></figure><h4 id="2）能介绍"><a href="#2）能介绍" class="headerlink" title="2）能介绍"></a><strong><span style='color:red'>2）能介绍</span></strong></h4><ul><li><code>less-loader</code>：负责将 Less 文件编译成 Css 文件</li><li><code>less</code>：<code>less-loader</code> 依赖 <code>less</code> 进行编译</li></ul><h4 id="3）配置-1"><a href="#3）配置-1" class="headerlink" title="3）配置"></a><strong><span style='color:red'>3）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;less-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="5-3、处理-Sass-和-Scss-资源"><a href="#5-3、处理-Sass-和-Scss-资源" class="headerlink" title="5.3、处理 Sass 和 Scss 资源"></a><strong>5.3、处理 Sass 和 Scss 资源</strong></h3><h4 id="sass-loader-sass"><a href="#sass-loader-sass" class="headerlink" title="sass-loader &amp; sass"></a>sass-loader &amp; sass</h4><h4 id="1）安装包-2"><a href="#1）安装包-2" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i sass-loader sass -D</span><br></pre></td></tr></table></figure><h4 id="2）功能介绍-1"><a href="#2）功能介绍-1" class="headerlink" title="2）功能介绍"></a><strong><span style='color:red'>2）功能介绍</span></strong></h4><ul><li><code>sass-loader</code>：负责将 Sass 文件编译成 css 文件</li><li><code>sass</code>：<code>sass-loader</code> 依赖 <code>sass</code> 进行编译</li></ul><h4 id="3）配置-2"><a href="#3）配置-2" class="headerlink" title="3）配置"></a><strong><span style='color:red'>3）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.s[ac]ss$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;sass-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="5-4、处理Stylus-资源"><a href="#5-4、处理Stylus-资源" class="headerlink" title="5.4、处理Stylus 资源"></a><strong>5.4、处理Stylus 资源</strong></h3><h4 id="stylus-loader-stylus"><a href="#stylus-loader-stylus" class="headerlink" title="stylus-loader &amp; stylus"></a>stylus-loader &amp; stylus</h4><h4 id="1）安装包-3"><a href="#1）安装包-3" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i stylus-loader stylus -D</span><br></pre></td></tr></table></figure><h4 id="2）能介绍-1"><a href="#2）能介绍-1" class="headerlink" title="2）能介绍"></a><strong><span style='color:red'>2）能介绍</span></strong></h4><ul><li><code>sass-loader</code>：负责将 Sass 文件编译成 css 文件</li><li><code>sass</code>：<code>sass-loader</code> 依赖 <code>sass</code> 进行编译</li></ul><h4 id="3）配置-3"><a href="#3）配置-3" class="headerlink" title="3）配置"></a><strong><span style='color:red'>3）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.styl$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;stylus-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><hr><h2 id="六、其他资源处理"><a href="#六、其他资源处理" class="headerlink" title="六、其他资源处理"></a><strong>六、其他资源处理</strong></h2><h3 id="图片-字体图标-音视频资源等"><a href="#图片-字体图标-音视频资源等" class="headerlink" title="图片&#x2F;字体图标&#x2F;音视频资源等"></a>图片&#x2F;字体图标&#x2F;音视频资源等</h3><p>Webpack 5 用 <strong>Asset Modules</strong> 统一替代了以下四个旧版 loader，资源会自动输出到dist目录下</p><table><thead><tr><th align="left">Webpack 4 时代（需安装）</th><th align="left">Webpack 5 内置 <code>type</code> 替代</th><th align="left">作用</th></tr></thead><tbody><tr><td align="left"><code>file-loader</code></td><td align="left"><code>asset/resource</code></td><td align="left">输出为独立文件，返回 URL</td></tr><tr><td align="left"><code>url-loader</code></td><td align="left"><code>asset/inline</code></td><td align="left">转为 base64 Data URI 内联</td></tr><tr><td align="left"><code>raw-loader</code></td><td align="left"><code>asset/source</code></td><td align="left">读取文件原始文本内容（字符串）</td></tr><tr><td align="left"><code>url-loader</code>（自动模式）</td><td align="left"><code>asset</code></td><td align="left">根据阈值自动选择 resource 或 inline</td></tr></tbody></table><p>**<code>webpack5</code>**中资源模块类型(asset module type)，通过添加 4 种新的模块类型，来替换所有这些 loader：</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>;</span><br></pre></td></tr></table></figure><ul><li><p><code>asset/resource</code><strong>（默认值）</strong></p><ul><li>作用：将资源文件原封不动地打包输出到一个单独的文件中，并导出该文件的 URL 路径。</li><li>替代：Webpack 4 中的 <code>file-loader</code>。</li><li>适用场景：较大的图片文件、字体文件（woff2, eot 等）、音视频文件等。</li></ul></li><li><p><code>asset/inline</code></p><ul><li>作用：将资源文件转换为 Data URI（通常是 Base64 编码的字符串），并直接内嵌到打包后的 JS（或 CSS）文件中，不会生成独立的资源文件。</li><li>替代：Webpack 4 中的<code> url-loader</code>（且不配置 limit 限制大小的情况）。</li><li>适用场景：极小的图标（如 1kb、2kb 的小 png）。好处是可以减少网页加载时的 HTTP 请求数量；坏处是如果把大图片转成 Base64，会让你的 JS 文件体积急剧增大。</li></ul></li><li><p><code>asset/source</code></p><ul><li>作用：导出资源的源代码（把文件内容作为字符串原样导出）。</li><li>替代：Webpack 4 中的 <code>raw-loader</code>。</li><li>适用场景：需要读取文本内容时，比如导入 .txt 文本文件、.md Markdown 文件，或者你想把 SVG 图标直接当成 HTML 字符串插入到页面中时。</li></ul></li><li><p><code>asset</code> <strong>(最常用)</strong></p><ul><li><p>作用：这是一个智能类型。它会在 asset&#x2F;resource（输出文件）和 asset&#x2F;inline（转 Base64 内嵌）之间自动做出选择。</p></li><li><p>替代：Webpack 4 中配置了文件体积限制（limit）的 <code>url-loader</code>。</p></li><li><p>适用场景：常规的图片处理。</p></li><li><p>默认规则：如果文件大小小于 8kb，Webpack 会自动将其当作 asset&#x2F;inline 处理（转 Base64）；如果文件大小大于 8kb，则将其当作 asset&#x2F;resource 处理（输出为单独文件）。你可以通过配置</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(png|jpe?g|gif|webp|svg)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset&quot;</span>,</span><br><span class="line">        <span class="attr">parser</span>: &#123;</span><br><span class="line">          <span class="attr">dataUrlCondition</span>: &#123;</span><br><span class="line">            <span class="attr">maxSize</span>: <span class="number">10</span> * <span class="number">1024</span>, <span class="comment">// 小于10kb的图片会被base64处理</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><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul></li></ul><h3 id="6-1、图片资源处理"><a href="#6-1、图片资源处理" class="headerlink" title="6.1、图片资源处理"></a><strong>6.1、图片资源处理</strong></h3><p>过去在 Webpack4 时，我们处理图片资源通过 <code>file-loader</code> 和 <code>url-loader</code> 进行处理</p><p>因为webpack内置了 <strong>Asset Modules</strong>直接执行打包指令，默认<code>type: “asset/resource”</code></p><h4 id="1）使用默认配置构建"><a href="#1）使用默认配置构建" class="headerlink" title="1）使用默认配置构建"></a><strong><span style='color:red'>1）使用默认配置构建</span></strong></h4><p><strong>添加图片资源</strong></p><ul><li>src\images\1.png</li><li>src\images\2.png</li></ul><p><code>src\css\index.css</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="selector-class">.box1</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../images/1.png&quot;</span>);</span><br><span class="line">  <span class="attribute">background-size</span>: cover;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.box2</span> &#123;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">height</span>: <span class="number">100px</span>;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">&quot;../images/2.png&quot;</span>);</span><br><span class="line">  <span class="attribute">background-size</span>: cover;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>public\index.html</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></pre></td><td class="code"><pre><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;en&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 class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;../dist/main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</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><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">class</span>=<span class="string">&quot;box1&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">div</span> <span class="attr">class</span>=<span class="string">&quot;box2&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><p><strong>运行打包指令</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack</span><br></pre></td></tr></table></figure><p>打开 index.html 页面查看效果</p><h4 id="2）输出资源情况"><a href="#2）输出资源情况" class="headerlink" title="2）输出资源情况"></a><strong><span style='color:red'>2）输出资源情况</span></strong></h4><p>此时如果查看 dist 目录的话，会发现多了2张图片资源</p><p>因为 Webpack 会将所有打包好的资源输出到 dist 目录下</p><ul><li><strong>为什么样式资源没有呢？</strong></li></ul><p>因为经过 <code>style-loader</code> 的处理，在浏览器<strong>运行时</strong>动态创建 <code>&lt;style&gt;</code> 标签并插入 DOM中，所以没有额外输出出来</p><h4 id="3）对图片资源进行优化"><a href="#3）对图片资源进行优化" class="headerlink" title="3）对图片资源进行优化"></a><strong><span style='color:red'>3）对图片资源进行优化</span></strong></h4><p>将小于某个大小的图片转化成 data URI 形式（Base64 格式）</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(png|jpe?g|gif|webp|svg)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset&quot;</span>,</span><br><span class="line">        <span class="attr">parser</span>: &#123;</span><br><span class="line">          <span class="attr">dataUrlCondition</span>: &#123;</span><br><span class="line">            <span class="attr">maxSize</span>: <span class="number">10</span> * <span class="number">1024</span>, <span class="comment">// 小于10kb的图片会被base64处理</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><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><ul><li>优点：减少请求数量</li><li>缺点：体积变得更大</li></ul><p>此时输出的图片文件就只有两张，有一张图片以 data URI 形式内置到 js 中了 （注意：需要将上次打包生成的文件清空，再重新打包才有效果）</p><h3 id="6-2、自动清空上次打包资源"><a href="#6-2、自动清空上次打包资源" class="headerlink" title="6.2、自动清空上次打包资源"></a><strong>6.2、自动清空上次打包资源</strong></h3><p><code>clean: true</code></p><figure class="highlight js"><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">// webpack.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">output</span>: &#123;</span><br><span class="line">    <span class="attr">clean</span>: <span class="literal">true</span>, <span class="comment">// 开发模式没有输出，不需要清空输出结果</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h3 id="6-3、修改输出资源名称和路径"><a href="#6-3、修改输出资源名称和路径" class="headerlink" title="6.3、修改输出资源名称和路径"></a><strong>6.3、修改输出资源名称和路径</strong></h3><p>默认情况下，<code>asset</code> 模块以 <code>[hash][ext][query]</code> 文件名发送到输出目录dist。</p><p>可以通过在 webpack 配置中设置<code>Rule.generator.filename</code> 与 <a href="https://www.webpackjs.com/configuration/output/#outputassetmodulefilename"><code>output.assetModuleFilename</code></a>来修改此模板字符串，仅适用于 <code>asset</code> 和 <code>asset/resource</code> 模块类型。</p><h4 id="1）最佳实践"><a href="#1）最佳实践" class="headerlink" title="1）最佳实践"></a><strong><span style='color:red'>1）最佳实践</span></strong></h4><p>在 output 里设置一个比如叫 assets 的通用目录作为“兜底”，然后对于图片，再在 rule 里单独覆盖它</p><figure class="highlight js"><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="comment">// webpack.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">output</span>: &#123;</span><br><span class="line">    <span class="comment">// ✨ 全局兜底：所有没单独配置 generator 的资源，都放到 media 文件夹</span></span><br><span class="line">    <span class="attr">assetModuleFilename</span>: <span class="string">&quot;assets/media/[hash:10][ext][query]&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(png|jpe?g|gif|webp)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset&quot;</span>,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="comment">// ✨ 优先级更高：覆盖兜底配置，放到 images 目录</span></span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;images/[hash:10][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(woff2?|eot|ttf|otf)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>,</span><br><span class="line">        <span class="comment">// 这里不写 generator，就会默认触发兜底，被放进 media/ 目录下</span></span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><blockquote><p><code>[hash:10][ext][query]</code> &#x3D; <code>[ hash值取8位][文件扩展名][添加之前的query参数]</code></p></blockquote><h3 id="6-4、字体图标资源"><a href="#6-4、字体图标资源" class="headerlink" title="6.4、字体图标资源"></a><strong>6.4、字体图标资源</strong></h3><h4 id="1）下载字体图标文件"><a href="#1）下载字体图标文件" class="headerlink" title="1）下载字体图标文件"></a><strong><span style='color:red'>1）下载字体图标文件</span></strong></h4><ol><li>打开<a href="https://www.iconfont.cn/">阿里巴巴矢量图标库</a></li><li>选择想要的图标添加到购物车，统一下载到本地</li><li>将字体图标文件夹修改名字<code>fonts</code>，放置<code>src\assets\fonts</code>中</li></ol><h4 id="2）添加字体图标资源"><a href="#2）添加字体图标资源" class="headerlink" title="2）添加字体图标资源"></a><strong><span style='color:red'>2）添加字体图标资源</span></strong></h4><ul><li><p><strong>src\main.js</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">&quot;./fonts/iconfont.css&quot;</span>;</span><br></pre></td></tr></table></figure></li><li><p>public\index.html</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></pre></td><td class="code"><pre><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;en&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 class="tag">&lt;<span class="name">title</span>&gt;</span>Document<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">script</span> <span class="attr">src</span>=<span class="string">&quot;../dist/main.js&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">script</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><br><span class="line">  <span class="tag">&lt;<span class="name">body</span>&gt;</span></span><br><span class="line">    <span class="comment">&lt;!-- 使用字体图标 --&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;iconfont icon-arrow-down&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;iconfont icon-ashbin&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">i</span> <span class="attr">class</span>=<span class="string">&quot;iconfont icon-browse&quot;</span>&gt;</span><span class="tag">&lt;/<span class="name">i</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></li></ul><h4 id="3）配置-4"><a href="#3）配置-4" class="headerlink" title="3）配置"></a><strong><span style='color:red'>3）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ttf|woff2?)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;assets/media/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="4）运行指令"><a href="#4）运行指令" class="headerlink" title="4）运行指令"></a><strong><span style='color:red'>4）运行指令</span></strong></h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack  // 打开 index.html 页面查看效果</span><br></pre></td></tr></table></figure><h3 id="6-5、音视频资源等"><a href="#6-5、音视频资源等" class="headerlink" title="6.5、音视频资源等"></a><strong>6.5、音视频资源等</strong></h3><p>开发中可能还存在一些其他资源，如音视频等，我们也一起处理了</p><h4 id="1）配置"><a href="#1）配置" class="headerlink" title="1）配置"></a><strong><span style='color:red'>1）配置</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ttf|woff2?|map4|map3|avi)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;assets/media/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>就是在处理字体图标资源基础上增加其他文件类型，统一处理即可</p><hr><h2 id="七、处理JavaScript资源"><a href="#七、处理JavaScript资源" class="headerlink" title="七、处理JavaScript资源"></a><strong>七、处理JavaScript资源</strong></h2><p><strong>有人可能会问，js 资源 Webpack 不能已经处理了吗，为什么我们还要处理呢？</strong></p><p>原因是 Webpack 对 js 处理是有限的，只能编译 js 中 ES 模块化语法，不能编译其他语法，导致 js 不能在 IE 等浏览器运行，所以我们希望做一些兼容性处理。</p><p>其次开发中，团队对代码格式是有严格要求的，我们不能由肉眼去检测代码格式，需要使用专业的工具来检测。</p><ul><li>针对 js 兼容性处理，我们使用 Babel 来完成</li><li>针对代码格式，我们使用 Eslint 来完成</li></ul><p>我们先完成 Eslint，检测代码格式无误后，在由 Babel 做代码兼容性处理</p><h3 id="7-1、Eslint"><a href="#7-1、Eslint" class="headerlink" title="7.1、Eslint"></a><strong>7.1、Eslint</strong></h3><p>可组装的 JavaScript 和 JSX 检查工具。意思就是它是用来检测 js 和 jsx 语法的工具，可以配置各项功能。</p><p>我们使用 Eslint，关键是写 Eslint 配置文件，里面写上各种 rules 规则，将来运行 Eslint 时就会以写的规则对代码进行检查。</p><blockquote><p><strong>注意：</strong> ESLint 从 <strong>v9.0.0</strong> 开始，<strong>Flat Config（扁平化配置）</strong> 成为默认且唯一推荐的配置方式，旧版的 <code>.eslintrc.*</code> 系列配置文件已被废弃。以下内容均以 v9.0.0+ 的新版写法为准。</p></blockquote><h4 id="1）配置文件"><a href="#1）配置文件" class="headerlink" title="1）配置文件"></a><strong><span style='color:red'>1）配置文件</span></strong></h4><ul><li><p>ESLint v9.0.0+ 使用 <strong>Flat Config</strong> 格式，配置文件为以下之一，项目根目录下存在一个即可：</p><ul><li><code>eslint.config.js</code>（推荐）</li><li><code>eslint.config.mjs</code></li><li><code>eslint.config.cjs</code></li></ul><blockquote><p><strong>旧版（已废弃）：</strong> v9 之前使用的 <code>.eslintrc</code>、<code>.eslintrc.js</code>、<code>.eslintrc.json</code> 以及 <code>package.json</code> 中的 <code>eslintConfig</code> 字段，在 v9.0.0 中已被移除，不应再使用。</p></blockquote></li></ul><h4 id="2）具体配置"><a href="#2）具体配置" class="headerlink" title="2）具体配置"></a><strong><span style='color:red'>2）具体配置</span></strong></h4><p>Flat Config 的配置文件导出一个<strong>数组</strong>，数组中的每个对象都是一组独立的配置，按顺序叠加生效。</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// eslint.config.js 基本结构</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// 每个对象代表一组配置</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">files</span>: [<span class="string">&quot;**/*.js&quot;</span>], <span class="comment">// files: 指定该配置生效的文件范围（不写则对所有文件生效）</span></span><br><span class="line">    <span class="attr">languageOptions</span>: &#123;&#125;, <span class="comment">// languageOptions: 取代旧版的 parserOptions + env</span></span><br><span class="line">    <span class="attr">rules</span>: &#123;&#125;, <span class="comment">// rules: 具体检查规则（写法与旧版相同）</span></span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h5 id="a、languageOptions（语言选项）"><a href="#a、languageOptions（语言选项）" class="headerlink" title="a、languageOptions（语言选项）"></a><strong><span style='color:cornflowerblue'>a、languageOptions（语言选项）</span></strong></h5><p>取代了旧版的 <code>parserOptions</code> 和 <code>env</code>，告诉 ESLint 代码的语法特性和可用的全局变量。</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// eslint.config.js 基本结构</span></span><br><span class="line"><span class="keyword">import</span> globals <span class="keyword">from</span> <span class="string">&quot;globals&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="attr">languageOptions</span>: &#123;</span><br><span class="line">    <span class="attr">ecmaVersion</span>: <span class="string">&quot;latest&quot;</span>, <span class="comment">// ES 语法版本，推荐使用 &quot;latest&quot;</span></span><br><span class="line">    <span class="attr">sourceType</span>: <span class="string">&quot;module&quot;</span>, <span class="comment">// ES 模块化</span></span><br><span class="line">    <span class="attr">globals</span>: &#123;</span><br><span class="line">        ...globals.<span class="property">node</span>, <span class="comment">// 启用 Node.js 全局变量（取代旧版 env.node）</span></span><br><span class="line">        ...globals.<span class="property">browser</span>, <span class="comment">// 启用浏览器全局变量（取代旧版 env.browser）</span></span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">parserOptions</span>: &#123;</span><br><span class="line">        <span class="attr">ecmaFeatures</span>: &#123;</span><br><span class="line">            <span class="attr">jsx</span>: <span class="literal">true</span>, <span class="comment">// 如果是 React 项目，则需要开启 JSX 语法</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><h5 id="b、rules-具体规则"><a href="#b、rules-具体规则" class="headerlink" title="b、rules 具体规则"></a><strong><span style='color:cornflowerblue'>b、rules 具体规则</span></strong></h5><ol start="2"><li>规则的值与旧版完全一致：</li></ol><ul><li><code>&quot;off&quot;</code> 或 <code>0</code> - 关闭规则</li><li><code>&quot;warn&quot;</code> 或 <code>1</code> - 开启规则，使用警告级别的错误：<code>warn</code>（不会导致程序退出）</li><li><code>&quot;error&quot;</code> 或 <code>2</code> - 开启规则，使用错误级别的错误：<code>error</code>（当被触发的时候，程序会退出）</li></ul><figure class="highlight js"><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="attr">rules</span>: &#123;</span><br><span class="line">    <span class="attr">semi</span>: <span class="string">&quot;error&quot;</span>,                     <span class="comment">// 禁止使用分号</span></span><br><span class="line">    <span class="string">&#x27;array-callback-return&#x27;</span>: <span class="string">&#x27;warn&#x27;</span>,   <span class="comment">// 强制数组方法的回调函数中有 return 语句，否则警告</span></span><br><span class="line">    <span class="string">&#x27;default-case&#x27;</span>: [</span><br><span class="line">        <span class="string">&#x27;warn&#x27;</span>,                          <span class="comment">// 要求 switch 语句中有 default 分支，否则警告</span></span><br><span class="line">        &#123; <span class="attr">commentPattern</span>: <span class="string">&#x27;^no default$&#x27;</span> &#125; <span class="comment">// 允许在最后注释 no default，就不会有警告了</span></span><br><span class="line">      ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>更多规则详见：<a href="https://eslint.org/docs/latest/rules/">规则文档</a></p><h5 id="c、继承官方推荐规则"><a href="#c、继承官方推荐规则" class="headerlink" title="c、继承官方推荐规则"></a><strong><span style='color:cornflowerblue'>c、继承官方推荐规则</span></strong></h5><p>开发中一条一条写 <code>rules</code> 规则太费劲，可以直接继承现有的规则集。在新版中通过<strong>直接引入模块</strong>来实现，不再使用字符串形式的 <code>extends</code>。</p><p><strong>常见规则集：</strong></p><ul><li><strong>ESLint 官方推荐</strong>：来自 <code>@eslint/js</code> 包的 <code>js.configs.recommended</code></li><li><strong>Vue 官方规则</strong>：来自 <code>eslint-plugin-vue</code> 包的 <code>pluginVue.configs[&quot;flat/essential&quot;]</code></li><li><strong>React 官方规则</strong>：来自 <code>eslint-plugin-react</code> 包</li></ul><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 以 Vue 项目为例</span></span><br><span class="line"><span class="keyword">import</span> pluginVue <span class="keyword">from</span> <span class="string">&quot;eslint-plugin-vue&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// Vue 推荐配置 (应用到 .vue 文件及相关解析设置)</span></span><br><span class="line">  ...pluginVue.<span class="property">configs</span>[<span class="string">&quot;flat/essential&quot;</span>],</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自定义配置：针对 .vue 文件的覆盖</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">files</span>: [<span class="string">&quot;**/*.vue&quot;</span>],</span><br><span class="line">    <span class="attr">rules</span>: &#123;</span><br><span class="line">      <span class="string">&quot;vue/multi-word-component-names&quot;</span>: <span class="string">&quot;off&quot;</span>,</span><br><span class="line">      <span class="string">&quot;vue/no-unused-vars&quot;</span>: <span class="string">&quot;warn&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h4 id="3）完整示例（Vue-项目）"><a href="#3）完整示例（Vue-项目）" class="headerlink" title="3）完整示例（Vue 项目）"></a><strong><span style='color:red'>3）完整示例（Vue 项目）</span></strong></h4><figure class="highlight js"><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><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// eslint.config.js</span></span><br><span class="line"><span class="keyword">import</span> js <span class="keyword">from</span> <span class="string">&quot;@eslint/js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> globals <span class="keyword">from</span> <span class="string">&quot;globals&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> pluginVue <span class="keyword">from</span> <span class="string">&quot;eslint-plugin-vue&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  <span class="comment">// 1. 继承 ESLint 官方推荐规则（应用于 .js、.mjs、.cjs 文件）</span></span><br><span class="line">  js.<span class="property">configs</span>.<span class="property">recommended</span>,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 继承 Vue 官方推荐配置（自动处理 .vue 文件的解析）</span></span><br><span class="line">  ...pluginVue.<span class="property">configs</span>[<span class="string">&quot;flat/essential&quot;</span>],</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 3. 针对 .vue 文件的自定义覆盖规则</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">files</span>: [<span class="string">&quot;**/*.vue&quot;</span>],</span><br><span class="line">    <span class="attr">rules</span>: &#123;</span><br><span class="line">      <span class="string">&quot;vue/multi-word-component-names&quot;</span>: <span class="string">&quot;off&quot;</span>,</span><br><span class="line">      <span class="string">&quot;vue/no-unused-vars&quot;</span>: <span class="string">&quot;warn&quot;</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 4. 针对 .js 文件的语言选项与自定义规则</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">files</span>: [<span class="string">&quot;**/*.js&quot;</span>], <span class="comment">// 如有 JSX 则改为 [&quot;**/*.js&quot;, &quot;**/*.jsx&quot;]</span></span><br><span class="line">    <span class="attr">languageOptions</span>: &#123;</span><br><span class="line">      <span class="attr">ecmaVersion</span>: <span class="string">&quot;latest&quot;</span>,</span><br><span class="line">      <span class="attr">sourceType</span>: <span class="string">&quot;module&quot;</span>,</span><br><span class="line">      <span class="attr">globals</span>: &#123;</span><br><span class="line">        ...globals.<span class="property">node</span>, <span class="comment">// 启用 Node.js 全局变量</span></span><br><span class="line">        ...globals.<span class="property">browser</span>, <span class="comment">// 启用浏览器全局变量</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">rules</span>: &#123;</span><br><span class="line">      <span class="comment">// 在 js.configs.recommended 基础上覆盖或添加</span></span><br><span class="line">      <span class="string">&quot;no-console&quot;</span>: <span class="number">2</span>, <span class="comment">// 禁止使用console</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h4 id="4）在-Webpack-中使用"><a href="#4）在-Webpack-中使用" class="headerlink" title="4）在 Webpack 中使用"></a><strong><span style='color:red'>4）在 Webpack 中使用</span></strong></h4><h5 id="a、安装包"><a href="#a、安装包" class="headerlink" title="a、安装包"></a><strong><span style='color:cornflowerblue'>a、安装包</span></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i eslint-webpack-plugin eslint globals @eslint/js -D</span><br></pre></td></tr></table></figure><p><code>eslint-webpack-plugin</code> 的作用是<strong>将 ESLint 集成进 Webpack 的构建流程</strong>，让代码在每次打包（或 webpack-dev-server 热更新）时自动执行 lint 检查。</p><table><thead><tr><th align="left">功能</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><strong>构建时自动检查</strong></td><td align="left">每次执行 <code>webpack</code> &#x2F; <code>npm run build</code> 时，自动对源码跑一遍 ESLint</td></tr><tr><td align="left"><strong>开发时实时反馈</strong></td><td align="left">配合 <code>webpack-dev-server</code>，保存文件时立即在终端或浏览器控制台显示 lint 错误</td></tr><tr><td align="left"><strong>阻断编译（可选）</strong></td><td align="left">开启 <code>failOnError: true</code> 后，lint 报错会直接让构建失败，防止问题代码上线</td></tr></tbody></table><h5 id="b、定义-Eslint-配置文件"><a href="#b、定义-Eslint-配置文件" class="headerlink" title="b、定义 Eslint 配置文件"></a><strong><span style='color:cornflowerblue'>b、定义 Eslint 配置文件</span></strong></h5><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// eslint.config.js</span></span><br><span class="line"><span class="keyword">import</span> js <span class="keyword">from</span> <span class="string">&quot;@eslint/js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> globals <span class="keyword">from</span> <span class="string">&quot;globals&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  js.<span class="property">configs</span>.<span class="property">recommended</span>,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">files</span>: [<span class="string">&quot;**/*.js&quot;</span>], <span class="comment">// 如有 JSX 则改为 [&quot;**/*.js&quot;, &quot;**/*.jsx&quot;]</span></span><br><span class="line">    <span class="attr">languageOptions</span>: &#123;</span><br><span class="line">      <span class="attr">ecmaVersion</span>: <span class="string">&quot;latest&quot;</span>,</span><br><span class="line">      <span class="attr">sourceType</span>: <span class="string">&quot;module&quot;</span>,</span><br><span class="line">      <span class="attr">globals</span>: &#123;</span><br><span class="line">        ...globals.<span class="property">node</span>, <span class="comment">// 启用 Node.js 全局变量</span></span><br><span class="line">        ...globals.<span class="property">browser</span>, <span class="comment">// 启用浏览器全局变量</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">rules</span>: &#123;</span><br><span class="line">      <span class="comment">// 在 js.configs.recommended 基础上覆盖或添加</span></span><br><span class="line">      <span class="string">&quot;no-console&quot;</span>: <span class="number">2</span>, <span class="comment">// 禁止使用console</span></span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h5 id="c、webpack配置"><a href="#c、webpack配置" class="headerlink" title="c、webpack配置"></a><strong><span style='color:cornflowerblue'>c、webpack配置</span></strong></h5><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ESLintWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;eslint-webpack-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ESLintWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 插件会自动识别 eslint.config.js</span></span><br><span class="line">      <span class="comment">// 指定检查文件的根目录</span></span><br><span class="line">      <span class="attr">context</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;src&quot;</span>),</span><br><span class="line">      <span class="attr">failOnError</span>: <span class="literal">true</span>, <span class="comment">// 默认值：true。eslint 报错时终止构建（生产环境推荐开启</span></span><br><span class="line">      <span class="attr">failOnWarning</span>: <span class="literal">false</span>, <span class="comment">// 默认值：false。eslint 警告时终止构建（按需开启）</span></span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h5 id="d、测试验证"><a href="#d、测试验证" class="headerlink" title="d、测试验证"></a><strong><span style='color:cornflowerblue'>d、测试验证</span></strong></h5><figure class="highlight js"><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">// main.js</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">11111</span>);</span><br></pre></td></tr></table></figure><p>不设置规则的情况下js写了<code>console.log(11111)</code>构建是不会报错的</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">rules</span>: &#123;</span><br><span class="line">    <span class="string">&quot;no-console&quot;</span>: <span class="number">2</span>,  <span class="comment">// 禁止使用console</span></span><br><span class="line">&#125;,</span><br></pre></td></tr></table></figure><p>设置规则之后，<code>npx webpck</code>构建就会失败</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260305173616845.png" alt="image-20260305173616845"></p><h4 id="5）VSCode-Eslint-插件"><a href="#5）VSCode-Eslint-插件" class="headerlink" title="5）VSCode Eslint 插件"></a><strong><span style='color:red'>5）VSCode Eslint 插件</span></strong></h4><p>打开 VSCode，下载 Eslint 插件，即可不用编译就能看到错误，可以提前解决。</p><p>但是此时就会对项目所有文件默认进行 Eslint 检查了，我们 dist 目录下的打包后文件就会报错。但是我们只需要检查 src 下面的文件，不需要检查 dist 下面的文件。</p><p>所以需要配置 Eslint 忽略文件：</p><p><strong>新版配置 (Flat Config)</strong>：直接在 <code>eslint.config.js</code> 中添加 <code>ignores</code> 属性</p><figure class="highlight js"><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="keyword">export</span> <span class="keyword">default</span> [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">ignores</span>: [<span class="string">&quot;dist/**&quot;</span>], <span class="comment">// 忽略 dist 目录下所有文件</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// ... 其他配置</span></span><br><span class="line">];</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260305174025794.png" alt="image-20260305174025794"></p><h3 id="7-2、Babel"><a href="#7-2、Babel" class="headerlink" title="7.2、Babel"></a><strong>7.2、Babel</strong></h3><p><strong>Babel（准确说是 preset-env 自身）主要负责语法</strong>：比如 <code>const</code> 改成 <code>var</code>、去掉箭头函数等。</p><p><strong>对于新的 API 或内置对象</strong>：比如 <code>Promise</code>、<code>Array.from</code>、<code>Object.assign</code>、<code>[].includes</code>，老浏览器里根本没有这些全局对象和原型方法。Babel 转换后还是 <code>Promise</code> 字母，必定在旧浏览器报错。我们需要 <strong>Polyfill</strong> 给环境打补丁。</p><p><a href="https://zhbcloud.github.io/posts/b41c92e1.html">Babel 7 从入门到进阶的全场景指南</a></p><p><a href="https://zhbcloud.github.io/posts/44f179c0b9.html">Babel Runtime 依赖图谱与模块化 Polyfill 方案</a></p><h4 id="1）配置文件-1"><a href="#1）配置文件-1" class="headerlink" title="1）配置文件"></a><strong><span style='color:red'>1）配置文件</span></strong></h4><p>配置文件由很多种写法：</p><ul><li>babel.config.*<ul><li><code>babel.config.js</code>（有动态逻辑就使用这个）</li><li><code>babel.config.json</code>（官方推荐使用）</li></ul></li><li>.babelrc.*<ul><li><code>.babelrc</code></li><li><code>.babelrc.js</code></li><li><code>.babelrc.json</code></li></ul></li><li><code>package.json</code> 中 <code>babel</code>：不需要创建文件，在原有文件基础上写Babel 会查找和自动读取它们，所以以上配置文件只需要存在一个即可</li></ul><h4 id="2）具体配置-1"><a href="#2）具体配置-1" class="headerlink" title="2）具体配置"></a><strong><span style='color:red'>2）具体配置</span></strong></h4><p>⚠️⚠️⚠️JSON 格式<strong>不支持注释</strong>，这会导致 Babel 解析配置文件时报错或<strong>直接忽略整个配置</strong></p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span><span class="punctuation">,</span> <span class="comment">// 预设：告诉 Babel 如何处理通用的语法</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span> <span class="comment">// babel插件 处理特殊的、非通用的转换需求</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>presets 预设</strong></p><p>简单理解：就是预先设定好的一组 Babel 插件的集合，扩展 Babel 功能</p><ul><li><code>@babel/preset-env</code>: 一个智能预设，允许您使用最新的 JavaScript。</li><li><code>@babel/preset-react</code>：一个用来编译 React jsx 语法的预设</li><li><code>@babel/preset-typescript</code>：一个用来编译 TypeScript 语法的预设</li></ul><h4 id="3）在-Webpack-中使用"><a href="#3）在-Webpack-中使用" class="headerlink" title="3）在 Webpack 中使用"></a><strong><span style='color:red'>3）在 Webpack 中使用</span></strong></h4><h5 id="a、安装包-1"><a href="#a、安装包-1" class="headerlink" title="a、安装包"></a><strong><span style='color:cornflowerblue'>a、安装包</span></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i babel-loader @babel/core @babel/preset-env -D</span><br></pre></td></tr></table></figure><p>这三个依赖是前端开发中处理 JavaScript 新语法的“黄金组合”，它们配合工作，让我们可以放心地使用最新的 JS 语法，而不用担心浏览器兼容性问题。它们的具体含义和分工如下：</p><p><strong><code>babel-loader（搬运工/桥梁）</code></strong></p><ul><li><strong>含义</strong>：它是 Webpack 和 Babel 之间的一座桥梁。</li><li><strong>作用</strong>：Webpack 默认只能看懂基础的 JS，遇到高级的新语法它可能就不认识了。<code>babel-loader</code> 告诉 Webpack：“遇到 JS 文件时，先交给我，我把它送到 Babel 那里翻译一下，翻译好了再还给你去打包。”</li><li><strong>大白话</strong>：它就是一个快递员，负责把 Webpack 里的 JS 代码打包盒，送到 Babel 工厂去加工。</li></ul><p><strong><code>@babel/core（核心大脑）</code></strong></p><ul><li><strong>含义</strong>：Babel 的核心库，也就是刚才说的“Babel 工厂”的总部。</li><li><strong>作用</strong>：它包含了 Babel 编译代码的核心 API。它知道如何解析（Parse）JS 代码变成抽象语法树（AST），如何遍历（Traverse）这棵树，以及如何根据规则把树重新生成（Generate）为新的 JS 代码。</li><li><strong>注意</strong>：<code>@babel/core</code> 本身不包含具体的翻译规则。它只是一个引擎，提供能力，但不知道具体要把什么语法转换成什么样（比如它不知道要把箭头函数转成普通函数，这需要别人告诉它）。</li></ul><p><strong><code>@babel/preset-env（规则字典/翻译官）</code></strong></p><ul><li><strong>含义</strong>：这是一个预设（preset），也就是一组插件（翻译规则）的集合包。</li><li><strong>作用</strong>：前面说了核心大脑不知道具体的翻译规则，<code>@babel/preset-env</code> 就是这本厚厚的“翻译字典”。它包含了将最新 JavaScript 语法（如 ES6 的箭头函数、const&#x2F;let、类等）转换成旧版本浏览器（如 IE、老版 Chrome）能听懂的 ES5 语法的所有必要规则（也就是各种 Babel plugins）。</li><li><strong>强大之处</strong>：它不仅包含规则，还很“智能”。你可以配置你想要支持的浏览器环境（比如 “目标是 Chrome 80 以上”），它会自动判断哪些语法需要转换，哪些不需要，从而避免过度编译，减小打包后的文件体积。</li></ul><h5 id="b、定义-Babel-配置文件"><a href="#b、定义-Babel-配置文件" class="headerlink" title="b、定义 Babel 配置文件"></a><strong><span style='color:cornflowerblue'>b、定义 Babel 配置文件</span></strong></h5><figure class="highlight json"><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">// babel.config.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">[</span><span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">]</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h5 id="c、改-js-文件代码"><a href="#c、改-js-文件代码" class="headerlink" title="c、改 js 文件代码"></a><strong><span style='color:cornflowerblue'>c、改 js 文件代码</span></strong></h5><figure class="highlight js"><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">// main.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">sum</span> = (<span class="params">...args</span>) =&gt; args.<span class="title function_">reduce</span>(<span class="function">(<span class="params">p, c</span>) =&gt;</span> p + c, <span class="number">0</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">sum</span>(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>));</span><br></pre></td></tr></table></figure><h5 id="d、配置webpack"><a href="#d、配置webpack" class="headerlink" title="d、配置webpack"></a><strong><span style='color:cornflowerblue'>d、配置webpack</span></strong></h5><figure class="highlight js"><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="comment">// webpack.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="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>, <span class="comment">// 排除node_modules代码不编译</span></span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h5 id="e、运行指令"><a href="#e、运行指令" class="headerlink" title="e、运行指令"></a><strong><span style='color:cornflowerblue'>e、运行指令</span></strong></h5><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack  // 打开 dist/static/js/main.js 文件查看，会发现箭头函数等 ES6 语法已经转换了</span><br></pre></td></tr></table></figure><hr><h2 id="八、Loader的多种写法"><a href="#八、Loader的多种写法" class="headerlink" title="八、Loader的多种写法"></a><strong>八、Loader的多种写法</strong></h2><p>在 Webpack 的 <code>module.rules</code> 里，指定用哪个 loader 处理文件有两种方式：<code>loader</code> 或 <code>use</code>。二者都支持多种写法，按「单个&#x2F;多个、有无选项」选即可。以下写法均以 <a href="https://webpack.docschina.org/loaders/babel-loader/">Webpack 中文文档</a> 为准。</p><h3 id="8-1、写法总览"><a href="#8-1、写法总览" class="headerlink" title="8.1、写法总览"></a><strong>8.1、写法总览</strong></h3><table><thead><tr><th>写法</th><th>适用场景</th></tr></thead><tbody><tr><td><code>loader: &quot;babel-loader&quot;</code></td><td>只有一个 loader 且无选项时的简写</td></tr><tr><td><code>use: &quot;babel-loader&quot;</code></td><td>单个 loader，无选项，字符串</td></tr><tr><td><code>use: [&quot;style-loader&quot;, &quot;css-loader&quot;]</code></td><td>多个 loader，都无选项，字符串数组</td></tr><tr><td><code>use: &#123; loader: &quot;babel-loader&quot;, options: &#123;&#125; &#125;</code></td><td>单个 loader 带选项，<strong>一个对象</strong>（官网常用）</td></tr><tr><td><code>use: [&#123; loader: &quot;babel-loader&quot;, options: &#123;&#125; &#125;]</code></td><td>单个 loader 带选项，数组里一个对象</td></tr><tr><td><code>use: [&#123; loader, options? &#125;, &#123; loader, options? &#125;, ...]</code></td><td>多个 loader，且有的要传选项，<strong>必须用数组 + 对象</strong></td></tr></tbody></table><p>要点：<strong>只有一个 loader 且要传 options 时</strong>，可以写 <code>use: &#123; loader, options &#125;</code>，不必包在数组里。</p><h3 id="8-2、按场景选择"><a href="#8-2、按场景选择" class="headerlink" title="8.2、按场景选择"></a><strong>8.2、按场景选择</strong></h3><h4 id="1）一个-loader、无选项"><a href="#1）一个-loader、无选项" class="headerlink" title="1）一个 loader、无选项"></a><strong><span style='color:red'>1）一个 loader、无选项</span></strong></h4><p>用 <code>loader</code> 或 <code>use</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></pre></td><td class="code"><pre><span class="line">&#123; <span class="attr">test</span>: <span class="regexp">/\.js$/</span>, <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span> &#125;</span><br><span class="line"><span class="comment">// 或</span></span><br><span class="line">&#123; <span class="attr">test</span>: <span class="regexp">/\.js$/</span>, <span class="attr">use</span>: <span class="string">&quot;babel-loader&quot;</span> &#125;</span><br></pre></td></tr></table></figure><h4 id="2）一个-loader、有选项"><a href="#2）一个-loader、有选项" class="headerlink" title="2）一个 loader、有选项"></a><strong><span style='color:red'>2）一个 loader、有选项</span></strong></h4><p>推荐用 <strong>对象</strong> 形式的 <code>use</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><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 推荐：直接一个对象</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">  <span class="attr">use</span>: &#123;</span><br><span class="line">    <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">    <span class="attr">options</span>: &#123;</span><br><span class="line">      <span class="attr">presets</span>: [<span class="string">&quot;@babel/preset-env&quot;</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><br><span class="line"><span class="comment">// 也可以：数组里一个对象</span></span><br><span class="line">&#123; <span class="attr">test</span>: <span class="regexp">/\.js$/</span>, <span class="attr">use</span>: [&#123; <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>, <span class="attr">options</span>: &#123; <span class="attr">presets</span>: [<span class="string">&quot;@babel/preset-env&quot;</span>] &#125; &#125;] &#125;</span><br></pre></td></tr></table></figure><h4 id="3）-多个-loader"><a href="#3）-多个-loader" class="headerlink" title="3） 多个 loader"></a><strong><span style='color:red'>3） 多个 loader</span></strong></h4><p>必须用 <strong>数组</strong>。都无选项时用字符串数组；有选项的写成对象放进数组：</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">// 都无选项</span></span><br><span class="line"><span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 有的有选项：全部写成对象</span></span><br><span class="line"><span class="attr">use</span>: [</span><br><span class="line">  &#123; <span class="attr">loader</span>: <span class="string">&quot;style-loader&quot;</span>, <span class="attr">options</span>: &#123; <span class="attr">injectType</span>: <span class="string">&quot;styleTag&quot;</span> &#125; &#125;,</span><br><span class="line">  &#123; <span class="attr">loader</span>: <span class="string">&quot;css-loader&quot;</span>, <span class="attr">options</span>: &#123; <span class="attr">modules</span>: <span class="literal">true</span> &#125; &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><h3 id="8-3、示例：多个-loader-且都带选项"><a href="#8-3、示例：多个-loader-且都带选项" class="headerlink" title="8.3、示例：多个 loader 且都带选项"></a><strong>8.3、示例：多个 loader 且都带选项</strong></h3><p>例如给 JS 先经 <code>thread-loader</code> 再经 <code>babel-loader</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><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">    <span class="attr">test</span>: <span class="regexp">/\.m?js$/</span>,</span><br><span class="line">    <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>,</span><br><span class="line">    <span class="attr">use</span>: [</span><br><span class="line">        &#123; <span class="attr">loader</span>: <span class="string">&quot;thread-loader&quot;</span>, <span class="attr">options</span>: &#123; <span class="attr">workers</span>: <span class="number">2</span> &#125; &#125;,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">                <span class="attr">presets</span>: [<span class="string">&quot;@babel/preset-env&quot;</span>],</span><br><span class="line">                <span class="attr">cacheDirectory</span>: <span class="literal">true</span>,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">    ],</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>以上写法适用于所有 loader，不限于 babel-loader。</p><hr><h2 id="九、处理-Html-资源"><a href="#九、处理-Html-资源" class="headerlink" title="九、处理 Html 资源"></a><strong>九、处理 Html 资源</strong></h2><h3 id="9-1、安装包"><a href="#9-1、安装包" class="headerlink" title="9.1、安装包"></a><strong>9.1、安装包</strong></h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i html-webpack-plugin -D</span><br></pre></td></tr></table></figure><p><strong>帮你自动生成</strong> HTML 文件，并把打包好的 JS 和 CSS 自动插入到这个 HTML 中。</p><h3 id="9-2、配置webpack"><a href="#9-2、配置webpack" class="headerlink" title="9.2、配置webpack"></a><strong>9.2、配置webpack</strong></h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;html-webpack-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(), <span class="comment">// 一般这么配置即可</span></span><br><span class="line">    <span class="comment">// new HtmlWebpackPlugin(&#123;</span></span><br><span class="line">    <span class="comment">// 以 public/index.html 为模板创建文件</span></span><br><span class="line">    <span class="comment">// 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源</span></span><br><span class="line">    <span class="comment">// template: path.resolve(__dirname, &quot;public/index.html&quot;),</span></span><br><span class="line">    <span class="comment">// &#125;),</span></span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行<code>npx webpack</code>此时 dist 目录就会输出一个 index.html 文件</p><hr><h2 id="十、开发服务器-自动化"><a href="#十、开发服务器-自动化" class="headerlink" title="十、开发服务器&amp;自动化"></a><strong>十、开发服务器&amp;自动化</strong></h2><p>每次写完代码都需要手动输入指令才能编译代码，太麻烦了，我们希望一切自动化</p><h3 id="10-1、安装包"><a href="#10-1、安装包" class="headerlink" title="10.1、安装包"></a><strong>10.1、安装包</strong></h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i webpack-dev-server -D</span><br></pre></td></tr></table></figure><h3 id="10-2、配置webpack"><a href="#10-2、配置webpack" class="headerlink" title="10.2、配置webpack"></a><strong>10.2、配置webpack</strong></h3><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;html-webpack-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="comment">// 开发服务器:不会输出资源，在内存中编译打包的</span></span><br><span class="line">  <span class="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="attr">host</span>: <span class="string">&quot;localhost&quot;</span>, <span class="comment">// 启动服务器域名</span></span><br><span class="line">    <span class="attr">port</span>: <span class="string">&quot;3000&quot;</span>, <span class="comment">// 启动服务器端口号</span></span><br><span class="line">    <span class="attr">open</span>: <span class="literal">true</span>, <span class="comment">// 是否自动打开浏览器</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行<code>npx webpack serve</code>此时当你使用开发服务器时，所有代码都会在内存中编译打包，并不会输出到 dist 目录下。</p><p>开发时我们只关心代码能运行，有效果即可，至于代码被编译成什么样子，我们并不需要知道。</p><hr><h2 id="十一、生产模式"><a href="#十一、生产模式" class="headerlink" title="十一、生产模式"></a><strong>十一、生产模式</strong></h2><p>生产模式是开发完成代码后，我们需要得到代码将来部署上线。</p><p>这个模式下我们主要对代码进行优化，让其运行性能更好。</p><p>优化主要从两个角度出发:</p><ol><li>优化代码运行性能</li><li>优化代码打包速度</li></ol><p>我们在<code>config </code>中分别准备两个配置文件来放不同的配置</p><h3 id="11-1、文件目录"><a href="#11-1、文件目录" class="headerlink" title="11.1、文件目录"></a><strong>11.1、文件目录</strong></h3><figure class="highlight plaintext"><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">├── webpack-code (项目根目录)</span><br><span class="line">    ├── config (Webpack配置文件目录)</span><br><span class="line">    │    ├── webpack.dev.js(开发模式配置文件)</span><br><span class="line">    │    └── webpack.prod.js(生产模式配置文件)</span><br><span class="line">    ├── node_modules (下载包存放目录)</span><br><span class="line">    ├── src (项目源码目录，除了html其他都在src里面)</span><br><span class="line">    │    └── 略</span><br><span class="line">    ├── public (项目html文件)</span><br><span class="line">    │    └── index.html</span><br><span class="line">    ├── eslint.config.js(Eslint配置文件)</span><br><span class="line">    ├── babel.config.json(Babel配置文件)</span><br><span class="line">    └── package.json (包的依赖管理配置文件)</span><br></pre></td></tr></table></figure><h3 id="11-2、修改-webpack-dev-js"><a href="#11-2、修改-webpack-dev-js" class="headerlink" title="11.2、修改 webpack.dev.js"></a><strong>11.2、修改 webpack.dev.js</strong></h3><p>因为文件目录变了，所以所有绝对路径需要回退一层目录才能找到对应的文件</p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ESLintWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;eslint-webpack-plugin&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;html-webpack-plugin&quot;</span>);</span><br><span class="line"></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">entry</span>: <span class="string">&quot;./src/main.js&quot;</span>,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="literal">undefined</span>, <span class="comment">// 开发模式没有输出，不需要指定输出目录</span></span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;js/main.js&quot;</span>, <span class="comment">// 将 js 文件输出到 static/js 目录中</span></span><br><span class="line">    <span class="comment">// ✨ 全局兜底：所有没单独配置 generator 的资源，都放到 media 文件夹</span></span><br><span class="line">    <span class="attr">assetModuleFilename</span>: <span class="string">&quot;media/[hash:10][ext][query]&quot;</span>, <span class="comment">// 图片的输出路径</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 用来匹配 .css 结尾的文件</span></span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="comment">// use 数组里面 Loader 执行顺序是从右到左</span></span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;less-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.s[ac]ss$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;sass-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.styl$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;stylus-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(png|jpe?g|gif|webp)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset&quot;</span>,</span><br><span class="line">        <span class="attr">parser</span>: &#123;</span><br><span class="line">          <span class="attr">dataUrlCondition</span>: &#123;</span><br><span class="line">            <span class="attr">maxSize</span>: <span class="number">10</span> * <span class="number">1024</span>, <span class="comment">// 小于10kb的图片会被base64处理</span></span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="comment">// 将图片文件输出到 /imgs 目录中</span></span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;imgs/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ttf|woff2?)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;static/media/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>, <span class="comment">// 排除node_modules代码不编译</span></span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ESLintWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 指定检查文件的根目录</span></span><br><span class="line">      <span class="attr">context</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../src&quot;</span>),</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 以 public/index.html 为模板创建文件</span></span><br><span class="line">      <span class="comment">// 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源</span></span><br><span class="line">      <span class="attr">template</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../public/index.html&quot;</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="attr">devServer</span>: &#123;</span><br><span class="line">    <span class="attr">host</span>: <span class="string">&quot;localhost&quot;</span>, <span class="comment">// 启动服务器域名</span></span><br><span class="line">    <span class="attr">port</span>: <span class="string">&quot;3000&quot;</span>, <span class="comment">// 启动服务器端口号</span></span><br><span class="line">    <span class="attr">open</span>: <span class="literal">true</span>, <span class="comment">// 是否自动打开浏览器</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;development&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行开发模式的指令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack serve --config ./config/webpack.dev.js</span><br></pre></td></tr></table></figure><h3 id="11-3、修改-webpack-prod-js"><a href="#11-3、修改-webpack-prod-js" class="headerlink" title="11.3、修改 webpack.prod.js"></a><strong>11.3、修改 webpack.prod.js</strong></h3><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ESLintWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;eslint-webpack-plugin&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">HtmlWebpackPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;html-webpack-plugin&quot;</span>);</span><br><span class="line"></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">entry</span>: <span class="string">&quot;./src/main.js&quot;</span>,</span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../dist&quot;</span>), <span class="comment">// 生产模式需要输出</span></span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;js/main.js&quot;</span>, <span class="comment">// 将 js 文件输出到 static/js 目录中</span></span><br><span class="line">    <span class="comment">// ✨ 全局兜底：所有没单独配置 generator 的资源，都放到 media 文件夹</span></span><br><span class="line">    <span class="attr">assetModuleFilename</span>: <span class="string">&quot;media/[hash:10][ext][query]&quot;</span>, <span class="comment">// 图片的输出路径</span></span><br><span class="line">    <span class="attr">clean</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="comment">// 用来匹配 .css 结尾的文件</span></span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="comment">// use 数组里面 Loader 执行顺序是从右到左</span></span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;less-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.s[ac]ss$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;sass-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.styl$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="string">&quot;style-loader&quot;</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;stylus-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(png|jpe?g|gif|webp)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset&quot;</span>,</span><br><span class="line">        <span class="attr">parser</span>: &#123;</span><br><span class="line">          <span class="attr">dataUrlCondition</span>: &#123;</span><br><span class="line">            <span class="attr">maxSize</span>: <span class="number">10</span> * <span class="number">1024</span>, <span class="comment">// 小于10kb的图片会被base64处理</span></span><br><span class="line">          &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="comment">// 将图片文件输出到 /imgs 目录中</span></span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;imgs/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.(ttf|woff2?)$/</span>,</span><br><span class="line">        <span class="attr">type</span>: <span class="string">&quot;asset/resource&quot;</span>,</span><br><span class="line">        <span class="attr">generator</span>: &#123;</span><br><span class="line">          <span class="attr">filename</span>: <span class="string">&quot;static/media/[hash:8][ext][query]&quot;</span>,</span><br><span class="line">        &#125;,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.js$/</span>,</span><br><span class="line">        <span class="attr">exclude</span>: <span class="regexp">/node_modules/</span>, <span class="comment">// 排除node_modules代码不编译</span></span><br><span class="line">        <span class="attr">loader</span>: <span class="string">&quot;babel-loader&quot;</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">ESLintWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 指定检查文件的根目录</span></span><br><span class="line">      <span class="attr">context</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../src&quot;</span>),</span><br><span class="line">    &#125;),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">HtmlWebpackPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 以 public/index.html 为模板创建文件</span></span><br><span class="line">      <span class="comment">// 新的html文件有两个特点：1. 内容和源文件一致 2. 自动引入打包生成的js等资源</span></span><br><span class="line">      <span class="attr">template</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;../public/index.html&quot;</span>),</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行生产模式的指令：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack --config ./config/webpack.prod.js</span><br></pre></td></tr></table></figure><h3 id="11-4、配置运行指令"><a href="#11-4、配置运行指令" class="headerlink" title="11.4、配置运行指令"></a><strong>11.4、配置运行指令</strong></h3><p>为了方便运行不同模式的指令，我们将指令定义在 package.json 中 scripts 里面</p><figure class="highlight json"><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">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 其他省略</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx webpack serve --config ./config/webpack.dev.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;build&quot;</span><span class="punctuation">:</span> <span class="string">&quot;npx webpack --config ./config/webpack.prod.js&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>以后启动指令：</p><ul><li>开发模式：<code>npm run dev</code></li><li>生产模式：<code>npm run build</code></li></ul><h2 id="十二、CSS进阶优化"><a href="#十二、CSS进阶优化" class="headerlink" title="十二、CSS进阶优化"></a><strong>十二、CSS进阶优化</strong></h2><h3 id="12-1、提取-CSS-成单独文件"><a href="#12-1、提取-CSS-成单独文件" class="headerlink" title="12.1、提取 CSS 成单独文件"></a><strong>12.1、提取 CSS 成单独文件</strong></h3><p>Css 文件目前被<code>style-loader</code>打包到 js 文件中，当 js 文件加载时，会创建一个 style 标签来生成样式。</p><p>这样对于网站来说，会出现闪屏现象，用户体验不好，我们应该是单独的 Css 文件，通过 link 标签加载性能才好。</p><p><strong><code>link</code>标签加载的优势</strong></p><ul><li>浏览器缓存 (Caching)</li><li>并行下载 (Parallel Downloading)</li><li>复用性与关注点分离 (Separation of Concerns)</li><li>优化首屏渲染 (Performance)</li></ul><h4 id="1）安装包-4"><a href="#1）安装包-4" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i mini-css-extract-plugin -D</span><br></pre></td></tr></table></figure><h4 id="2）配置webpack"><a href="#2）配置webpack" class="headerlink" title="2）配置webpack"></a><strong><span style='color:red'>2）配置webpack</span></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MiniCssExtractPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;mini-css-extract-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>, <span class="string">&quot;css-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;less-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.s[ac]ss$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;sass-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.styl$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [<span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>, <span class="string">&quot;css-loader&quot;</span>, <span class="string">&quot;stylus-loader&quot;</span>],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">// 提取css成单独文件</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MiniCssExtractPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 定义输出文件名和目录</span></span><br><span class="line">      <span class="attr">filename</span>: <span class="string">&quot;css/main.css&quot;</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行<code>npm run build</code></p><h4 id="3）最佳实践"><a href="#3）最佳实践" class="headerlink" title="3）最佳实践"></a><strong><span style='color:red'>3）最佳实践</span></strong></h4><p>在目前的 Webpack 项目中，通常会看到这种配置：</p><ul><li><strong>开发环境</strong>：使用 <code>style-loader</code>。它通过 JS 动态创建 <code>&lt;style&gt;</code> 标签插入到页面。这样做是为了支持 <strong>热更新 (HMR)</strong>，修改 CSS 后页面不刷新即可生效，开发体验极佳。</li><li><strong>生产环境</strong>：使用 <code>MiniCssExtractPlugin.loader</code>。它会将 CSS 提取到独立文件中，生成类似 <code>main.abcd123.css</code> 的文件。这样可以配合浏览器的强缓存，极大提升用户的二次访问速度。</li></ul><h3 id="12-2、CSS兼容性处理"><a href="#12-2、CSS兼容性处理" class="headerlink" title="12.2、CSS兼容性处理"></a>12.2、CSS兼容性处理</h3><h4 id="1）安装包-5"><a href="#1）安装包-5" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i postcss-loader postcss postcss-preset-env -D</span><br></pre></td></tr></table></figure><p>这三个工具在 Webpack 构建流程中共同协作，负责处理 CSS 的兼容性和现代特性。它们之间的关系可以用“核心引擎”、“连接器”和“插件集”来类比。</p><p><strong>下面是详细的分析：</strong></p><ul><li><span style='color:#10c300'> 核心作用与定位</span></li></ul><table><thead><tr><th align="left">工具名称</th><th align="left">作用定位</th><th align="left">详细说明</th></tr></thead><tbody><tr><td align="left"><strong><code>postcss</code></strong></td><td align="left"><strong>核心转换引擎</strong></td><td align="left">这是一个用 JavaScript 转换 CSS 的工具。它本身不做任何具体的样式处理，而是提供了一个极其强大的 API，允许通过各种插件来分析和修改 CSS（类似 Babel 处理 JS 的机制）。</td></tr><tr><td align="left"><strong><code>postcss-loader</code></strong></td><td align="left"><strong>Webpack 连接器</strong></td><td align="left">它是 Webpack 的一个 Loader。作用是让 Webpack 能够调用 PostCSS。当 Webpack 遇到 CSS 文件时，<code>postcss-loader</code> 会把 CSS 交给 <code>postcss</code> 引擎去处理。</td></tr><tr><td align="left"><strong><code>postcss-preset-env</code></strong></td><td align="left"><strong>功能插件集</strong></td><td align="left">这是一个 PostCSS 插件。它能让你使用最新的 CSS 语法（如 变量、嵌套等），并自动根据你指定的浏览器目标（Browserslist）添加对应的 Vendor Prefix（前缀）或降级处理。它集成了 <code>autoprefixer</code> 等多个常用插件。</td></tr></tbody></table><hr><ul><li><span style='color:#10c300'>三者之间的依赖关系</span></li></ul><p>它们的关系是<strong>层层递进</strong>的：</p><ol><li><strong><code>postcss-loader</code></strong> 依赖 <strong><code>postcss</code></strong>：Loader 只是一个外壳，没有核心引擎它无法工作。</li><li><strong><code>postcss</code></strong> 需要 <strong><code>postcss-preset-env</code></strong>：核心引擎只是一个平台，如果没有插件，它进出 CSS 的结果是一模一样的。</li><li><strong>Webpack 流程</strong>： <code>Webpack</code> ➔ <code>postcss-loader</code> ➔ <code>postcss</code> ➔ <code>postcss-preset-env</code> (各种具体转换逻辑)</li></ol><h4 id="2）配置webpack-1"><a href="#2）配置webpack-1" class="headerlink" title="2）配置webpack"></a><strong><span style='color:red'>2）配置webpack</span></strong></h4><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MiniCssExtractPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;mini-css-extract-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">          <span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>,</span><br><span class="line">          <span class="string">&quot;css-loader&quot;</span>,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">loader</span>: <span class="string">&quot;postcss-loader&quot;</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">              <span class="attr">postcssOptions</span>: &#123;</span><br><span class="line">                <span class="attr">plugins</span>: [</span><br><span class="line">                  <span class="string">&quot;postcss-preset-env&quot;</span>, <span class="comment">// 能解决大多数样式兼容性问题</span></span><br><span class="line">                ],</span><br><span class="line">              &#125;,</span><br><span class="line">            &#125;,</span><br><span class="line">          &#125;,</span><br><span class="line">        ],</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">        <span class="attr">use</span>: [</span><br><span class="line">          <span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>,</span><br><span class="line">          <span class="string">&quot;css-loader&quot;</span>,</span><br><span class="line">          &#123;</span><br><span class="line">            <span class="attr">loader</span>: <span class="string">&quot;postcss-loader&quot;</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">              <span class="attr">postcssOptions</span>: &#123;</span><br><span class="line">                <span class="attr">plugins</span>: [</span><br><span class="line">                  <span class="string">&quot;postcss-preset-env&quot;</span>, <span class="comment">// 能解决大多数样式兼容性问题</span></span><br><span class="line">                ],</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="string">&quot;less-loader&quot;</span>,</span><br><span class="line">        ],</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">plugins</span>: [</span><br><span class="line">    <span class="comment">// 提取css成单独文件</span></span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MiniCssExtractPlugin</span>(&#123;</span><br><span class="line">      <span class="comment">// 定义输出文件名和目录</span></span><br><span class="line">      <span class="attr">filename</span>: <span class="string">&quot;css/main.css&quot;</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">  ],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="3）控制兼容性"><a href="#3）控制兼容性" class="headerlink" title="3）控制兼容性"></a><strong><span style='color:red'>3）控制兼容性</span></strong></h4><p>我们可以在 <code>package.json</code> 文件中添加 <code>browserslist</code> 来控制样式的兼容性做到什么程度。</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 其他省略</span></span><br><span class="line">  <span class="attr">&quot;browserslist&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;ie &gt;= 8&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>想要知道更多的 <code>browserslist</code> 配置，查看<a href="https://github.com/browserslist/browserslist">browserslist 文档</a></p><p>以上为了测试兼容性所以设置兼容浏览器 ie8 以上。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260228132335215.png" alt="image-20260228132335215"></p><p>实际开发中我们一般不考虑旧版本浏览器了，所以我们可以这样设置：</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="comment">// 其他省略</span></span><br><span class="line">  <span class="attr">&quot;browserslist&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;last 2 version&quot;</span><span class="punctuation">,</span> <span class="comment">// 每个主流浏览器（Chrome, Firefox, Safari, Edge 等）的最后两个版本。</span></span><br><span class="line">    <span class="string">&quot;&gt; 1%&quot;</span><span class="punctuation">,</span> <span class="comment">// 全球使用份额超过 1% 的浏览器。</span></span><br><span class="line">    <span class="string">&quot;not dead&quot;</span> <span class="comment">// 排除**“已死亡”**的浏览器。</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="4）优化合并"><a href="#4）优化合并" class="headerlink" title="4）优化合并"></a><strong><span style='color:red'>4）优化合并</span></strong></h4><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MiniCssExtractPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;mini-css-extract-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 获取处理样式的Loaders</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">getStyleLoaders</span> = (<span class="params">preProcessor</span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">return</span> [</span><br><span class="line">        <span class="title class_">MiniCssExtractPlugin</span>.<span class="property">loader</span>,</span><br><span class="line">        <span class="string">&quot;css-loader&quot;</span>,</span><br><span class="line">        &#123;</span><br><span class="line">            <span class="attr">loader</span>: <span class="string">&quot;postcss-loader&quot;</span>,</span><br><span class="line">            <span class="attr">options</span>: &#123;</span><br><span class="line">                <span class="attr">postcssOptions</span>: &#123;</span><br><span class="line">                    <span class="attr">plugins</span>: [</span><br><span class="line">                        <span class="string">&quot;postcss-preset-env&quot;</span>, <span class="comment">// 能解决大多数样式兼容性问题</span></span><br><span class="line">                    ],</span><br><span class="line">                &#125;,</span><br><span class="line">            &#125;,</span><br><span class="line">        &#125;,</span><br><span class="line">        preProcessor,</span><br><span class="line">    ].<span class="title function_">filter</span>(<span class="title class_">Boolean</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">    <span class="attr">module</span>: &#123;</span><br><span class="line">        <span class="attr">rules</span>: [</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">test</span>: <span class="regexp">/\.css$/</span>,</span><br><span class="line">                <span class="attr">use</span>: <span class="title function_">getStyleLoaders</span>()</span><br><span class="line">            &#125;,</span><br><span class="line">            &#123;</span><br><span class="line">                <span class="attr">test</span>: <span class="regexp">/\.less$/</span>,</span><br><span class="line">                <span class="attr">use</span>:  <span class="attr">use</span>: <span class="title function_">getStyleLoaders</span>(<span class="string">&quot;less-loader&quot;</span>)</span><br><span class="line">            &#125;,</span><br><span class="line">        ],</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">plugins</span>: [</span><br><span class="line">        <span class="comment">// 提取css成单独文件</span></span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">MiniCssExtractPlugin</span>(&#123;</span><br><span class="line">            <span class="comment">// 定义输出文件名和目录</span></span><br><span class="line">            <span class="attr">filename</span>: <span class="string">&quot;css/main.css&quot;</span>,</span><br><span class="line">        &#125;),</span><br><span class="line">    ],</span><br><span class="line">    <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行<code>npm run build</code>查看</p><h3 id="12-3、CSS压缩"><a href="#12-3、CSS压缩" class="headerlink" title="12.3、CSS压缩"></a>12.3、CSS压缩</h3><h4 id="1）安装包-6"><a href="#1）安装包-6" class="headerlink" title="1）安装包"></a><strong><span style='color:red'>1）安装包</span></strong></h4><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i css-minimizer-webpack-plugin -D</span><br></pre></td></tr></table></figure><h4 id="2）配置webpack-2"><a href="#2）配置webpack-2" class="headerlink" title="2）配置webpack"></a><strong><span style='color:red'>2）配置webpack</span></strong></h4><figure class="highlight js"><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">// webpack.config.js</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CssMinimizerPlugin</span> = <span class="built_in">require</span>(<span class="string">&quot;css-minimizer-webpack-plugin&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// ...</span></span><br><span class="line">  <span class="attr">plugins</span>: [<span class="keyword">new</span> <span class="title class_">CssMinimizerPlugin</span>()],</span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p>运行<code>npm run build</code>查看</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;📚 本指南旨在帮助开发者从零开始掌握 Webpack 5 的核心概念与基础配置，涵盖样式、资源、JS 处理及生产环境优化等全方位知识。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;目录&quot;&gt;&lt;a href=&quot;#目录&quot; class=</summary>
      
    
    
    
    <category term="工程化与质量" scheme="http://example.com/categories/%E5%B7%A5%E7%A8%8B%E5%8C%96%E4%B8%8E%E8%B4%A8%E9%87%8F/"/>
    
    
    <category term="webpack5" scheme="http://example.com/tags/webpack5/"/>
    
  </entry>
  
  <entry>
    <title>Babel 7 从入门到进阶的全场景指南</title>
    <link href="http://example.com/posts/b41c92e1.html"/>
    <id>http://example.com/posts/b41c92e1.html</id>
    <published>2026-02-27T22:30:00.000Z</published>
    <updated>2026-04-02T08:39:33.923Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>📚 本指南旨在帮助开发者彻底掌握 Babel 7+ 的工作机制、配置技巧及核心插件系统的使用。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80-babel-%E7%AE%80%E4%BB%8B">Babel 简介</a></li><li><a href="#%E4%BA%8C-5-%E5%88%86%E9%92%9F%E6%90%9E%E6%87%82-babel-%E7%9A%84%E5%B7%A5%E4%BD%9C%E6%96%B9%E5%BC%8F">工作方式</a></li><li><a href="#%E4%B8%89-%E6%9C%80%E5%B0%8F%E5%8F%AF%E8%BF%90%E8%A1%8C%E7%A4%BA%E4%BE%8B">快速上手</a></li><li><a href="#%E5%9B%9B-%E5%BF%85%E9%A1%BB%E5%BC%84%E6%87%82%E7%9A%84-4-%E4%B8%AA%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5">核心概念</a></li><li><a href="#%E4%BA%94-preset-env--core-js">Polyfill 实战</a></li><li><a href="#%E5%85%AD-runtime-%E4%B8%8E-polyfill-%E7%9A%84%E5%8C%BA%E5%88%AB">Runtime 优化</a></li><li><a href="#%E4%B8%83-babel-%E4%B8%8E-ts--react-%E7%BB%93%E5%90%88">生态集成</a></li><li><a href="#%E5%85%AB-babel-%E5%9C%A8-webpack-%E5%92%8C-vite-%E4%B8%AD%E7%9A%84%E7%94%9F%E6%80%81%E5%AE%9A%E4%BD%8D">打包构建</a></li><li><a href="#%E4%B9%9D-ast%E9%81%8D%E5%8E%86%E4%B8%8E%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86%E9%80%9F%E9%80%9A">高级原理</a></li><li><a href="#%E5%8D%81-%E6%89%8B%E5%86%99%E4%B8%80%E4%B8%AA-babel-%E6%8F%92%E4%BB%B6">插件开发</a></li><li><a href="#%E5%8D%81%E4%B8%80-%E7%BB%88%E6%9E%81%E4%B8%80%E9%94%AE%E5%A4%8D%E5%88%B6%E9%85%8D%E7%BD%AE%E6%A8%A1%E6%9D%BF">配置模板</a></li></ol><br><h2 id="一、-Babel-简介"><a href="#一、-Babel-简介" class="headerlink" title="一、 Babel 简介"></a><strong>一、 Babel 简介</strong></h2><h3 id="1-1-Babel-是什么？解决什么问题？"><a href="#1-1-Babel-是什么？解决什么问题？" class="headerlink" title="1.1 Babel 是什么？解决什么问题？"></a><strong><font color='red'>1.1 Babel 是什么？解决什么问题？</font></strong></h3><p><strong>一句话</strong>：Babel 是一个 <strong>JavaScript 编译器（Compiler &#x2F; Transpiler）</strong>，把“你写的现代 JS&#x2F;TS&#x2F;JSX”等代码，转换成“目标环境（老版浏览器&#x2F;Node）能平稳运行的代码”。</p><p>你会在这些场景用到 Babel：</p><ul><li><strong>降级新语法</strong>：把 ES202x 新语法转成旧语法（比如可选链 <code>?.</code>、空值合并 <code>??</code>、class 私有字段等）。</li><li><strong>编译 JSX</strong>：把 React 或 Vue 的 JSX 转成 <code>React.createElement</code> 或 <code>jsx-runtime</code> 的原生函数调用形式。</li><li><strong>擦除 TypeScript</strong>：把 TypeScript 里的类型声明直接去掉（<strong>注意：Babel 不做任何类型检查</strong>）。</li><li><strong>做代码定制化变换</strong>：比如自动注入日志、移除 <code>console.log</code>、按需引入组件库样式、甚至开发自己的宏（Macros），这些通常靠 Babel 插件完成。</li></ul><hr><br><h2 id="二、-5-分钟搞懂-Babel-的工作方式"><a href="#二、-5-分钟搞懂-Babel-的工作方式" class="headerlink" title="二、 5 分钟搞懂 Babel 的工作方式"></a><strong>二、 5 分钟搞懂 Babel 的工作方式</strong></h2><h3 id="2-1-编译流水线"><a href="#2-1-编译流水线" class="headerlink" title="2.1 编译流水线"></a><strong><font color='red'>2.1 编译流水线</font></strong></h3><p>Babel 编译其实就是一条 3 步走的流水线：</p><ol><li><strong>Parse（解析）</strong>：源码字符串转换成计算机更容易理解处理的数据结构<strong>AST（抽象语法树）</strong><ul><li>由 <code>@babel/parser</code>（以前被称为 Babylon）负责完成。</li></ul></li><li><strong>Transform（遍历 &#x2F; 转换阶段）</strong>：接收 Parse 阶段生成的 AST，对其进行深度优先遍历，并允许我们对树上的节点进行增、删、改操作（<strong>Babel 插件全在这里工作</strong>）<ul><li>由 <code>@babel/traverse</code> 负责完成，这是你编写自定义 Babel 插件时<strong>唯一需要介入</strong>的阶段。Babel 在这里采用了<strong>访问者模式 (Visitor Pattern)</strong>。</li></ul></li><li><strong>Generate（生成）</strong>：拿着经过 Traverse 阶段被插件修改过的全新 AST 树，把它重新拼接复原成普通的 JavaScript 代码字符串。<ul><li>由 <code>@babel/generator</code> 负责完成。在这个阶段，Babel 会再次深度优先遍历新的 AST，根据节点的各种类型，翻译成目标代码。在这个阶段，同时也可以提取出 <strong>Source Map（源码映射表）</strong>，方便开发者在浏览器里调试。</li></ul></li></ol><blockquote><p>💡 <strong>核心结论</strong>：</p><ul><li>Babel 自己的“本能”其实很弱，所有的实际能力都来自 <strong>插件（Plugins）</strong>。</li><li>但你不可能为了编译一个项目一个个去手写或安装几十个插件，所以官方提供了 <strong>预设（Presets）</strong>（即一组打包好的插件集合）。</li></ul></blockquote><hr><br><h2 id="三、-最小可运行示例"><a href="#三、-最小可运行示例" class="headerlink" title="三、 最小可运行示例"></a><strong>三、 最小可运行示例</strong></h2><blockquote><p>⚠️ <strong>避坑指南</strong>：Babel 7 开始，所有官方包都移入了 <code>@babel/</code> 作用域命名空间。如果你在网上查教程，看到 <code>babel-core</code>、<code>babel-preset-env</code>（没有 <code>@</code> 开头的），请直接关闭网页，那是老旧的 Babel 6 黑历史！</p></blockquote><h3 id="3-1-安装核心包"><a href="#3-1-安装核心包" class="headerlink" title="3.1 安装核心包"></a><strong><font color='red'>3.1 安装核心包</font></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i -D @babel/core @babel/cli</span><br></pre></td></tr></table></figure><h3 id="3-2-写一个最简单的配置"><a href="#3-2-写一个最简单的配置" class="headerlink" title="3.2 写一个最简单的配置"></a><strong><font color='red'>3.2 写一个最简单的配置</font></strong></h3><p>创建 <code>babel.config.json</code>（Babel 7 推荐，放置在项目根目录）：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="3-3-尝试编译"><a href="#3-3-尝试编译" class="headerlink" title="3.3 尝试编译"></a><strong><font color='red'>3.3 尝试编译</font></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx babel src --out-dir dist</span><br></pre></td></tr></table></figure><p><em>你会发现：不加任何 preset 或 plugin 时，Babel 输出的代码和源码一模一样。因为它“没有被灌输任何变换规则”。</em></p><hr><br><h2 id="四、-必须弄懂的-4-个核心概念"><a href="#四、-必须弄懂的-4-个核心概念" class="headerlink" title="四、 必须弄懂的 4 个核心概念"></a><strong>四、 必须弄懂的 4 个核心概念</strong></h2><h3 id="4-1-babel-core-是什么？"><a href="#4-1-babel-core-是什么？" class="headerlink" title="4.1 @babel&#x2F;core 是什么？"></a><strong><font color='red'>4.1 @babel&#x2F;core 是什么？</font></strong></h3><p>Babel 核心编译引擎，负责解析代码、调度执行配置好的插件、生成最终代码。但其实它自己什么语法都不会转。</p><h3 id="4-2-Plugin（插件）是什么？"><a href="#4-2-Plugin（插件）是什么？" class="headerlink" title="4.2 Plugin（插件）是什么？"></a><strong><font color='red'>4.2 Plugin（插件）是什么？</font></strong></h3><p><strong>插件“从左往右”</strong>：如果有多个插件，排在前面的先执行。</p><p>插件就是一个 JS 模块，负责告诉 Babel：遇到某种特定的 AST 节点时，该怎么改。<br><em>例如：遇到“箭头函数”，就把它转成 <code>function () &#123;&#125;</code>；遇到 <code>?.</code>，就改成安全的兼容判断。</em></p><h3 id="4-3-Preset（预设）是什么？"><a href="#4-3-Preset（预设）是什么？" class="headerlink" title="4.3 Preset（预设）是什么？"></a><strong><font color='red'>4.3 Preset（预设）是什么？</font></strong></h3><p><strong>预设“从右往左”</strong>（逆序）：如果有多个预设，排在<strong>后面</strong>的先执行。</p><p>预设解决的是“懒人不用挑插件”的问题。现代项目最常用的老三样：</p><ul><li><strong><code>@babel/preset-env</code></strong>：智能预设，根据你配置的目标浏览器，自动决定需要转哪些 JS 语法。</li><li><strong><code>@babel/preset-react</code></strong>：专门用来编译 JSX。</li><li><strong><code>@babel/preset-typescript</code></strong>：负责把 TS 类型代码擦除成普通的 JS（不做类型检查）。</li></ul><h3 id="4-4-配置文件怎么选？"><a href="#4-4-配置文件怎么选？" class="headerlink" title="4.4 配置文件怎么选？"></a><strong><font color='red'>4.4 配置文件怎么选？</font></strong></h3><ul><li><strong><code>babel.config.json</code>（Babel 7+ 强烈推荐）</strong>：作用于整个项目（甚至跨层级的 monorepo），一劳永逸。</li><li><strong><code>.babelrc</code> &#x2F; <code>.babelrc.json</code></strong>：只针对当前文件夹目录生效（局部配置），在复杂的 monorepo 工程中非常容易出现配置覆盖和失效的深坑。</li></ul><hr><br><h2 id="五、-babel-preset-env-core-js"><a href="#五、-babel-preset-env-core-js" class="headerlink" title="五、 @babel&#x2F;preset-env + core-js"></a><strong>五、 @babel&#x2F;preset-env + core-js</strong></h2><p>这一章是 Babel 学习的分水岭：大家经常把 <strong>语法转换</strong> 与 <strong>API 补齐（Polyfill）</strong> 混为一谈。</p><h3 id="5-1-语法转换-≠-API-补齐"><a href="#5-1-语法转换-≠-API-补齐" class="headerlink" title="5.1 语法转换 ≠ API 补齐"></a><strong><font color='red'>5.1 语法转换 ≠ API 补齐</font></strong></h3><ul><li><strong>Babel（准确说是 preset-env 自身）主要负责语法</strong>：比如 <code>const</code> 改成 <code>var</code>、去掉箭头函数等。</li><li><strong>但是对于新的 API 或内置对象</strong>：比如 <code>Promise</code>、<code>Array.from</code>、<code>Object.assign</code>、<code>[].includes</code>，老浏览器里根本没有这些全局对象和原型方法。Babel 转换后还是 <code>Promise</code> 字母，必定在旧浏览器报错。我们需要 <strong>Polyfill</strong> 给环境打补丁。</li></ul><blockquote><p>⚠️ <strong>废弃提醒</strong>：以前大家常装一个万金油包叫 <code>@babel/polyfill</code>。<strong>从 Babel 7.4 起这个包已被废弃！</strong> 现在的标准做法是直接使用 <code>core-js</code>，靠 <code>preset-env</code> 去自动引入。</p></blockquote><h3 id="5-2-正确的配置姿势（按需注入-Polyfill）"><a href="#5-2-正确的配置姿势（按需注入-Polyfill）" class="headerlink" title="5.2 正确的配置姿势（按需注入 Polyfill）"></a><strong><font color='red'>5.2 正确的配置姿势（按需注入 Polyfill）</font></strong></h3><p>首先安装 <code>core-js</code>（生产依赖）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npm i core-js</span><br><span class="line"><span class="comment"># 注意，preset 仍然放开发依赖</span></span><br><span class="line">npm i -D @babel/preset-env</span><br></pre></td></tr></table></figure><p>配置 <code>babel.config.json</code>：</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;targets&quot;</span><span class="punctuation">:</span> <span class="string">&quot;&gt; 0.2%, not dead&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.38&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;modules&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="1）必懂参数解析"><a href="#1）必懂参数解析" class="headerlink" title="1）必懂参数解析"></a><strong><font color='#10c300'>1）必懂参数解析</font></strong></h4><ul><li><code>targets</code>：你的目标环境。建议将其放到 <code>package.json</code> 的 <code>browserslist</code> 字段中统一定义，这里可以直接不写。</li><li><code>useBuiltIns: &quot;usage&quot;</code>：<strong>真正的神级配置，重点注意！</strong> 这是按需加载模式。这意味着在你项目的<strong>源码文件中，绝对不需要手动写 <code>import &#39;core-js&#39;</code></strong>。Babel 会静态分析你的代码，比如你用到了 <code>Promise</code>，它就在编译后的该文件顶部，自动帮你默默塞入一句 <code>import &quot;core-js/modules/es.promise.js&quot;</code>。这就叫按需补齐，体积最小！<ul><li><em>（补充：如果这里配置成 <code>&quot;entry&quot;</code>，那你才需要在项目入口文件如 <code>main.js</code> 的第一行手动写上一句 <code>import &quot;core-js&quot;</code>，此模式会把浏览器缺失的 API 全量打进来，容易导致包体积过大，不推荐。）</em></li></ul></li><li><code>corejs</code>：指定 core-js 的主版本号（目前标配使用的是 3.x ）。</li><li><code>modules: false</code>：让 Babel 别管 ES6 Module 的 <code>import/export</code>，把模块化的保留权交给 Webpack&#x2F;Vite 这种打包工具，以便它们做无用代码修剪（Tree-shaking）。</li></ul><hr><br><h2 id="六、-Runtime-与-Polyfill-的区别"><a href="#六、-Runtime-与-Polyfill-的区别" class="headerlink" title="六、 Runtime 与 Polyfill 的区别"></a><strong>六、 Runtime 与 Polyfill 的区别</strong></h2><p>很多人到死都没搞清楚 <code>core-js</code> 和 <code>@babel/runtime</code> 为什么同时出现。</p><h3 id="6-1-为什么需要-runtime-插件？"><a href="#6-1-为什么需要-runtime-插件？" class="headerlink" title="6.1 为什么需要 runtime 插件？"></a><strong><font color='red'>6.1 为什么需要 runtime 插件？</font></strong></h3><p>Babel 在转译像 <code>class</code>、<code>async/await</code> 这种复杂新特性时，会在文件里注入一些辅助函数（Helpers）。比如：<code>_classCallCheck</code>。<br>如果项目里有 100 个文件用了 <code>class</code>，这段冗长的 helper 函数就会被内联 100 次，包体积会变得极速膨胀。</p><h4 id="1）为什么需要辅助函数"><a href="#1）为什么需要辅助函数" class="headerlink" title="1）为什么需要辅助函数"></a><strong><span style='color:#10c300'>1）为什么需要辅助函数</span></strong></h4><p>这触及了 Babel 转译的核心——<strong>语法模拟（Syntax Emulation）</strong>。</p><ul><li><p><strong>为什么不能直接转译？</strong></p><p>虽然 ES6 的 <code>class</code>、<code>async/await</code> 经过 Babel 转换后变成了 ES5 的代码（主要是 <code>function</code> 和 <code>prototype</code>），但 ES5 本身的语法非常简陋。为了让转译后的 ES5 代码<strong>表现得和 ES6 原生一模一样</strong>，Babel 必须额外写一堆逻辑来“模拟”这些新特性的行为。</p></li><li><p><strong>以 _classCallCheck 为例</strong></p><p>在 ES6 中，如果你直接像调用普通函数一样调用 <code>class</code>（即不加 <code>new</code>），JS 引擎会报错：<code>TypeError: Class constructor cannot be invoked without &#39;new&#39;</code>。</p><p><strong>但是</strong>，转译成 ES5 后，<code>class</code> 变成了普通的 <code>function</code>。在 ES5 里，<code>function</code> 是可以直接调用的。</p><p>为了<strong>强制报错（模拟原生行为）</strong>，Babel 就会注入 <code>_classCallCheck</code>：</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Babel 生成的辅助函数</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">_classCallCheck</span>(<span class="params">instance, Constructor</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (!(instance <span class="keyword">instanceof</span> <span class="title class_">Constructor</span>)) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">TypeError</span>(<span class="string">&quot;Cannot call a class as a function&quot;</span>);</span><br><span class="line">  &#125;</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="keyword">var</span> <span class="title class_">Person</span> = <span class="keyword">function</span> <span class="title function_">Person</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="title function_">_classCallCheck</span>(<span class="variable language_">this</span>, <span class="title class_">Person</span>); <span class="comment">// 检查是不是用 new 调用的</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure></li></ul><h3 id="6-2-transform-runtime-的作用"><a href="#6-2-transform-runtime-的作用" class="headerlink" title="6.2 transform-runtime 的作用"></a><strong><font color='red'>6.2 transform-runtime 的作用</font></strong></h3><p>安装配套：</p><figure class="highlight bash"><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">npm i -D @babel/plugin-transform-runtime</span><br><span class="line">npm i @babel/runtime</span><br></pre></td></tr></table></figure><p>配置（<strong>这通常写在你的 <code>babel.config.json</code> 或对应 Babel 配置文件中</strong>）：</p><blockquote><p>💡 <strong>极其容易困惑的基础知识</strong>：<br>在 Babel 配置文件中，<code>presets</code>（预设数组） 和 <code>plugins</code>（插件数组） 是<strong>完全同级并列</strong>的。</p></blockquote><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="comment">// 这里放 Babel 的环境预设、React 预设等</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="comment">// 核心参数：告诉 Babel 帮我把内联的辅助函数（Helpers）替换为以 module 形式引入 @babel/runtime 相关方法</span></span><br><span class="line">        <span class="attr">&quot;&lt;br&gt;&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span> <span class="comment">// 默认true</span></span><br><span class="line">        <span class="comment">// 是否开启 generator 相关的 polyfill 抽离，避免全局污染全局变量</span></span><br><span class="line">        <span class="attr">&quot;regenerator&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="comment">// 默认true</span></span><br><span class="line">        <span class="comment">// (可选) 如果你是开发 npm 包库，不想全局引入 Promise 等 API</span></span><br><span class="line">        <span class="comment">// 你可以把这里的 corejs 设为 3，让 Babel 从 runtime 帮你进行局部的安全 polyfill 加载</span></span><br><span class="line">        <span class="comment">// &quot;corejs&quot;: 3</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="1）核心结论"><a href="#1）核心结论" class="headerlink" title="1）核心结论"></a><strong><font color='#10c300'>1）核心结论</font></strong></h4><p><code>transform-runtime</code> 被召唤出来就是为了 <strong>避免每个文件重复声明 Babel Helper</strong>。它会把那些注入的辅助函数，统统变成 <code>require(&quot;@babel/runtime/helpers/xxx&quot;)</code> 这个包里的引用。包体积瞬间被拯救。</p><blockquote><p>💡 <strong>项目选择建议</strong>：</p><ul><li><strong>做业务项目（Web App）</strong>：用 <code>@babel/preset-env</code> 配上 <code>core-js</code> (useBuiltIns) 做全局 Polyfill。</li><li><strong>开发基础组件库&#x2F;NPM 包</strong>：尽量不要用 <code>useBuiltIns</code> 去污染全局原型，而是完全依赖 <code>@babel/plugin-transform-runtime</code> 做局部沙盒版的 api 隔离降级（配置它的 <code>corejs</code> 选项）。</li></ul></blockquote><hr><br><h2 id="七、-Babel-与-TS-React-结合"><a href="#七、-Babel-与-TS-React-结合" class="headerlink" title="七、 Babel 与 TS &#x2F; React 结合"></a><strong>七、 Babel 与 TS &#x2F; React 结合</strong></h2><h3 id="7-1-React：自动引入-JSX-运行时"><a href="#7-1-React：自动引入-JSX-运行时" class="headerlink" title="7.1 React：自动引入 JSX 运行时"></a><strong><font color='red'>7.1 React：自动引入 JSX 运行时</font></strong></h3><p>配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">[</span><span class="string">&quot;@babel/preset-react&quot;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;runtime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;automatic&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><em><code>runtime: &quot;automatic&quot;</code>（React 17+ 特性）意味着你再也不用在组件第一行手动写 <code>import React from &quot;react&quot;</code> 了。</em></p><h3 id="7-2-TypeScript：编译-TS-这点坑"><a href="#7-2-TypeScript：编译-TS-这点坑" class="headerlink" title="7.2 TypeScript：编译 TS 这点坑"></a><strong><font color='red'>7.2 TypeScript：编译 TS 这点坑</font></strong></h3><p>配置：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;@babel/preset-typescript&quot;</span><span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="1）关键注意事项"><a href="#1）关键注意事项" class="headerlink" title="1）关键注意事项"></a><strong><font color='#10c300'>1）关键注意事项</font></strong></h4><ol><li>Babel 剥离 TS 极快，但<strong>不负责类型检查</strong>。想要类型检查报错，得加上 <code>tsc --noEmit</code> 命令配合。<ul><li><strong>具体操作</strong>：你应该在根目录下生成一个 <code>tsconfig.json</code> 文件。然后在项目的 <code>package.json</code> 的 <code>scripts</code> 中添加一条脚本，例如 <code>&quot;type-check&quot;: &quot;tsc --noEmit&quot;</code>。在每次打包（如 <code>npm run build</code>）或者提交代码（配合 husky + lint-staged）之前，先运行这条命令，以便把任何类型错误扼杀在摇篮里。这里 <code>--noEmit</code> 的意思是：tsc 编译器只负责报错，坚决不输出任何编译后的 js 文件（因为输出打包的工作已经全权交给 Babel &#x2F; Webpack &#x2F; Vite 了）。</li></ul></li><li><strong>踩坑预警</strong>：如果你直接用 <code>@babel/cli</code> 去编译一个带有 TS 文件的项目，它默认是会忽略 <code>.ts</code> 扩展名的！必须通过 CLI 指定扩展名才能生效：<br><code>npx babel src --out-dir dist --extensions &quot;.ts,.tsx&quot;</code></li></ol><hr><br><h2 id="八、-Babel-在-Webpack-和-Vite-中的生态定位"><a href="#八、-Babel-在-Webpack-和-Vite-中的生态定位" class="headerlink" title="八、 Babel 在 Webpack 和 Vite 中的生态定位"></a><strong>八、 Babel 在 Webpack 和 Vite 中的生态定位</strong></h2><ul><li><strong>Webpack（搭配 <code>babel-loader</code>）</strong>：<br>在 Webpack 中，一切都靠 loader 接入。当 Webpack 遇到 <code>.js/.ts/.jsx</code> 时，会交给 <code>babel-loader</code> 唤醒 Babel 编译，编译后的字符串再吐回给 Webpack 打包。</li><li><strong>Vite（基于 ESBuild）</strong>：<br>Vite 在开发环境下主要使用 go 语言写的 ESBuild 极速转译代码，默认不启用 Babel。但在一些依赖 Babel 生态的场景下（例如 React 的 Fast Refresh 热更新、CSS in JS 宏、旧版本浏览器兼容、按需组件导入等），Vite 依然会借助自身的插件底层调用 Babel 辅助构建。</li></ul><hr><br><h2 id="九、-AST、遍历与编译原理速通"><a href="#九、-AST、遍历与编译原理速通" class="headerlink" title="九、 AST、遍历与编译原理速通"></a><strong>九、 AST、遍历与编译原理速通</strong></h2><p>你不需要成为编译原理专家，但要记住这 3 个词：</p><h3 id="9-1-AST（Abstract-Syntax-Tree，抽象语法树）"><a href="#9-1-AST（Abstract-Syntax-Tree，抽象语法树）" class="headerlink" title="9.1 AST（Abstract Syntax Tree，抽象语法树）"></a><strong><font color='red'>9.1 AST（Abstract Syntax Tree，抽象语法树）</font></strong></h3><p>简单来说：<strong>AST 就是把你写的“源代码字符串”，转换成了计算机（编译器）更容易理解和操作的“树状数据结构”。</strong></p><ul><li><strong>为什么需要 AST？</strong><br>对于计算机来说，代码只是一串字符序列，想要修改、翻译或者检查代码（比如处理复杂的嵌套和作用域），直接操作字符串极其困难。所以需要把一维字符串解析成结构化的树状数据（通常是 JSON 格式）。</li><li><strong>代码是怎么变成 AST 的？</strong><br>这个过程叫做<strong>解析（Parsing）</strong>，通常分为两步：<ul><li><strong>词法分析（Tokenization）</strong>：把代码切成一个个具备独立意义的“词法单元”（Tokens）。例如 <code>let a = 1;</code> 会被切成：<code>let</code>, <code>a</code>, <code>=</code>, <code>1</code>, <code>;</code>。</li><li><strong>语法分析（Syntax Analysis）</strong>：把切出来的 Tokens 数组，根据 JS 语法规则组合拼装成树状结构（AST）。比如 <code>let a = 1;</code> 会被解析成包含 <code>VariableDeclaration</code>、<code>Identifier</code>、<code>NumericLiteral</code> 等节点的树。</li></ul></li><li><strong>AST 的用处：</strong><br>前端工程化里 90% 的“魔法”（比如 Babel 转译、ESLint 检查、Webpack 构建依赖图谱、Vue&#x2F;React 模板编译）的核心流程都是：<strong>源码 → AST → 操作（修改&#x2F;分析）AST → 重新生成代码</strong>。</li></ul><h3 id="9-2-Visitor（访问者模式）"><a href="#9-2-Visitor（访问者模式）" class="headerlink" title="9.2 Visitor（访问者模式）"></a><strong><font color='red'>9.2 Visitor（访问者模式）</font></strong></h3><p>你可以把这个想成一个事件监听器，设定“当我遍历 AST 遇到特定的节点（比如遇到 <code>BinaryExpression</code> 节点）时，触发我的回调函数”。</p><ul><li>通过节点类型（如CallExpression()）进入钩子函数。</li><li>传入的参数是 <code>path</code>（路径），它包含了当前节点数据 <code>path.node</code> 和很多操作节点的 API 挂载点。</li><li><strong>关键</strong>：仅靠节点类型是不够的（比如有很多函数调用），必须使用 <code>@babel/types</code> (即 <code>t</code>) 提供的方法进行严谨的条件过滤，避免误伤其他代码。</li></ul><h3 id="9-3-Path（路径对象）"><a href="#9-3-Path（路径对象）" class="headerlink" title="9.3 Path（路径对象）"></a><strong><font color='red'>9.3 Path（路径对象）</font></strong></h3><p>插件拿到节点不仅仅是拿到节点本身的数据，而是拿到一个 Path 包装对象。它不仅包含了当前节点的信息，还包含了节点之间的父子上下文关联，并通过调用 <code>path.replaceWith()</code>、<code>path.remove()</code> 等提供的一整套 API 让你去安全地修改和操作 these 节点。</p><ul><li><strong>删</strong>：<code>path.remove()</code></li><li><strong>改</strong>：<code>path.replaceWith(newNode)</code> 或直接修改 <code>path.node.xxx</code> 的属性。</li><li><strong>增</strong>：<code>path.insertBefore(newNode)</code> 或 <code>path.insertAfter(newNode)</code>。</li><li><strong>创建新节点</strong>：使用 <code>@babel/types</code> (如 <code>t.identifier(&#39;myVar&#39;)</code>) 或 <code>@babel/template</code> (根据字符串模板快速生成复杂 AST)。</li></ul><hr><br><h2 id="十、-手写一个-Babel-插件"><a href="#十、-手写一个-Babel-插件" class="headerlink" title="十、 手写一个 Babel 插件"></a><strong>十、 手写一个 Babel 插件</strong></h2><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// babel插件核心结构</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> (<span class="params">&#123; types: t &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&quot;remove-console&quot;</span>,</span><br><span class="line">        <span class="attr">visitor</span>: &#123;</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></pre></td></tr></table></figure><h3 id="10-1-项目结构"><a href="#10-1-项目结构" class="headerlink" title="10.1 项目结构"></a><strong><span style='color:red'>10.1 项目结构</span></strong></h3><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">babel-plugin-dome/</span><br><span class="line">    │</span><br><span class="line">    ├── src/                    # 测试源码目录（你想用 Babel 转换的代码）</span><br><span class="line">    │   └── index.js            # 包含各种 console.log let 等待被转换的 JS 文件</span><br><span class="line">    │</span><br><span class="line">    ├── plugin/                 # Babel 插件源码目录（你手写的插件全放在这里）</span><br><span class="line">    │   ├── remove-console.js   # 我们实现的第一个插件：移除 console 对象调用</span><br><span class="line">    │   └── let-to-var.js       # 我们实现的第二个插件：将 let 转换为 var</span><br><span class="line">    │</span><br><span class="line">    ├── dist/                   # 编译结果目录（Babel 转换后生成的代码存放在此）</span><br><span class="line">    │   └── index.js            # 经过转换后的最终 JS 文件</span><br><span class="line">    │</span><br><span class="line">    ├── babel.config.json       # Babel 的核心配置文件（用来引入我们的自定义插件）</span><br><span class="line">    │  </span><br><span class="line">    ├── package.json            # npm 项目描述文件</span><br><span class="line">    │</span><br><span class="line">    └── node_modules/           # 项目依赖模块包（Babel 运行时所需的第三方库）</span><br></pre></td></tr></table></figure><h3 id="10-2、安装依赖"><a href="#10-2、安装依赖" class="headerlink" title="10.2、安装依赖"></a><strong><span style='color:red'>10.2、安装依赖</span></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm i @babel/cli @babel/core @babel/preset-env -D</span><br></pre></td></tr></table></figure><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;@babel/cli&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.28.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@babel/core&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.29.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;@babel/preset-env&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.29.0&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h3 id="10-3、配置"><a href="#10-3、配置" class="headerlink" title="10.3、配置"></a><strong><span style='color:red'>10.3、配置</span></strong></h3><figure class="highlight js"><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="comment">// src\index.js</span></span><br><span class="line"><span class="comment">// 这是待转换的业务代码</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">add</span> = (<span class="params">a, b</span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;计算开始，参数：&quot;</span>, a, b);</span><br><span class="line">    <span class="keyword">const</span> result = a + b;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">warn</span>(<span class="string">&quot;这是一个警告&quot;</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">error</span>(<span class="string">&quot;这是一个错误日志&quot;</span>);</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>));</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">var</span> b = <span class="number">2</span>;</span><br></pre></td></tr></table></figure><figure class="highlight js"><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">// plugin\remove-console.js</span></span><br><span class="line"><span class="comment">// Babel 插件就是一个函数，接收 babel 对象，返回一个包含 visitor 的对象</span></span><br><span class="line"><span class="comment">// visitor 是「访问者模式」的体现：Traverse 阶段会遍历每个 AST 节点，</span></span><br><span class="line"><span class="comment">// 当节点类型匹配时，自动调用对应的钩子函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> (<span class="params">&#123; types: t &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&quot;remove-console&quot;</span>,</span><br><span class="line">        <span class="attr">visitor</span>: &#123;</span><br><span class="line">            <span class="title class_">CallExpression</span>(path) &#123;</span><br><span class="line">                <span class="keyword">const</span> &#123; callee &#125; = path.<span class="property">node</span>;</span><br><span class="line">                <span class="keyword">if</span> (</span><br><span class="line">                    t.<span class="title function_">isMemberExpression</span>(callee) &amp;&amp;</span><br><span class="line">                    t.<span class="title function_">isIdentifier</span>(callee.<span class="property">object</span>, &#123; <span class="attr">name</span>: <span class="string">&quot;console&quot;</span> &#125;)</span><br><span class="line">                ) &#123;</span><br><span class="line">                    path.<span class="title function_">remove</span>();</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;,</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><ul><li>types: 就是@babel&#x2F;types 包含各种构建和校验 AST（抽象语法树）节点工具的方法库</li><li>visitor: 这是插件的核心。Babel 会遍历（Traverse）代码生成的 AST，visitor 对象定义了当 Babel “访问”到特定类型的节点时该执行的操作。</li><li>CallExpression: 这是一个 AST 节点类型，代表函数调用表达式（例如 func() 或 console.log()）。</li><li>path: 代表当前节点的“路径”对象。它不仅包含当前节点的信息（path.node），还包含与父节点的关系、作用域信息，以及操作节点的方法（如 path.remove）。</li><li>path.node: 获取当前的 CallExpression 节点。</li><li>callee: 函数调用中的“被调用者”。在 console.log() 中，callee 就是 console.log 这部分。</li><li>t.isMemberExpression(callee): 判断被调用者是否是一个成员表达式。<ul><li>console.log 是成员表达式（对象 console 上的属性 log）</li><li>普通的 alert() 就不是成员表达式，而是 Identifier。</li></ul></li><li>t.isIdentifier(callee.object, { name: “console” }): 进一步判断该成员表达式的对象（object）部分是否是一个名为 “console” 的标识符（Identifier）。<ul><li>这确保了我们匹配的是 console.log、console.error 等，而不会误伤其他类似的调用。</li></ul></li></ul><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// plugin\let-to-var.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">function</span> (<span class="params">&#123; types: t &#125;</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">        <span class="attr">name</span>: <span class="string">&quot;let-to-var&quot;</span>,</span><br><span class="line">        <span class="attr">visitor</span>: &#123;</span><br><span class="line">            <span class="title class_">VariableDeclaration</span>(path) &#123;</span><br><span class="line">                <span class="comment">// 如果当前声明的不是 let，则不处理，直接返回</span></span><br><span class="line">                <span class="keyword">if</span> (path.<span class="property">node</span>.<span class="property">kind</span> !== <span class="string">&quot;let&quot;</span>) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">                <span class="comment">// 将声明的 kind 从 let 修改为 var</span></span><br><span class="line">                path.<span class="property">node</span>.<span class="property">kind</span> = <span class="string">&quot;var&quot;</span>;</span><br><span class="line">            &#125;,</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><p><strong>调用</strong></p><figure class="highlight js"><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">// babel.config.json</span></span><br><span class="line">&#123;</span><br><span class="line">    <span class="string">&quot;presets&quot;</span>: [<span class="string">&quot;@babel/preset-env&quot;</span>],</span><br><span class="line">    <span class="string">&quot;plugins&quot;</span>: [<span class="string">&quot;./plugin/remove-console.js&quot;</span>, <span class="string">&quot;./plugin/let-to-var.js&quot;</span>]</span><br><span class="line">&#125;</span><br><span class="line"></span><br></pre></td></tr></table></figure><hr><br><h2 id="十一、编写插件的核心注意点"><a href="#十一、编写插件的核心注意点" class="headerlink" title="十一、编写插件的核心注意点"></a>十一、编写插件的核心注意点</h2><h3 id="11-1、切勿产生死循环-Infinite-Loops"><a href="#11-1、切勿产生死循环-Infinite-Loops" class="headerlink" title="11.1、切勿产生死循环 (Infinite Loops)"></a><strong><span style='color:red'>11.1、切勿产生死循环 (Infinite Loops)</span></strong></h3><p>如果你在某个节点中<strong>插入或替换</strong>了一个与当前节点类型相同的新节点，Babel 会继续遍历这个新生成的节点，从而引发死循环。 </p><h4 id="1）死循环发生的场景模拟"><a href="#1）死循环发生的场景模拟" class="headerlink" title="1）死循环发生的场景模拟"></a><strong><span style='color:#10c300'>1）死循环发生的场景模拟</span></strong></h4><p>假设我们写了这样一个插件：<strong>“只要看到标识符 <code>foo</code>，就把他替换成新的标识符 <code>foo</code>”</strong>（虽然看起来没用，但很典型）。</p><figure class="highlight js"><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="attr">visitor</span>: &#123;</span><br><span class="line">  <span class="title class_">Identifier</span>(path) &#123;</span><br><span class="line">    <span class="keyword">if</span> (path.<span class="property">node</span>.<span class="property">name</span> === <span class="string">&#x27;foo&#x27;</span>) &#123;</span><br><span class="line">      <span class="comment">// 创建一个新的 foo 节点，替换掉老的 foo 节点</span></span><br><span class="line">      path.<span class="title function_">replaceWith</span>(t.<span class="title function_">identifier</span>(<span class="string">&#x27;foo&#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><h4 id="2）为什么会死循环？-Babel-的微观视角"><a href="#2）为什么会死循环？-Babel-的微观视角" class="headerlink" title="2）为什么会死循环？(Babel 的微观视角)"></a><strong><span style='color:#10c300'>2）为什么会死循环？(Babel 的微观视角)</span></strong></h4><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">1. 遍历到 Identifier 节点 &#123; type: &quot;Identifier&quot;, name: &quot;foo&quot; &#125;</span><br><span class="line">   ↓</span><br><span class="line">2. 触发 Identifier 钩子</span><br><span class="line">   ↓</span><br><span class="line">3. 执行 replaceWith，创建新节点 &#123; type: &quot;Identifier&quot;, name: &quot;foo&quot; &#125;</span><br><span class="line">   ↓</span><br><span class="line">4. Babel 继续遍历这个新节点</span><br><span class="line">   ↓</span><br><span class="line">5. 新节点类型是 Identifier，又触发 Identifier 钩子  ← 回到步骤 2！</span><br><span class="line">   ↓</span><br><span class="line">6. 无限循环... 💀</span><br></pre></td></tr></table></figure><h4 id="3）为什么对新节点会重新遍历？"><a href="#3）为什么对新节点会重新遍历？" class="headerlink" title="3）为什么对新节点会重新遍历？"></a><strong><span style='color:#10c300'>3）为什么对新节点会重新遍历？</span></strong></h4><p>Babel 这么设计<strong>并不是 Bug，而是一个必须存在的 Feature（特性）</strong>。</p><p><strong>场景设定</strong>：假设你有两个插件（执行顺序先A后B）</p><p><strong>插件 A</strong>：把 <code>foo</code> 替换成复杂的自执行函数<code>(function()&#123; console.log(&#39;bar&#39;) &#125;)()</code></p><p><strong>插件 B</strong>：移除所有 <code>console.log</code></p><p><strong><span style='color:cornflowerblue'>如果 Babel 替换后”拍拍屁股走人”（不重新遍历）</span></strong></p><p>不对新节点重新遍历就像Babel 只处理了你替换的那个节点本身，但没有深入遍历新节点内部的子节点。</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></pre></td><td class="code"><pre><span class="line">遍历过程：</span><br><span class="line"><span class="number">1.</span> 遇到 <span class="title class_">CallExpression</span> (<span class="title function_">foo</span>())</span><br><span class="line"><span class="number">2.</span> 插件 A 把它替换成 (<span class="keyword">function</span>(<span class="params"></span>)&#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bar&#x27;</span>) &#125;)()</span><br><span class="line"><span class="number">3.</span> <span class="title class_">Babel</span> 说：<span class="string">&quot;好的，替换完了，继续遍历下一个节点&quot;</span></span><br><span class="line"><span class="number">4.</span> 插件 B 根本没机会看到新插入的 <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bar&#x27;</span>)</span><br><span class="line"><span class="number">5.</span> 最终输出：(<span class="keyword">function</span>(<span class="params"></span>)&#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bar&#x27;</span>) &#125;)()  ← <span class="variable language_">console</span> 没被移除！</span><br></pre></td></tr></table></figure><p><strong><span style='color:cornflowerblue'>如果 Babel 对新节点重新遍历</span></strong></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></pre></td><td class="code"><pre><span class="line">遍历过程：</span><br><span class="line"><span class="number">1.</span> 遇到 <span class="title class_">CallExpression</span> (<span class="title function_">foo</span>())</span><br><span class="line"><span class="number">2.</span> 插件 A 把它替换成 (<span class="keyword">function</span>(<span class="params"></span>)&#123; <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bar&#x27;</span>) &#125;)()</span><br><span class="line"><span class="number">3.</span> <span class="title class_">Babel</span> 说：<span class="string">&quot;等等，这是个新节点，我要重新遍历它内部的子节点&quot;</span></span><br><span class="line"><span class="number">4.</span> 遍历新节点内部：</span><br><span class="line">   - 遇到 <span class="title class_">FunctionExpression</span></span><br><span class="line">   - 遇到 <span class="title class_">BlockStatement</span></span><br><span class="line">   - 遇到 <span class="title class_">ExpressionStatement</span></span><br><span class="line">   - 遇到 <span class="title class_">CallExpression</span> (<span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;bar&#x27;</span>))  ← 插件 B 发现了它！</span><br><span class="line"><span class="number">5.</span> 插件 B 把 <span class="variable language_">console</span>.<span class="property">log</span> 移除</span><br><span class="line"><span class="number">6.</span> 最终输出：(<span class="keyword">function</span>(<span class="params"></span>)&#123; &#125;)()  ← <span class="variable language_">console</span> 被正确移除！</span><br></pre></td></tr></table></figure><h4 id="4）如何打破这个死循环？"><a href="#4）如何打破这个死循环？" class="headerlink" title="4）如何打破这个死循环？"></a><strong><span style='color:#10c300'>4）如何打破这个死循环？</span></strong></h4><p>既然这是正常机制，Babel 也给我们提供了**“免检金牌”**，主要有两种方式来打破死循环：</p><p><strong>方案 1：打上人工标记 (Flag) 🌟（最常用）</strong></p><p>在生成新节点的时候，自己给它盖个章，在钩子最前面拦截：</p><figure class="highlight js"><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="attr">visitor</span>: &#123;</span><br><span class="line">  <span class="title class_">Identifier</span>(path) &#123;</span><br><span class="line">    <span class="comment">// 1. 如果这个节点有“免检印章”，直接放行</span></span><br><span class="line">    <span class="keyword">if</span> (path.<span class="property">node</span>.<span class="property">_isVisited</span>) <span class="keyword">return</span>; </span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (path.<span class="property">node</span>.<span class="property">name</span> === <span class="string">&#x27;foo&#x27;</span>) &#123;</span><br><span class="line">      <span class="keyword">const</span> newNode = t.<span class="title function_">identifier</span>(<span class="string">&#x27;foo&#x27;</span>);</span><br><span class="line">      <span class="comment">// 2. 给新节点盖章</span></span><br><span class="line">      newNode.<span class="property">_isVisited</span> = <span class="literal">true</span>; </span><br><span class="line">      </span><br><span class="line">      path.<span class="title function_">replaceWith</span>(newNode);</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><p><strong>方案 2：利用 <code>path.skip()</code> 直接命令 Babel 跳过</strong></p><p>如果替换后，你明确知道这个节点内部再也没有需要处理的子节点了，可以直接命令 Babel 质检员：“跳过对当前节点本身以及它所有子孙节点的二次遍历！”</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="attr">visitor</span>: &#123;</span><br><span class="line">  <span class="title class_">Identifier</span>(path) &#123;</span><br><span class="line">    <span class="keyword">if</span> (path.<span class="property">node</span>.<span class="property">name</span> === <span class="string">&#x27;foo&#x27;</span>) &#123;</span><br><span class="line">      path.<span class="title function_">replaceWith</span>(t.<span class="title function_">identifier</span>(<span class="string">&#x27;foo&#x27;</span>));   </span><br><span class="line">      <span class="comment">// 告诉 Babel：这个新节点我担保没问题，你跳过它，去检查下一个树枝吧！</span></span><br><span class="line">      path.<span class="title function_">skip</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><p><strong>总结</strong>：之所以死循环，是因为 Babel 为了保证 AST 的转换严谨性，<strong>默认会对一切新插入的节点进行二次递归遍历</strong>。如果你替换的恰好是同类型的节点，就会在原地无限触发同一个 Visitor 钩子。利用变量标记或 <code>path.skip()</code> 即可轻松破解。</p><h3 id="11-2、注意作用域与变量名冲突-Scope-Binding"><a href="#11-2、注意作用域与变量名冲突-Scope-Binding" class="headerlink" title="11.2、注意作用域与变量名冲突 (Scope &amp; Binding)"></a><strong><span style='color:red'>11.2、注意作用域与变量名冲突 (Scope &amp; Binding)</span></strong></h3><p>如果你想在 AST 中插入新的变量声明，切忌直接写死变量名（例如强行插入一个 <code>const temp = 1;</code>），这极易覆盖原有代码里的同名变量。</p><p> <strong>解决办法</strong>：利用 Babel 提供的作用域 API <code>path.scope.generateUidIdentifier(&#39;temp&#39;)</code>，它会自动在当前作用域寻找没有被占用的安全变量名（如 <code>_temp</code>、<code>_temp2</code>）。</p><h3 id="11-3、避免使用全局变量维持状态"><a href="#11-3、避免使用全局变量维持状态" class="headerlink" title="11.3、避免使用全局变量维持状态"></a><strong><span style='color:red'>11.3、避免使用全局变量维持状态</span></strong></h3><p>Babel 在编译多文件项目时，使用的是同一个插件实例。如果你把一些临时状态挂载在插件最外层的全局变量上，不同文件编译时会导致状态污染。 </p><p><strong>解决办法</strong>：将状态保存在 <code>state</code> 参数中，或者在 pre（遍历前）和 <code>post</code>（遍历后）生命周期钩子中初始化和清理状态。</p><figure class="highlight js"><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="attr">visitor</span>: &#123;</span><br><span class="line">  <span class="title class_">Program</span>: &#123;</span><br><span class="line">    <span class="title function_">enter</span>(<span class="params">path, state</span>) &#123;</span><br><span class="line">      <span class="comment">// 遍历整个文件前，在 state 上挂载状态</span></span><br><span class="line">      state.<span class="property">myCustomData</span> = []; </span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="title class_">CallExpression</span>(path, state) &#123;</span><br><span class="line">    <span class="comment">// 读取状态</span></span><br><span class="line">    state.<span class="property">myCustomData</span>.<span class="title function_">push</span>(path.<span class="property">node</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="11-4-尽早退出，优化性能-Bail-Out-Early"><a href="#11-4-尽早退出，优化性能-Bail-Out-Early" class="headerlink" title="11.4. 尽早退出，优化性能 (Bail Out Early)"></a><strong><span style='color:red'>11.4. 尽早退出，优化性能 (Bail Out Early)</span></strong></h3><p>庞大项目的 AST 非常深。如果你的条件不匹配，应该在 <code>visitor</code> 函数的第一行尽早 <code>return</code>，而不是把逻辑嵌套在深层的 <code>if</code> 块中。这能大幅提升编译速度。</p><h3 id="11-5-优雅的抛出错误"><a href="#11-5-优雅的抛出错误" class="headerlink" title="11.5. 优雅的抛出错误"></a><strong><span style='color:red'>11.5. 优雅的抛出错误</span></strong></h3><p>如果在解析过程中发现了不符合插件预期的非法语法，不要只用 <code>console.error</code>。使用 <code>path.buildCodeFrameError(message)</code> 抛出错误可以像原生 Babel 报错一样，在终端里精准地画出红线，指出出错的代码位置，极大提升调试体验。</p><h3 id="11-6-善用-babel-template"><a href="#11-6-善用-babel-template" class="headerlink" title="11.6. 善用 @babel/template"></a><strong><span style='color:red'>11.6. 善用 <code>@babel/template</code></span></strong></h3><p>当需要插入一大段复杂的代码逻辑时，用 <code>@babel/types</code> 一层层组装极度繁琐且容易发疯。引入 <code>@babel/template</code> 可以直接像写原生字符串一样生成代码树：</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> template = <span class="built_in">require</span>(<span class="string">&#x27;@babel/template&#x27;</span>).<span class="property">default</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> buildRequire = <span class="title function_">template</span>(<span class="string">`</span></span><br><span class="line"><span class="string">  var IMPORT_NAME = require(SOURCE);</span></span><br><span class="line"><span class="string">`</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 调用并传入变量，直接生成优雅的 AST</span></span><br><span class="line"><span class="keyword">const</span> ast = <span class="title function_">buildRequire</span>(&#123;</span><br><span class="line">  <span class="attr">IMPORT_NAME</span>: t.<span class="title function_">identifier</span>(<span class="string">&quot;myModule&quot;</span>),</span><br><span class="line">  <span class="attr">SOURCE</span>: t.<span class="title function_">stringLiteral</span>(<span class="string">&quot;my-module&quot;</span>)</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h2 id="十二、-终极一键复制配置模板"><a href="#十二、-终极一键复制配置模板" class="headerlink" title="十二、 终极一键复制配置模板"></a><strong>十二、 终极一键复制配置模板</strong></h2><h3 id="11-1-绝大多数现代工程（Vue-普通-JS-项目，使用打包工具）"><a href="#11-1-绝大多数现代工程（Vue-普通-JS-项目，使用打包工具）" class="headerlink" title="11.1 绝大多数现代工程（Vue&#x2F;普通 JS 项目，使用打包工具）"></a><strong><font color='red'>11.1 绝大多数现代工程（Vue&#x2F;普通 JS 项目，使用打包工具）</font></strong></h3><p><strong><code>babel.config.json</code></strong>：</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;modules&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.38&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;helpers&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;regenerator&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>依赖清单</strong>：</p><figure class="highlight bash"><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">npm i -D @babel/core @babel/preset-env @babel/plugin-transform-runtime</span><br><span class="line">npm i core-js @babel/runtime</span><br></pre></td></tr></table></figure><h3 id="11-2-React-TS-前端主应用项目"><a href="#11-2-React-TS-前端主应用项目" class="headerlink" title="11.2 React + TS 前端主应用项目"></a><strong><font color='red'>11.2 React + TS 前端主应用项目</font></strong></h3><p><strong><code>babel.config.json</code></strong>：</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;presets&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/preset-env&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;modules&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;useBuiltIns&quot;</span><span class="punctuation">:</span> <span class="string">&quot;usage&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;corejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3.38&quot;</span></span><br><span class="line">      <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="punctuation">[</span><span class="string">&quot;@babel/preset-react&quot;</span><span class="punctuation">,</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;runtime&quot;</span><span class="punctuation">:</span> <span class="string">&quot;automatic&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;@babel/preset-typescript&quot;</span></span><br><span class="line">  <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="punctuation">[</span></span><br><span class="line">      <span class="string">&quot;@babel/plugin-transform-runtime&quot;</span><span class="punctuation">,</span></span><br><span class="line">      <span class="punctuation">&#123;</span> <span class="attr">&quot;helpers&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span> <span class="attr">&quot;regenerator&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span> <span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;📚 本指南旨在帮助开发者彻底掌握 Babel 7+ 的工作机制、配置技巧及核心插件系统的使用。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id=&quot;目录&quot;&gt;&lt;a href=&quot;#目录&quot; class=&quot;headerlink&quot; title=&quot;</summary>
      
    
    
    
    <category term="框架与生态" scheme="http://example.com/categories/%E6%A1%86%E6%9E%B6%E4%B8%8E%E7%94%9F%E6%80%81/"/>
    
    
    <category term="javascript" scheme="http://example.com/tags/javascript/"/>
    
    <category term="babel7" scheme="http://example.com/tags/babel7/"/>
    
    <category term="compiler" scheme="http://example.com/tags/compiler/"/>
    
  </entry>
  
  <entry>
    <title>JavaScript 模块化与 Webpack 5：从原理剖析到工程化实战</title>
    <link href="http://example.com/posts/w41d92f5.html"/>
    <id>http://example.com/posts/w41d92f5.html</id>
    <published>2026-02-27T22:11:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<blockquote><p>📚 本总结深入剖析了 JavaScript 模块化规范的底层差异（CommonJS vs ESM），并结合 Webpack 5 及最新的 Webpack CLI 6.x 提供了详尽的实测验证与工程化最佳实践。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80-javascript-%E6%A8%A1%E5%9D%97%E5%8C%96%E8%A7%84%E8%8C%83%E8%AF%A6%E8%A7%A3">模块化规范详解</a></li><li><a href="#%E4%BA%8C-es6-modules-vs-commonjs-%E6%A0%B8%E5%BF%83%E5%8C%BA%E5%88%AB%E8%AF%A6%E8%A7%A3">核心区别：引用 vs 拷贝</a></li><li><a href="#%E4%B8%89-packagejson-%E4%B8%AD%E7%9A%84-type-%E9%85%8D%E7%BD%AE%E8%AF%A6%E8%A7%A3">package.json 配置项</a></li><li><a href="#%E5%9B%9B-webpack-cli-%E7%89%88%E6%9C%AC%E5%BD%B1%E5%93%8D%E5%AE%9E%E6%B5%8B%E7%BB%93%E6%9E%9C">webpack-cli 智能加载</a></li><li><a href="#%E4%BA%94-%E5%AE%9E%E6%B5%8B%E9%AA%8C%E8%AF%81%E8%AE%B0%E5%BD%95">实测验证记录</a></li><li><a href="#%E5%85%AD-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E5%BB%BA%E8%AE%AE%E5%9F%BA%E4%BA%8E%E5%AE%9E%E6%B5%8B">最佳实践建议</a></li><li><a href="#%E4%B8%83-%E9%85%8D%E7%BD%AE%E9%80%89%E6%8B%A9%E6%B5%81%E7%A8%8B%E5%9B%BE">配置选择流程图</a></li><li><a href="#%E5%85%AB-%E5%85%B3%E9%94%AE%E8%A6%81%E7%82%B9%E6%80%BB%E7%BB%93">关键项总结</a></li><li><a href="#%E4%B9%9D-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98-faq">常见问题 FAQ</a></li><li><a href="#%E5%8D%81-%E6%80%BB%E7%BB%93">核心结论总结</a></li></ol><br><h2 id="一、-JavaScript-模块化规范详解"><a href="#一、-JavaScript-模块化规范详解" class="headerlink" title="一、 JavaScript 模块化规范详解"></a><strong>一、 JavaScript 模块化规范详解</strong></h2><h3 id="1-1-CommonJS-CJS"><a href="#1-1-CommonJS-CJS" class="headerlink" title="1.1 CommonJS (CJS)"></a><strong><font color='red'>1.1 CommonJS (CJS)</font></strong></h3><p><strong>使用场景</strong>：Node.js 服务端</p><p><strong>特点</strong>：</p><ul><li>同步加载模块</li><li>运行时加载（值的拷贝）</li><li>每个文件是一个模块</li></ul><p><strong>语法</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导出</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123; <span class="attr">add</span>: <span class="function">(<span class="params">a, b</span>) =&gt;</span> a + b &#125;;</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">count</span> = <span class="number">10</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导入</span></span><br><span class="line"><span class="keyword">const</span> math = <span class="built_in">require</span>(<span class="string">&quot;./math&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; add &#125; = <span class="built_in">require</span>(<span class="string">&quot;./math&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="1-2-AMD-Asynchronous-Module-Definition"><a href="#1-2-AMD-Asynchronous-Module-Definition" class="headerlink" title="1.2 AMD (Asynchronous Module Definition)"></a><strong><font color='red'>1.2 AMD (Asynchronous Module Definition)</font></strong></h3><p><strong>使用场景</strong>：浏览器端</p><p><strong>特点</strong>：</p><ul><li>异步加载模块</li><li>依赖前置，提前执行</li><li>代表库：RequireJS</li></ul><p><strong>语法</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 定义模块</span></span><br><span class="line"><span class="title function_">define</span>([<span class="string">&quot;jquery&quot;</span>, <span class="string">&quot;underscore&quot;</span>], <span class="keyword">function</span> (<span class="params">$, _</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123;</span><br><span class="line">    <span class="attr">method</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;,</span><br><span class="line">  &#125;;</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="built_in">require</span>([<span class="string">&quot;module1&quot;</span>, <span class="string">&quot;module2&quot;</span>], <span class="keyword">function</span> (<span class="params">m1, m2</span>) &#123;</span><br><span class="line">  m1.<span class="title function_">method</span>();</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="1-3-UMD-Universal-Module-Definition"><a href="#1-3-UMD-Universal-Module-Definition" class="headerlink" title="1.3 UMD (Universal Module Definition)"></a><strong><font color='red'>1.3 UMD (Universal Module Definition)</font></strong></h3><p><strong>使用场景</strong>：通用环境（浏览器 + Node.js）</p><p><strong>特点</strong>：</p><ul><li>兼容 CommonJS 和 AMD</li><li>可以在多种环境下运行</li><li>通常用于第三方库</li></ul><p><strong>语法</strong>：</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">function</span> (<span class="params">root, factory</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (<span class="keyword">typeof</span> define === <span class="string">&quot;function&quot;</span> &amp;&amp; define.<span class="property">amd</span>) &#123;</span><br><span class="line">    <span class="comment">// AMD</span></span><br><span class="line">    <span class="title function_">define</span>([], factory);</span><br><span class="line">  &#125; <span class="keyword">else</span> <span class="keyword">if</span> (<span class="keyword">typeof</span> <span class="variable language_">module</span> === <span class="string">&quot;object&quot;</span> &amp;&amp; <span class="variable language_">module</span>.<span class="property">exports</span>) &#123;</span><br><span class="line">    <span class="comment">// CommonJS</span></span><br><span class="line">    <span class="variable language_">module</span>.<span class="property">exports</span> = <span class="title function_">factory</span>();</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="comment">// 浏览器全局变量</span></span><br><span class="line">    root.<span class="property">myModule</span> = <span class="title function_">factory</span>();</span><br><span class="line">  &#125;</span><br><span class="line">&#125;)(<span class="variable language_">this</span>, <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">method</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125; &#125;;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h3 id="1-4-ES6-Modules-ESM-⭐-推荐"><a href="#1-4-ES6-Modules-ESM-⭐-推荐" class="headerlink" title="1.4 ES6 Modules (ESM) ⭐ 推荐"></a><strong><font color='red'>1.4 ES6 Modules (ESM) ⭐ 推荐</font></strong></h3><p><strong>使用场景</strong>：现代浏览器和 Node.js</p><p><strong>特点</strong>：</p><ul><li>JavaScript 官方标准</li><li>静态导入，编译时加载</li><li>支持 Tree Shaking（摇树优化）</li><li>值的引用（而非拷贝）</li></ul><p><strong>语法</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 导出</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> name = <span class="string">&quot;value&quot;</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> (<span class="params"></span>) &#123;&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导入</span></span><br><span class="line"><span class="keyword">import</span> &#123; name, add &#125; <span class="keyword">from</span> <span class="string">&quot;./module.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> defaultExport <span class="keyword">from</span> <span class="string">&quot;./module.js&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> * <span class="keyword">as</span> <span class="variable language_">module</span> <span class="keyword">from</span> <span class="string">&quot;./module.js&quot;</span>;</span><br></pre></td></tr></table></figure><h3 id="1-5-CMD-Common-Module-Definition"><a href="#1-5-CMD-Common-Module-Definition" class="headerlink" title="1.5 CMD (Common Module Definition)"></a><strong><font color='red'>1.5 CMD (Common Module Definition)</font></strong></h3><p><strong>使用场景</strong>：浏览器端（已淘汰）</p><p><strong>特点</strong>：</p><ul><li>SeaJS 推广的规范</li><li>依赖就近，延迟执行</li><li>现在基本不再使用</li></ul><hr><br><h2 id="二、-ES6-Modules-vs-CommonJS-核心区别详解"><a href="#二、-ES6-Modules-vs-CommonJS-核心区别详解" class="headerlink" title="二、 ES6 Modules vs CommonJS 核心区别详解"></a><strong>二、 ES6 Modules vs CommonJS 核心区别详解</strong></h2><h3 id="2-1-值的引用-vs-值的拷贝"><a href="#2-1-值的引用-vs-值的拷贝" class="headerlink" title="2.1 值的引用 vs 值的拷贝"></a><strong><font color='red'>2.1 值的引用 vs 值的拷贝</font></strong></h3><p>这是 <strong>ES6 Modules</strong> 和 <strong>CommonJS</strong> 的一个关键区别。</p><h4 id="1）核心区别"><a href="#1）核心区别" class="headerlink" title="1）核心区别"></a><strong><font color='#10c300'>1）核心区别</font></strong></h4><table><thead><tr><th>模块系统</th><th>导出机制</th><th>特点</th></tr></thead><tbody><tr><td><strong>CommonJS</strong></td><td>值的拷贝 📸</td><td>导出时复制一份值，互不影响</td></tr><tr><td><strong>ES6 Modules</strong></td><td>值的引用（绑定）📹</td><td>导出的是引用，指向同一个值</td></tr></tbody></table><h4 id="2）CommonJS-值的拷贝（拍照片）"><a href="#2）CommonJS-值的拷贝（拍照片）" class="headerlink" title="2）CommonJS - 值的拷贝（拍照片）"></a><strong><font color='#10c300'>2）CommonJS - 值的拷贝（拍照片）</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// counter.js (CommonJS)</span></span><br><span class="line"><span class="keyword">let</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">  count++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getCount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  count, <span class="comment">// 导出的是 count 的拷贝</span></span><br><span class="line">  increment,</span><br><span class="line">  getCount,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><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">// main.js</span></span><br><span class="line"><span class="keyword">const</span> counter = <span class="built_in">require</span>(<span class="string">&quot;./counter.js&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(counter.<span class="property">count</span>); <span class="comment">// 0</span></span><br><span class="line">counter.<span class="title function_">increment</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(counter.<span class="property">count</span>); <span class="comment">// 还是 0 ❌（因为是拷贝，不会更新）</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(counter.<span class="title function_">getCount</span>()); <span class="comment">// 1 ✅（通过函数访问到最新值）</span></span><br></pre></td></tr></table></figure><p><strong>说明</strong>：</p><ul><li><code>counter.count</code> 是导出时的<strong>拷贝值</strong></li><li>内部 <code>count</code> 变化了，但导出的拷贝<strong>不会更新</strong></li><li>就像复印了一份文件，原件改了，复印件不会变</li></ul><h4 id="3）ES6-Modules-值的引用（视频直播）"><a href="#3）ES6-Modules-值的引用（视频直播）" class="headerlink" title="3）ES6 Modules - 值的引用（视频直播）"></a><strong><font color='#10c300'>3）ES6 Modules - 值的引用（视频直播）</font></strong></h4><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">// counter.js (ES6)</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> count = <span class="number">0</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_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">  count++;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">getCount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> count;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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">// main.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; count, increment, getCount &#125; <span class="keyword">from</span> <span class="string">&quot;./counter.js&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 0</span></span><br><span class="line"><span class="title function_">increment</span>();</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 1 ✅（实时更新！）</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">getCount</span>()); <span class="comment">// 1 ✅</span></span><br></pre></td></tr></table></figure><p><strong>说明</strong>：</p><ul><li><code>count</code> 是一个<strong>实时绑定</strong>（引用）</li><li>内部 <code>count</code> 变化，外部<strong>立即看到变化</strong></li><li>就像一个<strong>指针</strong>，始终指向同一个值</li></ul><h4 id="4）形象比喻"><a href="#4）形象比喻" class="headerlink" title="4）形象比喻"></a><strong><font color='#10c300'>4）形象比喻</font></strong></h4><p><strong>CommonJS - 拍照片 📸</strong></p><figure class="highlight plaintext"><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">原始文件（counter.js）：</span><br><span class="line">┌─────────┐</span><br><span class="line">│ count:0 │  → 导出时 → ┌─────────┐ 照片（main.js）</span><br><span class="line">│ ↓       │             │ count:0 │</span><br><span class="line">│ count:1 │             │         │</span><br><span class="line">└─────────┘             └─────────┘</span><br><span class="line">                        照片不会自动更新！</span><br></pre></td></tr></table></figure><p><strong>ES6 Modules - 视频直播 📹</strong></p><figure class="highlight plaintext"><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">原始文件（counter.js）：</span><br><span class="line">┌─────────┐</span><br><span class="line">│ count:0 │  ←═══════ 实时连接 ═══════┐</span><br><span class="line">│ ↓       │                         │</span><br><span class="line">│ count:1 │  ←═══════ 同步更新 ═════════┤ main.js</span><br><span class="line">│ └─────────┘                         │</span><br><span class="line">                                    看到实时值！</span><br></pre></td></tr></table></figure><h4 id="5）为什么这很重要？"><a href="#5）为什么这很重要？" class="headerlink" title="5）为什么这很重要？"></a><strong><font color='#10c300'>5）为什么这很重要？</font></strong></h4><p><strong>1. 状态管理更直观</strong></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">// store.js (ES6)</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> state = &#123; <span class="attr">count</span>: <span class="number">0</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">updateState</span>(<span class="params">newState</span>) &#123;</span><br><span class="line">  state = newState;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; state &#125; <span class="keyword">from</span> <span class="string">&quot;./store.js&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(state); <span class="comment">// ✅ 始终是最新的 state</span></span><br></pre></td></tr></table></figure><p><strong>2. 避免常见陷阱</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ CommonJS 的常见错误</span></span><br><span class="line"><span class="keyword">const</span> config = <span class="built_in">require</span>(<span class="string">&quot;./config&quot;</span>);</span><br><span class="line">config.<span class="title function_">setDebug</span>(<span class="literal">true</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(config.<span class="property">isDebug</span>); <span class="comment">// 还是 false ❌（拷贝不更新）</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ ES6 的正确行为</span></span><br><span class="line"><span class="keyword">import</span> &#123; isDebug &#125; <span class="keyword">from</span> <span class="string">&quot;./config.js&quot;</span>;</span><br><span class="line"><span class="title function_">setDebug</span>(<span class="literal">true</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(isDebug); <span class="comment">// true ✅（引用自动更新）</span></span><br></pre></td></tr></table></figure><p><strong>3. 注意：ES6 模块导入的值是只读的</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// counter.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">let</span> count = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; count &#125; <span class="keyword">from</span> <span class="string">&quot;./counter.js&quot;</span>;</span><br><span class="line">count = <span class="number">10</span>; <span class="comment">// ❌ TypeError: Assignment to constant variable</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 正确做法：通过导出的函数修改</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">setCount</span>(<span class="params">value</span>) &#123;</span><br><span class="line">  count = value; <span class="comment">// ✅ 在模块内部可以修改</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="6）总结对比"><a href="#6）总结对比" class="headerlink" title="6）总结对比"></a><strong><font color='#10c300'>6）总结对比</font></strong></h4><table><thead><tr><th>特性</th><th>CommonJS</th><th>ES6 Modules</th></tr></thead><tbody><tr><td><strong>导出机制</strong></td><td>值的拷贝 📸</td><td>值的引用 📹</td></tr><tr><td><strong>更新机制</strong></td><td>不会自动更新</td><td>实时同步</td></tr><tr><td><strong>外部修改</strong></td><td>可以修改导入的对象</td><td>只读，不能修改</td></tr><tr><td><strong>适合场景</strong></td><td>服务端同步加载</td><td>现代应用，静态分析</td></tr></tbody></table><hr><h3 id="2-2-Tree-Shaking（摇树优化）详解"><a href="#2-2-Tree-Shaking（摇树优化）详解" class="headerlink" title="2.2 Tree Shaking（摇树优化）详解"></a><strong><font color='red'>2.2 Tree Shaking（摇树优化）详解</font></strong></h3><h4 id="1）什么是-Tree-Shaking？"><a href="#1）什么是-Tree-Shaking？" class="headerlink" title="1）什么是 Tree Shaking？"></a><strong><font color='#10c300'>1）什么是 Tree Shaking？</font></strong></h4><p><strong>Tree Shaking</strong> 是一个术语，通常用于描述<strong>移除 JavaScript 代码中未使用（dead code）的过程</strong>。</p><p><strong>形象比喻</strong>：</p><ul><li>🌳 <strong>树</strong> &#x3D; 你的整个代码库</li><li>🍃 <strong>绿叶</strong> &#x3D; 实际使用的代码（活代码）</li><li>🍂 <strong>枯叶</strong> &#x3D; 未使用的代码（死代码）</li></ul><p><strong>摇树（Shaking）</strong> &#x3D; 摇动这棵树，让<strong>枯叶（未使用的代码）掉落</strong>，只保留<strong>绿叶（使用的代码）</strong></p><h4 id="2）Tree-Shaking-的作用"><a href="#2）Tree-Shaking-的作用" class="headerlink" title="2）Tree Shaking 的作用"></a><strong><font color='#10c300'>2）Tree Shaking 的作用</font></strong></h4><p><strong>优化前（没有 Tree Shaking）</strong>：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// math.js - 导出多个函数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">subtract</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a - b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a * b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">divide</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a / b;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// main.js - 只使用了 add</span></span><br><span class="line"><span class="keyword">import</span> &#123; add &#125; <span class="keyword">from</span> <span class="string">&quot;./math.js&quot;</span>;</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>));</span><br></pre></td></tr></table></figure><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="comment">// bundle.js - 包含所有函数（浪费）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">subtract</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a - b;</span><br><span class="line">&#125; <span class="comment">// ❌ 未使用但被打包</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">multiply</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a * b;</span><br><span class="line">&#125; <span class="comment">// ❌ 未使用但被打包</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">divide</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a / b;</span><br><span class="line">&#125; <span class="comment">// ❌ 未使用但被打包</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>));</span><br></pre></td></tr></table></figure><p><strong>优化后（有 Tree Shaking）</strong>：</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">// bundle.js - 只包含使用的函数（优化）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">add</span>(<span class="params">a, b</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> a + b;</span><br><span class="line">&#125; <span class="comment">// ✅ 使用了，保留</span></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="title function_">add</span>(<span class="number">1</span>, <span class="number">2</span>));</span><br></pre></td></tr></table></figure><p><strong>效果</strong>：</p><ul><li>📦 减小打包体积（可能减少 50%+ 的代码）</li><li>⚡ 加快加载速度</li><li>🚀 提升性能</li></ul><h4 id="3）Tree-Shaking-的工作原理"><a href="#3）Tree-Shaking-的工作原理" class="headerlink" title="3）Tree Shaking 的工作原理"></a><strong><font color='#10c300'>3）Tree Shaking 的工作原理</font></strong></h4><p><strong>1. 依赖 ES6 Modules（ESM）</strong></p><p>Tree Shaking <strong>只对 ES6 模块（import&#x2F;export）有效</strong>，对 CommonJS（require&#x2F;module.exports）<strong>无效</strong>。</p><p><strong>为什么？</strong></p><table><thead><tr><th>特性</th><th>ES6 Modules</th><th>CommonJS</th></tr></thead><tbody><tr><td><strong>加载时机</strong></td><td>编译时（静态）</td><td>运行时（动态）</td></tr><tr><td><strong>结构</strong></td><td>静态结构，可分析</td><td>动态结构，不可预测</td></tr><tr><td><strong>Tree Shaking</strong></td><td>✅ 支持</td><td>❌ 不支持</td></tr></tbody></table><p><strong>2. 静态分析</strong></p><p>Webpack 在<strong>编译阶段</strong>会分析代码的导入导出关系，标记哪些导出被使用了，并删除未使用的导出。</p><h4 id="4）如何启用-Tree-Shaking"><a href="#4）如何启用-Tree-Shaking" class="headerlink" title="4）如何启用 Tree Shaking"></a><strong><font color='#10c300'>4）如何启用 Tree Shaking</font></strong></h4><p><strong>在 webpack 5 中（自动启用）</strong></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">// webpack.config.js</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">mode</span>: <span class="string">&quot;production&quot;</span>, <span class="comment">// 生产模式自动启用 Tree Shaking</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong>package.json 配置（可选）</strong></p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;my-project&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;sideEffects&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">false</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><h4 id="5）sideEffects-配置的影响对比"><a href="#5）sideEffects-配置的影响对比" class="headerlink" title="5）sideEffects 配置的影响对比"></a><strong><font color='#10c300'>5）sideEffects 配置的影响对比</font></strong></h4><table><thead><tr><th>配置</th><th>Tree Shaking 粒度</th><th>删除范围</th></tr></thead><tbody><tr><td><strong>未设置 sideEffects: false</strong></td><td>成员级别</td><td>只删除文件内未使用的导出</td></tr><tr><td><strong>设置 sideEffects: false</strong></td><td>文件级别 + 成员级别</td><td>可删除整个未使用的文件</td></tr></tbody></table><h4 id="6）Tree-Shaking-的限制与最佳实践"><a href="#6）Tree-Shaking-的限制与最佳实践" class="headerlink" title="6）Tree Shaking 的限制与最佳实践"></a><strong><font color='#10c300'>6）Tree Shaking 的限制与最佳实践</font></strong></h4><p>✅ <strong>推荐做法</strong>：使用 ES6 模块、生产模式构建、避免副作用代码、使用支持 Tree Shaking 的库。<br>❌ <strong>避免</strong>：混用 CommonJS 和 ESM、顶层副作用代码、默认导出整个大对象。</p><h4 id="7）生产级-sideEffects-标准排除清单"><a href="#7）生产级-sideEffects-标准排除清单" class="headerlink" title="7）生产级 sideEffects 标准排除清单"></a><strong><span style='color:#10c300'>7）生产级 sideEffects 标准排除清单</span></strong></h4><p>这是解决 Tree Shaking 误删问题的标准化 <code>package.json</code> 配置，建议直接作为模板使用：</p><figure class="highlight json"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// package.json</span></span><br><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;sideEffects&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line">    <span class="string">&quot;*.css&quot;</span><span class="punctuation">,</span> <span class="comment">// 确保 CSS 不被误删</span></span><br><span class="line">    <span class="string">&quot;*.less&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;*.scss&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="string">&quot;./src/polyfills.js&quot;</span><span class="punctuation">,</span> <span class="comment">// Polyfill 全局初始化</span></span><br><span class="line">    <span class="string">&quot;./src/global.js&quot;</span><span class="punctuation">,</span> <span class="comment">// 全局变量或插件初始化</span></span><br><span class="line">    <span class="string">&quot;**/plugins/*.js&quot;</span> <span class="comment">// 通用插件目录</span></span><br><span class="line">  <span class="punctuation">]</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>数组配置的“潜规则”</strong>：<br>当你使用数组列出有副作用的文件时，<strong>没有出现在清单中的所有其他 JS 文件都会被 Webpack 默认为“绝对纯净”</strong>（即 <code>sideEffects: false</code>）。</p><p><strong>这意味着</strong>：如果一个 JS 文件不在清单里，即使它内部写了 <code>console.log</code> 或修改了全局变量，只要它的 <code>export</code> 成员未被其他地方使用，Webpack 就会<strong>直接跳过并物理删除整个文件</strong>，其中的副作用逻辑也会随之消失。</p><p><strong>建议</strong>：</p><ol><li>养成<strong>纯净模块化</strong>习惯：JS 文件应只负责导出功能，不应在顶层执行逻辑。</li><li>如果某些历史遗留文件确实有“只需加载即生效”的代码，请务必将其加入上述清单。</li></ol></blockquote><h4 id="8）常见误区：与-“type”-“module”-的关系"><a href="#8）常见误区：与-“type”-“module”-的关系" class="headerlink" title="8）常见误区：与 “type”: “module” 的关系"></a><strong><font color='#10c300'>8）常见误区：与 “type”: “module” 的关系</font></strong></h4><p><strong>真相</strong>：Tree Shaking 与 package.json 的 <code>&quot;type&quot;</code> 无关。它只取决于源代码是否使用了 <code>import/export</code> 语法。</p><hr><br><h2 id="三、-package-json-中的-“type”-配置详解"><a href="#三、-package-json-中的-“type”-配置详解" class="headerlink" title="三、 package.json 中的 “type” 配置详解"></a><strong>三、 package.json 中的 “type” 配置详解</strong></h2><h3 id="3-1-初始化项目与目录结构"><a href="#3-1-初始化项目与目录结构" class="headerlink" title="3.1 初始化项目与目录结构"></a><strong><font color='red'>3.1 初始化项目与目录结构</font></strong></h3><p>执行以下命令初始化项目并安装 Webpack 相关依赖：</p><figure class="highlight bash"><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">npm init -y</span><br><span class="line">npm i webpack webpack-cli -D</span><br></pre></td></tr></table></figure><p>初始化后的推荐工程目录结构如下：</p><figure class="highlight text"><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">webpack_code</span><br><span class="line">├── src # 源代码目录</span><br><span class="line">│   ├── js # JS 文件夹</span><br><span class="line">│   │   ├── count.js</span><br><span class="line">│   │   └── sum.js</span><br><span class="line">│   └── main.js # 项目主入口文件</span><br><span class="line">├── package.json # 项目配置文件</span><br><span class="line">└── webpack.config.js # Webpack 配置文件</span><br></pre></td></tr></table></figure><p><strong><code>webpack.config.js</code></strong></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> path = <span class="built_in">require</span>(<span class="string">&quot;path&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = &#123;</span><br><span class="line">  <span class="comment">// 入口</span></span><br><span class="line">  <span class="attr">entry</span>: <span class="string">&quot;./src/main.js&quot;</span>,</span><br><span class="line">  <span class="comment">// 输出</span></span><br><span class="line">  <span class="attr">output</span>: &#123;</span><br><span class="line">    <span class="comment">// __dirname nodejs的变量，表示当前文件的目录</span></span><br><span class="line">    <span class="attr">path</span>: path.<span class="title function_">resolve</span>(__dirname, <span class="string">&quot;dist&quot;</span>), <span class="comment">// 绝对路径</span></span><br><span class="line">    <span class="attr">filename</span>: <span class="string">&quot;js/main.js&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 加载器</span></span><br><span class="line">  <span class="attr">module</span>: &#123;</span><br><span class="line">    <span class="attr">rules</span>: [</span><br><span class="line">      <span class="comment">// loader的配置(执行顺序是 从下往上 或 从右到左)</span></span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 插件</span></span><br><span class="line">  <span class="attr">plugins</span>: [],</span><br><span class="line">  <span class="comment">// 模式</span></span><br><span class="line">  <span class="attr">mode</span>: <span class="string">&quot;production&quot;</span>,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><p><strong><code>运行</code></strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx webpack</span><br></pre></td></tr></table></figure><h3 id="3-2-npx-与-xxx-cli-的关系解析"><a href="#3-2-npx-与-xxx-cli-的关系解析" class="headerlink" title="3.2 npx 与 xxx-cli 的关系解析"></a><strong><font color='red'>3.2 npx 与 xxx-cli 的关系解析</font></strong></h3><p>在现代前端工程化中，<code>npx</code> 和 <code>xxx-cli</code>（如 <code>webpack-cli</code>）是相辅相成的协作关系：</p><ul><li><strong><code>xxx-cli</code> (工具箱)</strong>：是功能的实体。例如 <code>webpack-cli</code> 包含了解析命令行参数、读取配置文件、调用 Webpack 内核的核心代码。没有它，Webpack 就无法通过命令行运行。</li><li><strong><code>npx</code> (通行证)</strong>：是命令的执行者。它的核心作用是<strong>自动寻找并执行</strong>安装在项目本地（<code>node_modules/.bin</code>）的二进制命令。</li></ul><p><strong>协作逻辑</strong>：</p><ol><li><strong>安装</strong>：我们将 <code>webpack-cli</code> 安装为项目的本地 <code>devDependencies</code>（非全局安装）。</li><li><strong>寻找</strong>：当你输入 <code>npx webpack</code> 时，<code>npx</code> 会在当前项目的 <code>node_modules</code> 中精准定位到 <code>webpack-cli</code> 的执行入口。</li><li><strong>执行</strong>：<code>npx</code> 唤起 <code>webpack-cli</code>，后者开始读取配置并完成打包任务。</li></ol><p><strong>优势</strong>：这种模式避免了全局安装带来的版本冲突，确保每个项目使用的都是自己 package.json 中指定的特定版本工具。<br><br></p><h3 id="3-3-配置选项一览"><a href="#3-3-配置选项一览" class="headerlink" title="3.3 配置选项一览"></a><strong><font color='red'>3.3 配置选项一览</font></strong></h3><table><thead><tr><th>type 配置值</th><th>.js 处理方式</th><th>.mjs 文件</th><th>.cjs 文件</th></tr></thead><tbody><tr><td><strong>不设置</strong></td><td><strong>CommonJS</strong></td><td>ESM</td><td>CommonJS</td></tr><tr><td><strong>“commonjs”</strong></td><td>CommonJS（强制）</td><td>ESM</td><td>CommonJS</td></tr><tr><td><strong>“module”</strong></td><td>ESM（严格模式）</td><td>ESM</td><td>CommonJS</td></tr></tbody></table><h3 id="3-4-三种模式详解"><a href="#3-4-三种模式详解" class="headerlink" title="3.4 三种模式详解"></a><strong><font color='red'>3.4 三种模式详解</font></strong></h3><h4 id="1）不设置-type"><a href="#1）不设置-type" class="headerlink" title="1）不设置 type"></a><strong><font color='#10c300'>1）不设置 type</font></strong></h4><p><code>.js</code> 文件默认为 CommonJS，但由于最新的 <strong>webpack-cli 6.x</strong> 智能加载机制，配置文件依然可以使用 <code>export default</code>。</p><h4 id="2）设置-“type”-“commonjs”-（默认行为）"><a href="#2）设置-“type”-“commonjs”-（默认行为）" class="headerlink" title="2）设置 “type”: “commonjs” （默认行为）"></a><strong><font color='#10c300'>2）设置 “type”: “commonjs” （默认行为）</font></strong></h4><p>npm init -y默认生成<code>type&quot;: &quot;commonjs</code>。强制 <code>.js</code> 文件为 CommonJS。在此模式下，<code>webpack.config.js</code> <strong>不能</strong>使用 ESM 语法。</p><h4 id="3）设置-“type”-“module”⭐⭐⭐⭐⭐"><a href="#3）设置-“type”-“module”⭐⭐⭐⭐⭐" class="headerlink" title="3）设置 “type”: “module”⭐⭐⭐⭐⭐"></a><strong><font color='#10c300'>3）设置 “type”: “module”</font></strong>⭐⭐⭐⭐⭐</h4><p>这是最推荐的配置。后面比较新的插件有的可能就是<strong>纯 ESM 模块</strong> <code>.js</code> 文件遵循 ESM 严格模式。<strong>注意</strong>：在此模式下，<code>import</code> 必须携带完整的文件扩展名（如 <code>.js</code>）。</p><hr><br><h2 id="四、-webpack-cli-版本影响（实测结果）"><a href="#四、-webpack-cli-版本影响（实测结果）" class="headerlink" title="四、 webpack-cli 版本影响（实测结果）"></a><strong>四、 webpack-cli 版本影响（实测结果）</strong></h2><h3 id="4-1-webpack-cli-6-x-智能加载机制-🎉"><a href="#4-1-webpack-cli-6-x-智能加载机制-🎉" class="headerlink" title="4.1 webpack-cli 6.x 智能加载机制 🎉"></a><strong><font color='red'>4.1 webpack-cli 6.x 智能加载机制 🎉</font></strong></h3><table><thead><tr><th>package.json 配置</th><th>config 可用语法</th><th>源代码 import</th><th>实测结果</th></tr></thead><tbody><tr><td><strong>不设置 type</strong> ⭐</td><td><strong>CJS ✅ &#x2F; ESM ✅</strong></td><td>不需要 .js ✅</td><td>✅ 通过</td></tr><tr><td><strong>“type”: “commonjs”</strong></td><td>CJS ✅ &#x2F; ESM ❌</td><td>不需要 .js ✅</td><td>⚠️ ESM 报错</td></tr><tr><td><strong>“type”: “module”</strong></td><td>CJS ❌ &#x2F; ESM ✅</td><td><strong>必须加 .js</strong> ⚠️</td><td>⚠️ import 受限</td></tr></tbody></table><h3 id="4-2-智能加载原理解析"><a href="#4-2-智能加载原理解析" class="headerlink" title="4.2 智能加载原理解析"></a><strong><font color='red'>4.2 智能加载原理解析</font></strong></h3><p>webpack-cli 6.x 会先尝试 <code>require</code> 配置文件，如果遇到 ESM 语法抛出的报错，则会自动捕获并改用动态 <code>import()</code> 加载。这一特性让“不设置 type”成为了灵活性最高的选择。</p><hr><br><h2 id="五、-实测验证记录"><a href="#五、-实测验证记录" class="headerlink" title="五、 实测验证记录"></a><strong>五、 实测验证记录</strong></h2><h3 id="5-1-测试-1：不设置-type-export-default-✅"><a href="#5-1-测试-1：不设置-type-export-default-✅" class="headerlink" title="5.1 测试 1：不设置 type + export default ✅"></a><strong><font color='red'>5.1 测试 1：不设置 type + export default ✅</font></strong></h3><p>Node.js 环境下成功编译。说明 webpack-cli 6.x 成功兼容了不设 type 时的 ESM 配置。</p><h3 id="5-2-测试-2：type-“commonjs”-export-default-❌"><a href="#5-2-测试-2：type-“commonjs”-export-default-❌" class="headerlink" title="5.2 测试 2：type: “commonjs” + export default ❌"></a><strong><font color='red'>5.2 测试 2：type: “commonjs” + export default ❌</font></strong></h3><p>抛出 <code>SyntaxError: Unexpected token &#39;export&#39;</code>。因为显式声明为 CJS 后，尝试使用 ESM 语法会被捕获为非法。</p><h3 id="5-3-测试-3：type-“module”-export-default-⚠️"><a href="#5-3-测试-3：type-“module”-export-default-⚠️" class="headerlink" title="5.3 测试 3：type: “module” + export default ⚠️"></a><strong><font color='red'>5.3 测试 3：type: “module” + export default ⚠️</font></strong></h3><p>虽然配置文件通过，但源代码中的 <code>import</code> 因为没有写 <code>.js</code> 后缀而导致路径解析失败。</p><hr><br><h2 id="六、-最佳实践建议（基于实测）"><a href="#六、-最佳实践建议（基于实测）" class="headerlink" title="六、 最佳实践建议（基于实测）"></a><strong>六、 最佳实践建议（基于实测）</strong></h2><h3 id="6-1-推荐配置：五星级方案-⭐⭐⭐⭐⭐"><a href="#6-1-推荐配置：五星级方案-⭐⭐⭐⭐⭐" class="headerlink" title="6.1 推荐配置：五星级方案 ⭐⭐⭐⭐⭐"></a><strong><font color='red'>6.1 推荐配置：五星级方案 ⭐⭐⭐⭐⭐</font></strong></h3><ol><li><strong>package.json</strong>: 不设置 <code>type</code>。</li><li><strong>webpack.config.js</strong>: 使用 ESM 语法（<code>import path from &#39;path&#39;</code> 和 <code>export default</code>）。</li><li><strong>源代码</strong>: 使用标准的 ES6 模块（<code>import count from &quot;./js/count&quot;</code>），享受无需后缀的简洁感。</li></ol><hr><br><h2 id="七、-配置选择流程图"><a href="#七、-配置选择流程图" class="headerlink" title="七、 配置选择流程图"></a><strong>七、 配置选择流程图</strong></h2><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">需要配置 package.json 的 type 吗？</span><br><span class="line">    │</span><br><span class="line">    ├─→ 是否使用 webpack-cli 6.x？</span><br><span class="line">    │   ├─→ 是 → 推荐不设置 type ⭐⭐⭐⭐⭐ (灵活、简洁)</span><br><span class="line">    │   └─→ 否 → 配置文件必须用 CommonJS</span><br><span class="line">    │</span><br><span class="line">    ├─→ 是否纯 ESM 项目（Node.js 原生）？</span><br><span class="line">    │   ├─→ 是 → &quot;type&quot;: &quot;module&quot; (须加 .js 后缀)</span><br><span class="line">    │   └─→ 否 → 不设置 type ⭐ 最推荐</span><br></pre></td></tr></table></figure><hr><br><h2 id="八、-关键要点总结"><a href="#八、-关键要点总结" class="headerlink" title="八、 关键要点总结"></a><strong>八、 关键要点总结</strong></h2><ol><li><strong>默认最强</strong>：不设置 <code>type</code> 是目前最灵活、兼容性最好的方案。</li><li><strong>后缀规则</strong>：<code>.mjs</code> 始终为 ESM，<code>.cjs</code> 始终为 CommonJS。</li><li><strong>智能 cli</strong>：webpack-cli 6.x 的自动切换机制是简化工程配置的关键。</li><li><strong>Tree Shaking</strong>：只看代码语法，不看 <code>package.json</code> 配置。</li></ol><hr><br><h2 id="九、-常见问题-FAQ"><a href="#九、-常见问题-FAQ" class="headerlink" title="九、 常见问题 FAQ"></a><strong>九、 常见问题 FAQ</strong></h2><h3 id="Q1-不设置-type-能使用-Tree-Shaking-吗？"><a href="#Q1-不设置-type-能使用-Tree-Shaking-吗？" class="headerlink" title="Q1: 不设置 type 能使用 Tree Shaking 吗？"></a><strong><font color='red'>Q1: 不设置 type 能使用 Tree Shaking 吗？</font></strong></h3><p><strong>✅ 完全可以！</strong> 这是一个常见的认知误区。Tree Shaking 取决于你的源代码是否使用了 <code>import/export</code> 静态分析，只要代码是 ESM 风格且开启了 <code>production</code> 模式，打包器就能进行优化。</p><hr><br><h2 id="十、-总结"><a href="#十、-总结" class="headerlink" title="十、 总结"></a><strong>十、 总结</strong></h2><h3 id="10-1-核心结论"><a href="#10-1-核心结论" class="headerlink" title="10.1 核心结论"></a><strong><font color='red'>10.1 核心结论</font></strong></h3><p>在这场模块化演进中，<strong>webpack-cli 6.x</strong> 通过智能加载弥合了 CJS 与 ESM 的配置鸿沟。<strong>“不设置 type + 源代码使用 ESM”</strong> 既享受了现代化的开发体验，又避免了原生 ESM 严格的后缀限制。</p><p><strong>这就是 Webpack 5 的终极工程化实践！</strong> 🎯</p>]]></content>
    
    
      
      
    <summary type="html">&lt;blockquote&gt;
&lt;p&gt;📚 本总结深入剖析了 JavaScript 模块化规范的底层差异（CommonJS vs ESM），并结合 Webpack 5 及最新的 Webpack CLI 6.x 提供了详尽的实测验证与工程化最佳实践。&lt;/p&gt;
&lt;/blockquote&gt;
</summary>
      
    
    
    
    <category term="开发工具" scheme="http://example.com/categories/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="webpack" scheme="http://example.com/tags/webpack/"/>
    
    <category term="javascript" scheme="http://example.com/tags/javascript/"/>
    
    <category term="modules" scheme="http://example.com/tags/modules/"/>
    
  </entry>
  
  <entry>
    <title>WSL2安装与使用指南</title>
    <link href="http://example.com/posts/wsl2534guide6.html"/>
    <id>http://example.com/posts/wsl2534guide6.html</id>
    <published>2026-02-23T17:53:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、WSL2安装与卸载"><a href="#一、WSL2安装与卸载" class="headerlink" title="一、WSL2安装与卸载"></a><strong><font color='red'>一、WSL2安装与卸载</font></strong></h3><h4 id="1-1、安装WSL"><a href="#1-1、安装WSL" class="headerlink" title="1-1、安装WSL"></a><strong><font color='#10c300'>1-1、安装WSL</font></strong></h4><h5 id="一键安装（推荐）"><a href="#一键安装（推荐）" class="headerlink" title="一键安装（推荐）"></a><strong><font color='cornflowerblue'>一键安装（推荐）</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl --install</span><br></pre></td></tr></table></figure><p>此命令会自动完成：</p><ul><li>启用必要的 Windows 功能</li><li>安装最新的 Linux 内核</li><li><strong>默认安装 Ubuntu 发行版</strong></li></ul><p><strong>安装完成后需要重启计算机</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260120235112242.png" alt="image-20260120235112242"></p><br><h4 id="1-2、WSL常用命令"><a href="#1-2、WSL常用命令" class="headerlink" title="1-2、WSL常用命令"></a><strong><font color='#10c300'>1-2、WSL常用命令</font></strong></h4><table><thead><tr><th align="left">命令</th><th align="left">说明</th></tr></thead><tbody><tr><td align="left"><code>wsl</code></td><td align="left">启动默认发行版</td></tr><tr><td align="left"><code>wsl --list --online</code></td><td align="left">查看可安装的发行版</td></tr><tr><td align="left"><code>wsl --list</code></td><td align="left">查看已安装的发行版名称</td></tr><tr><td align="left"><code>wsl --list --verbose</code></td><td align="left">显示已安装的发行版名称、运行状态和WSL版本</td></tr><tr><td align="left"><code>wsl -d Ubuntu-22.04</code></td><td align="left">启动指定发行版</td></tr><tr><td align="left"><code>wsl --shutdown</code></td><td align="left">关闭所有正在运行的发行版</td></tr><tr><td align="left"><code>wsl --update</code></td><td align="left">更新 WSL</td></tr><tr><td align="left"><code>wsl --version</code></td><td align="left">查看 WSL 版本</td></tr><tr><td align="left"><code>wsl --set-default Ubuntu-22.04</code></td><td align="left">设置默认发行版</td></tr><tr><td align="left"><code>wsl --set-version Ubuntu-22.04 2</code></td><td align="left">转换发行版版本（WSL 1 ↔ WSL 2）</td></tr></tbody></table><br><h4 id="1-4、注意事项"><a href="#1-4、注意事项" class="headerlink" title="1-4、注意事项"></a><strong><font color='#10c300'>1-4、注意事项</font></strong></h4><ul><li>安装 WSL 需要管理员权限</li><li>卸载发行版会<strong>完全删除</strong>所有数据，卸载前请备份</li><li>WSL 2 性能更好，推荐使用</li></ul><br><h4 id="1-4、常见问题排查"><a href="#1-4、常见问题排查" class="headerlink" title="1-4、常见问题排查"></a><strong><font color='#10c300'>1-4、常见问题排查</font></strong></h4><h5 id="❌-问题1：计算机不支持WSL2"><a href="#❌-问题1：计算机不支持WSL2" class="headerlink" title="❌ 问题1：计算机不支持WSL2"></a><strong><font color='cornflowerblue'>❌ 问题1：计算机不支持WSL2</font></strong></h5><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260123111656607.png" alt="image-20260123111656607"></p><p>在powershell下输入wsl -l -v 也不显示版本</p><p>首先确保Hyper-v和适用于linux的windows子系统以及虚拟机平台是开启的</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260123111934330.png" alt="image-20260123111934330"></p><p>如果这些都没问题，在powershell（管理员模式）下输入如下命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bcdedit /enum | findstr -i hypervisorlaunchtype</span><br></pre></td></tr></table></figure><p>应该会显示off，那就说明有问题，那就再输入</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">bcdedit /set hypervisorlaunchtype Auto</span><br></pre></td></tr></table></figure><p>即可解决</p><br><hr><h3 id="二、安装AlmaLinux-9系统"><a href="#二、安装AlmaLinux-9系统" class="headerlink" title="二、安装AlmaLinux-9系统"></a><strong><font color='red'>二、安装AlmaLinux-9系统</font></strong></h3><p>一般企业服务器都是用的**<code>CentOS Linux</code><strong>系统，由于其2021 年底终止更新，于是乎企业环境中出现了</strong>CentOS 替代方案** <code>Rocky Linux</code>、<code>AlmaLinux</code>等等</p><h4 id="2-1、AlmaLinux-9包管理工具"><a href="#2-1、AlmaLinux-9包管理工具" class="headerlink" title="2-1、AlmaLinux-9包管理工具"></a><strong><span style='color:#10c300'>2-1、AlmaLinux-9包管理工具</span></strong></h4><p>默认使用 <strong>dnf（Dandified Yum）</strong>，<br>它是 <strong>yum 的下一代版本</strong>，在语法上几乎完全兼容 <code>yum</code>。</p><p>换句话说：</p><blockquote><p>在 AlmaLinux-9 上，<code>yum</code> 命令仍然可以用，但底层实际上调用的就是 <code>dnf</code>。</p></blockquote><h4 id="2-2、卸载Ubuntu-Server"><a href="#2-2、卸载Ubuntu-Server" class="headerlink" title="2-2、卸载Ubuntu Server "></a><strong><font color='#10c300'>2-2、卸载Ubuntu Server </font></strong></h4><p>在 **PowerShell（管理员）**中执行：</p><p><strong>1️⃣ 查看已安装的发行版</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--list</span> <span class="literal">--verbose</span></span><br></pre></td></tr></table></figure><p><strong>2️⃣ 注销并卸载（特定发行版）</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--unregister</span> Ubuntu</span><br></pre></td></tr></table></figure><p>根据你安装的具体发行版名称，例如：</p><figure class="highlight powershell"><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">wsl <span class="literal">--unregister</span> Ubuntu<span class="literal">-22</span>.<span class="number">04</span></span><br><span class="line">wsl <span class="literal">--unregister</span> Debian</span><br></pre></td></tr></table></figure><p>⚠️ <strong>注意</strong>：这会删除该发行版的所有数据！</p><br><p><strong><font color='cornflowerblue'>验证卸载</font></strong></p><figure class="highlight powershell"><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">wsl <span class="literal">--list</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看 WSL 版本</span></span><br><span class="line">wsl <span class="literal">--version</span></span><br></pre></td></tr></table></figure><p>如果显示为空或报错，说明卸载成功。</p><h4 id="2-3、开始安装"><a href="#2-3、开始安装" class="headerlink" title="2-3、开始安装"></a><strong><span style='color:#10c300'>2-3、开始安装</span></strong></h4><p>在 PowerShell 中执行：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">--install</span> AlmaLinux<span class="literal">-9</span></span><br></pre></td></tr></table></figure><p><strong>安装过程：</strong></p><ul><li>系统会开始下载AlmaLinux镜像（文件较大，请耐心等待）</li><li>显示进度条：<code>正在下载: AlmaLinux OS 9 [====70.3%====]</code></li><li>下载完成后开始安装</li><li>安装完成后会显示：<code>已成功安装分发。可以通过 &quot;wsl.exe -d AlmaLinux-9&quot; 启动它</code></li></ul><h4 id="2-4、安装完成标志"><a href="#2-4、安装完成标志" class="headerlink" title="2-4、安装完成标志"></a><strong><span style='color:#10c300'>2-4、安装完成标志</span></strong></h4><figure class="highlight plaintext"><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">正在安装: AlmaLinux OS 9</span><br><span class="line">已成功安装分发。可以通过 &quot;wsl.exe -d AlmaLinux-9&quot; 启动它</span><br><span class="line">正在启动 AlmaLinux-9...</span><br><span class="line">Please create a default UNIX user account.</span><br><span class="line">Enter new UNIX username:</span><br></pre></td></tr></table></figure><h4 id="2-5、初始化系统"><a href="#2-5、初始化系统" class="headerlink" title="2-5、初始化系统"></a><strong><span style='color:#10c300'>2-5、初始化系统</span></strong></h4><h5 id="第一步：启动AlmaLinux"><a href="#第一步：启动AlmaLinux" class="headerlink" title="第一步：启动AlmaLinux"></a><strong><span style='color:cornflowerblue'>第一步：启动AlmaLinux</span></strong></h5><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">-d</span> AlmaLinux<span class="literal">-9</span></span><br></pre></td></tr></table></figure><h5 id="第二步：创建UNIX用户账户"><a href="#第二步：创建UNIX用户账户" class="headerlink" title="第二步：创建UNIX用户账户"></a><strong><span style='color:cornflowerblue'>第二步：创建UNIX用户账户</span></strong></h5><p>系统会提示输入以下信息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Please create a default UNIX user account. The username does not need to match your Windows username.</span><br><span class="line">For more information visit: https://aka.ms/wslusers</span><br><span class="line">Enter new UNIX username:</span><br></pre></td></tr></table></figure><p><strong>输入步骤：</strong></p><ol><li><p><strong>输入用户名</strong>（例如：<code>admin</code>）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Enter new UNIX username: admin</span><br></pre></td></tr></table></figure></li><li><p><strong>设置密码</strong>（输入时不会显示）</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">New password:</span><br></pre></td></tr></table></figure></li><li><p><strong>重复确认密码</strong></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Retype new password:</span><br></pre></td></tr></table></figure></li></ol><h5 id="第三步：初始化完成"><a href="#第三步：初始化完成" class="headerlink" title="第三步：初始化完成"></a><strong><span style='color:cornflowerblue'>第三步：初始化完成</span></strong></h5><p>输入完成后，系统会进入bash shell，显示类似：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">[admin@host ~]$</span><br></pre></td></tr></table></figure><p>✅ <strong>AlmaLinux初始化完成！</strong></p><h4 id="3-3、常用命令"><a href="#3-3、常用命令" class="headerlink" title="3-3、常用命令"></a><strong><span style='color:cornflowerblue'>3-3、常用命令</span></strong></h4><figure class="highlight bat"><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">dnf list installed  // 列出所有已安装的包</span><br><span class="line">rpm -qa --last | head -<span class="number">20</span>  // 按安装时间排序</span><br><span class="line">dnf list installed | grep &lt;关键词&gt;  // 搜索特定软件</span><br><span class="line">dnf list installed | wc -l // 统计已安装软件数量</span><br></pre></td></tr></table></figure><hr><h3 id="三、WSL2基础操作"><a href="#三、WSL2基础操作" class="headerlink" title="三、WSL2基础操作"></a><strong><font color='red'>三、WSL2基础操作</font></strong></h3><h4 id="3-1、查看IP地址"><a href="#3-1、查看IP地址" class="headerlink" title="3-1、查看IP地址"></a><strong><font color='#10c300'>3-1、查看IP地址</font></strong></h4><h5 id="方法一：使用-ip-命令"><a href="#方法一：使用-ip-命令" class="headerlink" title="方法一：使用 ip 命令"></a><strong><font color='cornflowerblue'>方法一：使用 <code>ip</code> 命令</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ip addr show eth0</span><br><span class="line">简写</span><br><span class="line">ip a</span><br></pre></td></tr></table></figure><br><h5 id="方法二：使用-hostname-命令（推荐）"><a href="#方法二：使用-hostname-命令（推荐）" class="headerlink" title="方法二：使用 hostname 命令（推荐）"></a><strong><font color='cornflowerblue'>方法二：使用 <code>hostname</code> 命令（推荐）</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hostname -I</span><br></pre></td></tr></table></figure><br><h5 id="方法三：使用-ifconfig-命令"><a href="#方法三：使用-ifconfig-命令" class="headerlink" title="方法三：使用 ifconfig 命令"></a><strong><font color='cornflowerblue'>方法三：使用 <code>ifconfig</code> 命令</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ifconfig eth0</span><br></pre></td></tr></table></figure><p><em>注意：此方法需要先安装 <code>net-tools</code></em></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> dnf install net-tools</span><br></pre></td></tr></table></figure><p><strong>卸载某个软件包：</strong></p><figure class="highlight bash"><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"><span class="built_in">sudo</span> dnf remove net-tools</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将配置文件一起删除，彻底清除，之后 `ifconfig` 命令就完全不能使用了</span></span><br><span class="line"><span class="built_in">sudo</span> dnf remove net-tools</span><br></pre></td></tr></table></figure><br><h5 id="关于-IP-地址说明"><a href="#关于-IP-地址说明" class="headerlink" title="关于 IP 地址说明"></a><strong><font color='cornflowerblue'>关于 IP 地址说明</font></strong></h5><ul><li><strong>eth0</strong>: WSL2 虚拟网络接口，通常在 <code>172.x.x.x</code> 段</li><li><strong>lo</strong>: 本地回环地址 <code>127.0.0.1</code></li></ul><p><strong>示例输出：</strong></p><figure class="highlight bash"><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">$ hostname -I</span><br><span class="line">172.24.240.100</span><br></pre></td></tr></table></figure><p>这个 IP 地址可以用于从 Windows 访问 WSL2 中的服务</p><br><h4 id="3-2、查看用户信息"><a href="#3-2、查看用户信息" class="headerlink" title="3-2、查看用户信息"></a><strong><font color='#10c300'>3-2、查看用户信息</font></strong></h4><h5 id="查看用户信息"><a href="#查看用户信息" class="headerlink" title="查看用户信息"></a><strong><font color='cornflowerblue'>查看用户信息</font></strong></h5><p><strong>查看当前用户</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">whoami</span></span><br></pre></td></tr></table></figure><p><strong>查看所有用户</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cat</span> /etc/passwd</span><br></pre></td></tr></table></figure><p><strong>查看所有普通用户（UID &gt;&#x3D; 1000）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">awk -F: <span class="string">&#x27;$3 &gt;= 1000 &#123;print $1&#125;&#x27;</span> /etc/passwd</span><br></pre></td></tr></table></figure><p><strong>查看当前登录用户的详细信息</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">id</span></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">finger $(<span class="built_in">whoami</span>)  <span class="comment"># 需要安装 finger</span></span><br></pre></td></tr></table></figure><br><h5 id="密码管理"><a href="#密码管理" class="headerlink" title="密码管理"></a><strong><font color='cornflowerblue'>密码管理</font></strong></h5><p><strong>⚠️ 重要说明</strong><br><strong>无法直接查看密码</strong>，因为密码是加密存储的，在 <code>/etc/shadow</code> 文件中</p><p><strong>查看密码状态</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> passwd -S username</span><br></pre></td></tr></table></figure><p>会显示状态：</p><ul><li><code>P</code> &#x3D; 已设置密码</li><li><code>NP</code> &#x3D; 未设置密码</li><li><code>L</code> &#x3D; 账户被锁定</li></ul><br><h5 id="设置-修改密码"><a href="#设置-修改密码" class="headerlink" title="设置&#x2F;修改密码"></a><strong><font color='cornflowerblue'>设置&#x2F;修改密码</font></strong></h5><p><strong>修改当前用户密码</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">passwd</span><br></pre></td></tr></table></figure><p><strong>修改其他用户密码（需要 root 权限）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> passwd username</span><br></pre></td></tr></table></figure><p><strong>从 Windows 重置 WSL 用户密码</strong></p><p>如果忘记了密码，可以在 <strong>PowerShell</strong> 或 <strong>CMD</strong> 中执行：</p><figure class="highlight powershell"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 以 root 为默认用户</span></span><br><span class="line">wsl <span class="literal">-u</span> root</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 在 WSL 中重置密码</span></span><br><span class="line">passwd your_username</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 退出并恢复默认用户（可选）</span></span><br><span class="line"><span class="comment"># 编辑 /etc/wsl.conf 来使用默认用户</span></span><br><span class="line"><span class="keyword">exit</span></span><br></pre></td></tr></table></figure><p>或者一条命令：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl <span class="literal">-u</span> root passwd your_username</span><br></pre></td></tr></table></figure><br><h5 id="查看-sudo-权限用户"><a href="#查看-sudo-权限用户" class="headerlink" title="查看 sudo 权限用户"></a><strong><font color='cornflowerblue'>查看 sudo 权限用户</font></strong></h5><figure class="highlight bash"><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"># 查看 sudo 组成员</span></span><br><span class="line">getent group <span class="built_in">sudo</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 或</span></span><br><span class="line">grep <span class="string">&#x27;^sudo:&#x27;</span> /etc/group</span><br></pre></td></tr></table></figure><br><h5 id="管理用户和权限"><a href="#管理用户和权限" class="headerlink" title="管理用户和权限"></a><strong><font color='cornflowerblue'>管理用户和权限</font></strong></h5><figure class="highlight bash"><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"># 创建新用户</span></span><br><span class="line"><span class="built_in">sudo</span> adduser newuser</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除用户</span></span><br><span class="line"><span class="built_in">sudo</span> deluser username</span><br><span class="line"></span><br><span class="line"><span class="comment"># 将用户添加到 sudo 组</span></span><br><span class="line"><span class="built_in">sudo</span> usermod -aG <span class="built_in">sudo</span> username</span><br></pre></td></tr></table></figure><p><strong>提示</strong>：WSL2 首次安装时需要你创建用户和密码，如果忘记需要重置用户账号。</p><h4 id="3-3、设置自启动WSL"><a href="#3-3、设置自启动WSL" class="headerlink" title="3-3、设置自启动WSL"></a><strong><font color='#10c300'>3-3、设置自启动WSL</font></strong></h4><p>使用任务计划程序设置自启动</p><h5 id="1️⃣-确认-WSL-发行版名称"><a href="#1️⃣-确认-WSL-发行版名称" class="headerlink" title="1️⃣ 确认 WSL 发行版名称"></a><strong><span style='color:cornflowerblue'>1️⃣ 确认 WSL 发行版名称</span></strong></h5><p>在<code>PowerShell</code>终端输入</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">wsl -l -v</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331005012166.png" alt="image-20260331005012166"></p><h5 id="2️⃣-打开任务计划程序"><a href="#2️⃣-打开任务计划程序" class="headerlink" title="2️⃣ 打开任务计划程序"></a><strong><font color='cornflowerblue'>2️⃣ 打开任务计划程序</font></strong></h5><ul><li>按 <code>Win + R</code></li><li>输入 <code>taskschd.msc</code></li><li>点击”确定”</li></ul><h5 id="3️⃣-创建基本任务"><a href="#3️⃣-创建基本任务" class="headerlink" title="3️⃣ 创建基本任务"></a><strong><font color='cornflowerblue'>3️⃣ 创建基本任务</font></strong></h5><ul><li>在右侧”操作”面板，点击”创建基本任务”</li><li>名称输入：<code>自动启动WSL2</code></li><li>描述（可选）：<code>开机自动启动AlmaLinux-9</code></li><li>点击”下一步”</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331005949544.png" style="zoom:51%" />    <img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331010015335.png" style="zoom:40%" /> </p><h5 id="4️⃣-设置触发器"><a href="#4️⃣-设置触发器" class="headerlink" title="4️⃣ 设置触发器"></a><strong><font color='cornflowerblue'>4️⃣ 设置触发器</font></strong></h5><ul><li>选择”当计算机启动时”</li><li>点击”下一步”</li></ul><h5 id="5️⃣-设置操作"><a href="#5️⃣-设置操作" class="headerlink" title="5️⃣ 设置操作"></a><strong><span style='color:cornflowerblue'>5️⃣ 设置操作</span></strong></h5><ul><li>选择”启动程序”</li><li>点击”下一步”</li></ul><h5 id="6️⃣-配置程序"><a href="#6️⃣-配置程序" class="headerlink" title="6️⃣ 配置程序"></a><strong><span style='color:cornflowerblue'>6️⃣ 配置程序</span></strong></h5><ul><li>程序或脚本：输入 <code>wsl.exe</code></li><li>添加参数：输入 <code>-d AlmaLinux-9</code>（使用创建基本任务描述中的名字AlmaLinux-9）</li><li>点击”下一步”</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331011650222.png" style="zoom:51%" /><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331011357936.png" style="zoom:51%" /></p><figure class="highlight bash"><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"># 通过命令查找wsl安装路径</span></span><br><span class="line">where.exe wsl</span><br></pre></td></tr></table></figure><h5 id="7️⃣-完成创建"><a href="#7️⃣-完成创建" class="headerlink" title="7️⃣ 完成创建"></a><strong><span style='color:cornflowerblue'>7️⃣ 完成创建</span></strong></h5><ul><li>勾选”当单击’完成’时，打开此任务属性的对话框”</li><li>点击”完成”</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331012026057.png" alt="image-20260331012026057"></p><h5 id="8️⃣-修改任务属性"><a href="#8️⃣-修改任务属性" class="headerlink" title="8️⃣ 修改任务属性"></a><strong><span style='color:cornflowerblue'>8️⃣ 修改任务属性</span></strong></h5><ul><li>在弹出的属性窗口中：<ul><li>勾选”使用最高权限运行”</li><li>勾选”不管用户是否登录都要运行”</li><li>点击”确定”</li></ul></li><li>可能需要输入 Windows 密码确认</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331012158928.png" alt="image-20260331012158928"></p><h5 id="9️⃣完成测试"><a href="#9️⃣完成测试" class="headerlink" title="9️⃣完成测试"></a><strong><span style='color:cornflowerblue'>9️⃣完成测试</span></strong></h5><figure class="highlight bash"><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"># 如果看到 AlmaLinux 在运行列表中，说明配置成功。</span></span><br><span class="line">wsl -l --running</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260331012318326.png" alt="image-20260331012318326"></p><br><hr><h3 id="四、使用FinalShell连接WSL2"><a href="#四、使用FinalShell连接WSL2" class="headerlink" title="四、使用FinalShell连接WSL2"></a><strong><font color='red'>四、使用FinalShell连接WSL2</font></strong></h3><h4 id="4-1、连接原理"><a href="#4-1、连接原理" class="headerlink" title="4-1、连接原理"></a><strong><font color='#10c300'>4-1、连接原理</font></strong></h4><p>WSL2 默认<strong>没有开启 SSH 服务</strong>，需要手动安装和配置。</p><br><h4 id="4-2、安装和配置SSH服务"><a href="#4-2、安装和配置SSH服务" class="headerlink" title="4-2、安装和配置SSH服务"></a><strong><font color='#10c300'>4-2、安装和配置SSH服务</font></strong></h4><h5 id="1️⃣-在-WSL2-中安装-SSH-服务"><a href="#1️⃣-在-WSL2-中安装-SSH-服务" class="headerlink" title="1️⃣ 在 WSL2 中安装 SSH 服务"></a><strong><font color='cornflowerblue'>1️⃣ 在 WSL2 中安装 SSH 服务</font></strong></h5><figure class="highlight bash"><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"><span class="built_in">sudo</span> dnf update</span><br><span class="line"></span><br><span class="line"><span class="comment"># 安装 OpenSSH 服务器</span></span><br><span class="line"><span class="built_in">sudo</span> dnf install openssh-server -y</span><br></pre></td></tr></table></figure><br><h5 id="2️⃣-配置-SSH-服务"><a href="#2️⃣-配置-SSH-服务" class="headerlink" title="2️⃣ 配置 SSH 服务"></a><strong><font color='cornflowerblue'>2️⃣ 配置 SSH 服务</font></strong></h5><p>自己用可用不配置  用默认的即可</p><table><thead><tr><th>配置项</th><th>默认值</th><th>说明</th></tr></thead><tbody><tr><td><code>Port</code></td><td><code>22</code></td><td>默认 SSH 端口</td></tr><tr><td><code>PermitRootLogin</code></td><td><code>prohibit-password</code></td><td>允许 root 用密钥登录，禁止密码登录</td></tr><tr><td><code>PasswordAuthentication</code></td><td><code>yes</code></td><td>默认允许密码认证</td></tr><tr><td><code>PubkeyAuthentication</code></td><td><code>yes</code></td><td>默认启用密钥认证</td></tr></tbody></table><figure class="highlight bash"><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"># 编辑 SSH 配置文件</span></span><br><span class="line"><span class="built_in">sudo</span> nano /etc/ssh/sshd_config</span><br></pre></td></tr></table></figure><p><strong>修改以下配置项</strong>（使用 <code>Ctrl+W</code> 搜索）：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Port 22                          <span class="comment"># 确认端口</span></span><br><span class="line">PermitRootLogin no               <span class="comment"># 禁止 root 登录（推荐）</span></span><br><span class="line">PasswordAuthentication <span class="built_in">yes</span>       <span class="comment"># 启用密码认证</span></span><br></pre></td></tr></table></figure><p><strong>保存</strong>：<br>按 <code>Ctrl + O</code>(屏幕底部会提示 <code>File Name to Write:</code>)<br>然后按 <strong>Enter</strong> 确认保存。</p><p><strong>退出</strong>：<br>按 <code>Ctrl + X</code>。</p><br><h5 id="3️⃣-启动-SSH-服务"><a href="#3️⃣-启动-SSH-服务" class="headerlink" title="3️⃣ 启动 SSH 服务"></a><strong><font color='cornflowerblue'>3️⃣ 启动 SSH 服务</font></strong></h5><figure class="highlight bash"><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"># 启动 SSH 服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl start sshd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 检查 SSH 服务状态</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status sshd</span><br></pre></td></tr></table></figure><p>如果看到以下输出，说明 SSH 服务已成功启动：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Active: active (running) since Sat 2026-01-17 19:50:01 CST; 4s ago</span><br></pre></td></tr></table></figure><p><strong>状态是 <code>active (running)</code></strong>，说明服务正在运行。</p><p>关键信息：</p><figure class="highlight plaintext"><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">Server listening on 0.0.0.0 port 22.</span><br><span class="line">Server listening on :: port 22.</span><br></pre></td></tr></table></figure><p><strong>SSH 服务正在监听 22 端口</strong>，可以接受连接了。</p><br><h5 id="4️⃣-设置-SSH-自动启动（可选）"><a href="#4️⃣-设置-SSH-自动启动（可选）" class="headerlink" title="4️⃣ 设置 SSH 自动启动（可选）"></a><strong><font color='cornflowerblue'>4️⃣ 设置 SSH 自动启动（可选）</font></strong></h5><p>每次启动 WSL2 时自动启动 SSH：</p><figure class="highlight bash"><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"># 设置开机自启</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl <span class="built_in">enable</span> sshd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看是否开启成功</span></span><br><span class="line">systemctl is-enabled sshd</span><br><span class="line">输出 enabled 表示成功，disabled 表示未启用。</span><br></pre></td></tr></table></figure><br><h5 id="5️⃣-获取-WSL2-的-IP-地址"><a href="#5️⃣-获取-WSL2-的-IP-地址" class="headerlink" title="5️⃣ 获取 WSL2 的 IP 地址"></a><strong><font color='cornflowerblue'>5️⃣ 获取 WSL2 的 IP 地址</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hostname -I</span><br></pre></td></tr></table></figure><p>记录这个 IP，例如：<code>172.24.240.100</code></p><br><h5 id="6️⃣-在-FinalShell-中创建连接"><a href="#6️⃣-在-FinalShell-中创建连接" class="headerlink" title="6️⃣ 在 FinalShell 中创建连接"></a><strong><font color='cornflowerblue'>6️⃣ 在 FinalShell 中创建连接</font></strong></h5><ol><li><p><strong>打开 FinalShell</strong></p></li><li><p><strong>新建连接</strong> → 选择 <strong>SSH</strong></p></li><li><p>填写信息：</p><ul><li><strong>名称</strong>：WSL2-Ubuntu（自定义）</li><li><strong>主机</strong>：<code>172.x.x.x</code>（你的 WSL2 IP）</li><li><strong>端口</strong>：<code>22</code></li><li><strong>认证方式</strong>：密码</li><li><strong>用户名</strong>：你的 WSL2 用户名</li><li><strong>密码</strong>：你的 WSL2 密码</li></ul></li><li><p><strong>保存并连接</strong></p></li></ol><br><h4 id="4-3、常见问题排查"><a href="#4-3、常见问题排查" class="headerlink" title="4-3、常见问题排查"></a><strong><font color='#10c300'>4-3、常见问题排查</font></strong></h4><h5 id="❌-问题1：连接超时"><a href="#❌-问题1：连接超时" class="headerlink" title="❌ 问题1：连接超时"></a><strong><font color='cornflowerblue'>❌ 问题1：连接超时</font></strong></h5><figure class="highlight bash"><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"># 检查 SSH 是否运行</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl status sshd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果没有运行，启动服务</span></span><br><span class="line"><span class="built_in">sudo</span> systemctl start sshd</span><br></pre></td></tr></table></figure><br><h5 id="❌-问题2：端口被占用"><a href="#❌-问题2：端口被占用" class="headerlink" title="❌ 问题2：端口被占用"></a><strong><font color='cornflowerblue'>❌ 问题2：端口被占用</font></strong></h5><figure class="highlight bash"><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"># 检查 22 端口是否占用</span></span><br><span class="line"><span class="built_in">sudo</span> netstat -tlnp | grep :22</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果使用其他端口，如 2222</span></span><br><span class="line"><span class="built_in">sudo</span> nano /etc/ssh/sshd_config</span><br><span class="line"><span class="comment"># 修改 Port 为 2222</span></span><br><span class="line"><span class="built_in">sudo</span> service ssh restart</span><br></pre></td></tr></table></figure><br><h5 id="❌-问题3：防火墙阻拦"><a href="#❌-问题3：防火墙阻拦" class="headerlink" title="❌ 问题3：防火墙阻拦"></a><strong><font color='cornflowerblue'>❌ 问题3：防火墙阻拦</font></strong></h5><figure class="highlight bash"><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"><span class="built_in">sudo</span> ufw status</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果已启用，允许 SSH</span></span><br><span class="line"><span class="built_in">sudo</span> ufw allow 22/tcp</span><br></pre></td></tr></table></figure><br><h5 id="❌-问题4：IP-地址变化"><a href="#❌-问题4：IP-地址变化" class="headerlink" title="❌ 问题4：IP 地址变化"></a><strong><font color='cornflowerblue'>❌ 问题4：IP 地址变化</font></strong></h5><p><strong>问题原因：</strong></p><p>WSL2 使用虚拟网络适配器，每次重启 WSL2 或 Windows 时，WSL2 的 IP 地址可能会发生变化（例如从 <code>172.17.114.127</code> 变成 <code>172.24.240.100</code>）。这会导致 FinalShell 中保存的连接配置失效，需要频繁更新 IP 地址。</p><p><strong>解决方案A：使用 localhost + 端口转发（推荐）</strong></p><p>通过 Windows 的端口转发功能，将 Windows 的某个端口映射到 WSL2 的 SSH 端口，这样无论 WSL2 的 IP 如何变化，都可以通过 <code>localhost</code> 连接。</p><p><strong>步骤：</strong></p><ol><li><strong>获取当前 WSL2 的 IP 地址</strong></li></ol><figure class="highlight bash"><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"># 在 WSL2 中执行</span></span><br><span class="line">hostname -I</span><br></pre></td></tr></table></figure><ol start="2"><li><strong>在 Windows PowerShell（管理员）中配置端口转发</strong></li></ol><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 将 Windows 的 2222 端口转发到 WSL2 的 22 端口</span></span><br><span class="line"><span class="comment"># 注意：需要替换 172.x.x.x 为实际的 WSL2 IP</span></span><br><span class="line">netsh interface portproxy add v4tov4 listenport=<span class="number">2222</span> listenaddress=<span class="number">0.0</span>.<span class="number">0.0</span> connectport=<span class="number">22</span> connectaddress=<span class="number">172</span>.x.x.x</span><br></pre></td></tr></table></figure><ol start="3"><li><strong>在 FinalShell 中使用固定地址连接</strong><ul><li><strong>主机</strong>：<code>localhost</code> 或 <code>127.0.0.1</code></li><li><strong>端口</strong>：<code>2222</code></li></ul></li></ol><p>**⚠️ 注意：**每次 WSL2 IP 变化后，需要重新执行端口转发命令。可以创建一个 PowerShell 脚本自动获取 IP 并配置转发。</p><p><strong>解决方案B：使用 Windows Terminal（最简单）</strong></p><p>Windows Terminal 是 Windows 10&#x2F;11 自带的终端工具，可以直接启动 WSL2，无需 SSH 连接。</p><p><strong>优点：</strong></p><ul><li>✅ 无需配置 SSH</li><li>✅ 无需担心 IP 变化</li><li>✅ 启动速度快</li><li>✅ 集成度高</li></ul><p><strong>使用方法：</strong></p><ol><li>打开 <strong>Windows Terminal</strong></li><li>点击下拉箭头，选择 <strong>Ubuntu</strong>（或你的发行版）</li><li>直接使用，就像使用 SSH 客户端一样</li></ol><p><strong>解决方案C：配置静态 IP（高级）</strong></p><p>可以配置 WSL2 使用固定 IP，但需要修改 Windows 和 WSL2 的网络配置，比较复杂，不推荐普通用户使用。</p><br><h5 id="❌-问题5：密码认证失败"><a href="#❌-问题5：密码认证失败" class="headerlink" title="❌ 问题5：密码认证失败"></a><strong><font color='cornflowerblue'>❌ 问题5：密码认证失败</font></strong></h5><figure class="highlight bash"><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"># 确认密码认证已启用</span></span><br><span class="line">grep <span class="string">&quot;PasswordAuthentication&quot;</span> /etc/ssh/sshd_config</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重置密码</span></span><br><span class="line">passwd</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启 SSH 服务</span></span><br><span class="line"><span class="built_in">sudo</span> service ssh restart</span><br></pre></td></tr></table></figure><br><h4 id="4-4、快速测试连接"><a href="#4-4、快速测试连接" class="headerlink" title="4-4、快速测试连接"></a><strong><font color='#10c300'>4-4、快速测试连接</font></strong></h4><p>在 <strong>Windows CMD</strong> 或 <strong>PowerShell</strong> 中测试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh your_username@172.x.x.x</span><br></pre></td></tr></table></figure><p>如果能够连接，说明 SSH 配置正确，FinalShell 也应该能连接。</p><p><strong>SSH 连接格式说明：</strong></p><figure class="highlight bash"><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">ssh 用户名@IP地址</span><br><span class="line">ssh 用户名@IP地址 -p 端口号</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ssh zheng_hb@172.17.114.127</span><br><span class="line">ssh zheng_hb@172.17.114.127 -p 22</span><br><span class="line">ssh root@192.168.1.100 -p 2222</span><br></pre></td></tr></table></figure><br><hr><h3 id="五、关于IP地址变化的详细说明"><a href="#五、关于IP地址变化的详细说明" class="headerlink" title="五、关于IP地址变化的详细说明"></a><strong><font color='red'>五、关于IP地址变化的详细说明</font></strong></h3><h4 id="5-1、为什么WSL2的IP地址会变化？"><a href="#5-1、为什么WSL2的IP地址会变化？" class="headerlink" title="5-1、为什么WSL2的IP地址会变化？"></a><strong><font color='#10c300'>5-1、为什么WSL2的IP地址会变化？</font></strong></h4><p>WSL2 使用虚拟网络适配器（Virtual Network Adapter）来创建独立的网络环境。每次：</p><ul><li>重启 Windows</li><li>重启 WSL2（<code>wsl --shutdown</code> 后重新启动）</li><li>Windows 网络适配器重置</li></ul><p>WSL2 的虚拟网络可能会重新分配 IP 地址，导致 IP 地址发生变化。</p><p><strong>示例：</strong></p><ul><li>第一次启动：<code>172.17.114.127</code></li><li>重启后：<code>172.24.240.100</code></li><li>再次重启：<code>172.18.56.89</code></li></ul><br><h4 id="5-2、IP地址变化的影响"><a href="#5-2、IP地址变化的影响" class="headerlink" title="5-2、IP地址变化的影响"></a><strong><font color='#10c300'>5-2、IP地址变化的影响</font></strong></h4><p><strong>影响场景：</strong></p><ul><li>❌ FinalShell 中保存的连接配置失效</li><li>❌ 需要频繁更新 IP 地址</li><li>❌ 如果使用 IP 地址访问 WSL2 中的服务（如 Web 服务器），需要重新配置</li></ul><p><strong>不受影响：</strong></p><ul><li>✅ Windows Terminal 直接启动 WSL2（不依赖 IP）</li><li>✅ 在 Windows 中通过 <code>localhost</code> 访问 WSL2 服务（Windows 会自动处理）</li></ul><br><h4 id="5-3、推荐解决方案对比"><a href="#5-3、推荐解决方案对比" class="headerlink" title="5-3、推荐解决方案对比"></a><strong><font color='#10c300'>5-3、推荐解决方案对比</font></strong></h4><table><thead><tr><th align="left">方案</th><th align="left">优点</th><th align="left">缺点</th><th align="left">适用场景</th></tr></thead><tbody><tr><td align="left"><strong>Windows Terminal</strong></td><td align="left">无需配置、稳定可靠、启动快</td><td align="left">界面相对简单</td><td align="left">日常开发使用（最推荐）</td></tr><tr><td align="left"><strong>端口转发</strong></td><td align="left">可以使用 FinalShell 等专业工具</td><td align="left">需要手动配置、IP变化后需重新配置</td><td align="left">需要 FinalShell 的高级功能</td></tr><tr><td align="left"><strong>静态IP</strong></td><td align="left">IP 固定不变</td><td align="left">配置复杂、容易出错</td><td align="left">特殊需求场景</td></tr></tbody></table><br><h4 id="5-4、自动端口转发脚本（可选）"><a href="#5-4、自动端口转发脚本（可选）" class="headerlink" title="5-4、自动端口转发脚本（可选）"></a><strong><font color='#10c300'>5-4、自动端口转发脚本（可选）</font></strong></h4><p>如果你坚持使用 FinalShell，可以创建一个 PowerShell 脚本自动配置端口转发：</p><p><strong>创建脚本 <code>wsl-port-forward.ps1</code>：</strong></p><figure class="highlight powershell"><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"># 获取 WSL2 IP 地址</span></span><br><span class="line"><span class="variable">$wslIp</span> = (wsl hostname <span class="literal">-I</span>).Trim()</span><br><span class="line"></span><br><span class="line"><span class="comment"># 删除旧的端口转发规则</span></span><br><span class="line">netsh interface portproxy delete v4tov4 listenport=<span class="number">2222</span> listenaddress=<span class="number">0.0</span>.<span class="number">0.0</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 添加新的端口转发规则</span></span><br><span class="line">netsh interface portproxy add v4tov4 listenport=<span class="number">2222</span> listenaddress=<span class="number">0.0</span>.<span class="number">0.0</span> connectport=<span class="number">22</span> connectaddress=<span class="variable">$wslIp</span></span><br><span class="line"></span><br><span class="line"><span class="built_in">Write-Host</span> <span class="string">&quot;端口转发已配置: localhost:2222 -&gt; <span class="variable">$wslIp:22</span>&quot;</span></span><br></pre></td></tr></table></figure><p><strong>使用方法：</strong><br>每次启动 WSL2 后，以管理员身份运行此脚本即可。</p><br><hr><h3 id="六、总结"><a href="#六、总结" class="headerlink" title="六、总结"></a><strong><font color='red'>六、总结</font></strong></h3><p>本指南涵盖了 WSL2 的安装、卸载、基础操作和远程连接配置，帮助你快速上手使用 WSL2 开发环境。</p><p><strong>主要功能：</strong></p><ul><li>✅ WSL2 安装与卸载</li><li>✅ IP 地址查看</li><li>✅ 用户管理</li><li>✅ SSH 服务配置</li><li>✅ FinalShell 远程连接</li></ul><p><strong>注意事项：</strong></p><ul><li>安装和卸载操作需要管理员权限</li><li>卸载发行版会删除所有数据，请提前备份</li><li><strong>WSL2 的 IP 地址可能会变化</strong>：<ul><li><strong>推荐方案</strong>：直接使用 <strong>Windows Terminal</strong>，无需配置，稳定可靠</li><li><strong>备选方案</strong>：使用端口转发 + FinalShell，但需要处理 IP 变化问题</li></ul></li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、WSL2安装与卸载&quot;&gt;&lt;a href=&quot;#一、WSL2安装与卸载&quot; class=&quot;headerlink&quot; title=&quot;一、WSL2安装与卸载&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、WSL2安装与卸载&lt;/font&gt;&lt;/strong&gt;</summary>
      
    
    
    
    <category term="后端与运维" scheme="http://example.com/categories/%E5%90%8E%E7%AB%AF%E4%B8%8E%E8%BF%90%E7%BB%B4/"/>
    
    
    <category term="Windows" scheme="http://example.com/tags/Windows/"/>
    
    <category term="WSL2" scheme="http://example.com/tags/WSL2/"/>
    
    <category term="Linux" scheme="http://example.com/tags/Linux/"/>
    
  </entry>
  
  <entry>
    <title>Hyper-V虚拟机详细安装教程</title>
    <link href="http://example.com/posts/hyper-v-install.html"/>
    <id>http://example.com/posts/hyper-v-install.html</id>
    <published>2026-02-23T10:00:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、Hyper-V简介"><a href="#一、Hyper-V简介" class="headerlink" title="一、Hyper-V简介 "></a><strong><font color='red'>一、Hyper-V简介 </font></strong></h3><h4 id="1-1、什么是Hyper-V"><a href="#1-1、什么是Hyper-V" class="headerlink" title="1-1、什么是Hyper-V"></a><strong><font color='#10c300'>1-1、什么是Hyper-V</font></strong></h4><p>Hyper-V 是微软开发的原生虚拟化平台，内置于 Windows 专业版、企业版和教育版系统中。它允许用户在单台物理计算机上创建和管理多个虚拟机，每个虚拟机都可以运行独立的操作系统。</p><br><h4 id="1-2、虚拟机软件对比"><a href="#1-2、虚拟机软件对比" class="headerlink" title="1-2、虚拟机软件对比"></a><strong><font color='#10c300'>1-2、虚拟机软件对比</font></strong></h4><p>下表对比了目前主流的虚拟机软件，帮助您选择合适的虚拟化解决方案：</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118191245387.png" alt="image-20260118191245387"></p><p><strong><code>注意：Hyper-V 作为 Windows 系统自带的虚拟化工具，无需额外安装第三方软件，性能优秀且与 Windows 系统集成度高。</code></strong></p><br><h3 id="二、系统要求与准备"><a href="#二、系统要求与准备" class="headerlink" title="二、系统要求与准备 "></a><strong><font color='red'>二、系统要求与准备 </font></strong></h3><h4 id="2-1、Windows版本要求"><a href="#2-1、Windows版本要求" class="headerlink" title="2-1、Windows版本要求"></a><strong><font color='#10c300'>2-1、Windows版本要求</font></strong></h4><p>Hyper-V 功能仅支持以下 Windows 版本：</p><ul><li>Windows 10 专业版&#x2F;企业版&#x2F;教育版</li><li>Windows 11 专业版&#x2F;企业版&#x2F;教育版</li><li>Windows Server 2016 及以上版本</li></ul><p><strong><code>注意：Windows 家庭版不支持 Hyper-V 功能。如果您使用的是家庭版，需要升级到专业版。可以从官方渠道或电商平台购买升级密钥。</code></strong></p><p>查看当前系统版本：</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118191729848.png" alt="image-20260118191729848"></p><br><h4 id="2-2、硬件要求"><a href="#2-2、硬件要求" class="headerlink" title="2-2、硬件要求"></a><strong><font color='#10c300'>2-2、硬件要求</font></strong></h4><p>使用 Hyper-V 需要满足以下硬件条件：</p><ul><li><strong>处理器：</strong> 64位处理器，支持二级地址转换（SLAT）</li><li><strong>内存：</strong> 至少 4GB RAM（建议 8GB 以上）</li><li><strong>存储空间：</strong> 根据虚拟机数量和用途，建议预留 50GB 以上</li><li><strong>BIOS设置：</strong> 需要在 BIOS 中启用虚拟化技术（Intel VT-x 或 AMD-V）</li></ul><br><h4 id="2-3、准备安装镜像"><a href="#2-3、准备安装镜像" class="headerlink" title="2-3、准备安装镜像"></a><strong><font color='#10c300'>2-3、准备安装镜像</font></strong></h4><p>在创建虚拟机之前，需要下载操作系统的 ISO 镜像文件。本教程以 Windows 10 为例：</p><ul><li><a href="https://www.microsoft.com/zh-cn/software-download/windows10">下载 Windows 10 官方镜像</a></li><li><a href="https://www.microsoft.com/zh-cn/software-download/windows11">下载 Windows 11 官方镜像</a></li></ul><p>也可以下载 Linux 发行版（如 Ubuntu、CentOS）的 ISO 镜像文件用于安装。</p><br><h3 id="三、启用Hyper-V功能"><a href="#三、启用Hyper-V功能" class="headerlink" title="三、启用Hyper-V功能 "></a><strong><font color='red'>三、启用Hyper-V功能 </font></strong></h3><h4 id="3-1、通过Windows功能启用"><a href="#3-1、通过Windows功能启用" class="headerlink" title="3-1、通过Windows功能启用"></a><strong><font color='#10c300'>3-1、通过Windows功能启用</font></strong></h4><h5 id="1）打开Windows功能"><a href="#1）打开Windows功能" class="headerlink" title="1）打开Windows功能"></a><strong><font color='cornflowerblue'>1）打开Windows功能</font></strong></h5><p>在 Windows 搜索框中输入”<strong>启用或关闭Windows功能</strong>“或”<strong>功能</strong>“，打开 Windows 功能设置窗口。</p><br><h5 id="2）勾选Hyper-V选项"><a href="#2）勾选Hyper-V选项" class="headerlink" title="2）勾选Hyper-V选项"></a><strong><font color='cornflowerblue'>2）勾选Hyper-V选项</font></strong></h5><p>在 Windows 功能列表中，找到并勾选以下选项：</p><ul><li><strong>Hyper-V</strong><ul><li>Hyper-V 管理工具</li><li>Hyper-V 平台</li></ul></li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118191918620.png" alt="image-20260118191918620"></p><p>点击”<strong>确定</strong>“按钮，系统将自动安装 Hyper-V 相关组件。</p><br><h5 id="3）重启计算机"><a href="#3）重启计算机" class="headerlink" title="3）重启计算机"></a><strong><font color='cornflowerblue'>3）重启计算机</font></strong></h5><p>安装完成后，系统会提示重启计算机。点击”<strong>立即重启</strong>“使 Hyper-V 功能生效。</p><p><strong><code>注意：重启后 Hyper-V 服务会自动启动，您可以在开始菜单中找到&quot;Hyper-V 管理器&quot;。</code></strong></p><br><h4 id="3-2、通过PowerShell启用（可选）"><a href="#3-2、通过PowerShell启用（可选）" class="headerlink" title="3-2、通过PowerShell启用（可选）"></a><strong><font color='#10c300'>3-2、通过PowerShell启用（可选）</font></strong></h4><p>也可以使用 PowerShell 命令快速启用 Hyper-V：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Enable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> Microsoft<span class="literal">-Hyper-V</span> <span class="literal">-All</span></span><br></pre></td></tr></table></figure><p>执行命令后，同样需要重启计算机。</p><br><h3 id="四、创建和配置虚拟机"><a href="#四、创建和配置虚拟机" class="headerlink" title="四、创建和配置虚拟机 "></a><strong><font color='red'>四、创建和配置虚拟机 </font></strong></h3><h4 id="4-1、打开Hyper-V管理器"><a href="#4-1、打开Hyper-V管理器" class="headerlink" title="4-1、打开Hyper-V管理器"></a><strong><font color='#10c300'>4-1、打开Hyper-V管理器</font></strong></h4><p>在开始菜单中搜索并打开”<strong>Hyper-V 管理器</strong>“。Hyper-V 管理器是管理所有虚拟机的控制台。</p><br><h4 id="4-2、新建虚拟机"><a href="#4-2、新建虚拟机" class="headerlink" title="4-2、新建虚拟机"></a><strong><font color='#10c300'>4-2、新建虚拟机</font></strong></h4><h5 id="1）启动新建虚拟机向导"><a href="#1）启动新建虚拟机向导" class="headerlink" title="1）启动新建虚拟机向导"></a><strong><font color='cornflowerblue'>1）启动新建虚拟机向导</font></strong></h5><p>在 Hyper-V 管理器中，点击右侧的”<strong>新建</strong>“ &gt; “<strong>虚拟机</strong>“，启动新建虚拟机向导。</p><br><h5 id="2）指定名称和位置"><a href="#2）指定名称和位置" class="headerlink" title="2）指定名称和位置"></a><strong><font color='cornflowerblue'>2）指定名称和位置</font></strong></h5><p>为虚拟机指定一个有意义的名称（如”Win10-Dev”），并选择虚拟机文件的存储位置。</p><p><strong><code>建议：选择空间充足的磁盘驱动器存储虚拟机文件。</code></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193131223.png" alt="image-20260118193131223"></p><br><h5 id="3）指定代数"><a href="#3）指定代数" class="headerlink" title="3）指定代数"></a><strong><font color='cornflowerblue'>3）指定代数</font></strong></h5><p>选择虚拟机代数：</p><ul><li><strong>第一代：</strong> 适用于较旧的操作系统（如 Windows 7、Windows Server 2008 及更早版本）</li><li><strong>第二代：</strong> 适用于较新的操作系统（如 Windows 8.1、Windows 10、Windows 11 及 Windows Server 2012 以上版本），支持 UEFI 启动</li></ul><p><strong>本教程以安装 Windows 10 为例，选择”第一代”以获得更好的兼容性。</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193237778.png" alt="image-20260118193237778"></p><p><strong><code>注意：如果要安装 Windows 11，建议选择&quot;第二代&quot;虚拟机，因为 Windows 11 需要 UEFI 和 TPM 2.0 支持。</code></strong></p><br><h5 id="4）分配内存"><a href="#4）分配内存" class="headerlink" title="4）分配内存"></a><strong><font color='cornflowerblue'>4）分配内存</font></strong></h5><p>为虚拟机分配内存。根据要安装的操作系统和用途决定内存大小：</p><ul><li><strong>Windows 10：</strong> 建议至少 4GB（4096MB）</li><li><strong>Windows Server：</strong> 建议至少 4GB</li><li><strong>Linux桌面版：</strong> 建议至少 2GB</li></ul><p>可以勾选”<strong>为此虚拟机使用动态内存</strong>“选项，让 Hyper-V 自动调整内存分配。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193445956.png" alt="image-20260118193445956"></p><br><h5 id="5）配置网络"><a href="#5）配置网络" class="headerlink" title="5）配置网络"></a><strong><font color='cornflowerblue'>5）配置网络</font></strong></h5><p>选择默认的虚拟交换机。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193500644.png" alt="image-20260118193500644"></p><br><h5 id="6）连接虚拟硬盘"><a href="#6）连接虚拟硬盘" class="headerlink" title="6）连接虚拟硬盘"></a><strong><font color='cornflowerblue'>6）连接虚拟硬盘</font></strong></h5><p>配置虚拟硬盘：</p><ul><li><strong>创建虚拟硬盘：</strong> 指定虚拟硬盘大小（建议至少 60GB）和存储位置</li><li><strong>使用现有虚拟硬盘：</strong> 选择已有的 VHD&#x2F;VHDX 文件</li><li><strong>稍后附加虚拟硬盘：</strong> 跳过此步骤，稍后再添加</li></ul><p>本教程选择”<strong>创建虚拟硬盘</strong>“，设置大小为 60GB。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193653514.png" alt="image-20260118193653514"></p><br><h5 id="7）安装选项"><a href="#7）安装选项" class="headerlink" title="7）安装选项"></a><strong><font color='cornflowerblue'>7）安装选项</font></strong></h5><p>选择操作系统的安装方式：</p><ul><li><strong>从可启动的 CD&#x2F;DVD-ROM 安装操作系统：</strong> 选择之前下载的 ISO 镜像文件</li><li><strong>从网络安装操作系统：</strong> 通过 PXE 网络启动安装</li><li><strong>稍后安装操作系统：</strong> 先创建虚拟机，稍后再安装</li></ul><p>本教程选择”<strong>从映像文件(.iso)安装操作系统</strong>“，并浏览选择 Windows 10 的 ISO 镜像文件。</p><br><h5 id="8）完成创建"><a href="#8）完成创建" class="headerlink" title="8）完成创建"></a><strong><font color='cornflowerblue'>8）完成创建</font></strong></h5><p>检查所有配置信息，确认无误后点击”<strong>完成</strong>“按钮。虚拟机创建成功！</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260118193735999.png" alt="image-20260118193735999"></p><p><strong><code>注意：此时虚拟机仅创建完成，还需要启动虚拟机并安装操作系统。</code></strong></p><br><h3 id="五、启动和安装操作系统"><a href="#五、启动和安装操作系统" class="headerlink" title="五、启动和安装操作系统 "></a><strong><font color='red'>五、启动和安装操作系统 </font></strong></h3><h4 id="5-1、启动虚拟机"><a href="#5-1、启动虚拟机" class="headerlink" title="5-1、启动虚拟机"></a><strong><font color='#10c300'>5-1、启动虚拟机</font></strong></h4><p>在 Hyper-V 管理器中，右键点击新创建的虚拟机，选择”<strong>启动</strong>“。然后右键选择”<strong>连接</strong>“打开虚拟机控制台窗口。</p><br><h4 id="5-2、安装操作系统"><a href="#5-2、安装操作系统" class="headerlink" title="5-2、安装操作系统"></a><strong><font color='#10c300'>5-2、安装操作系统</font></strong></h4><p>虚拟机启动后，会自动从 ISO 镜像文件引导，进入操作系统安装程序。按照安装向导的提示完成操作系统安装：</p><ol><li>选择语言、时间和键盘输入法</li><li>点击”<strong>现在安装</strong>“</li><li>输入产品密钥（或选择”<strong>我没有产品密钥</strong>“跳过）</li><li>选择操作系统版本</li><li>接受许可条款</li><li>选择”<strong>自定义：仅安装 Windows（高级）</strong>“</li><li>选择虚拟硬盘并进行分区</li><li>等待安装完成</li></ol><br><h4 id="5-3、安装集成服务（可选）"><a href="#5-3、安装集成服务（可选）" class="headerlink" title="5-3、安装集成服务（可选）"></a><strong><font color='#10c300'>5-3、安装集成服务（可选）</font></strong></h4><p>对于 Windows 虚拟机，Hyper-V 集成服务通常会自动安装。集成服务提供了增强功能，如：</p><ul><li>鼠标集成</li><li>时间同步</li><li>数据交换</li><li>心跳检测</li></ul><p>对于 Linux 虚拟机，可能需要手动安装集成服务组件。</p><br><h3 id="六、常见问题与解决方案"><a href="#六、常见问题与解决方案" class="headerlink" title="六、常见问题与解决方案 "></a><strong><font color='red'>六、常见问题与解决方案 </font></strong></h3><h4 id="6-1、无法启用Hyper-V"><a href="#6-1、无法启用Hyper-V" class="headerlink" title="6-1、无法启用Hyper-V"></a><strong><font color='#10c300'>6-1、无法启用Hyper-V</font></strong></h4><p><strong>问题：</strong> 勾选 Hyper-V 选项后提示无法安装。</p><p><strong>解决方案：</strong></p><ol><li>检查 Windows 版本是否为专业版&#x2F;企业版&#x2F;教育版</li><li>确认 CPU 支持虚拟化技术（Intel VT-x 或 AMD-V）</li><li>进入 BIOS 设置，启用虚拟化功能（通常在 CPU Configuration 或 Advanced 选项中）</li></ol><br><h4 id="6-2、虚拟机无法联网"><a href="#6-2、虚拟机无法联网" class="headerlink" title="6-2、虚拟机无法联网"></a><strong><font color='#10c300'>6-2、虚拟机无法联网</font></strong></h4><p><strong>问题：</strong> 虚拟机创建后无法访问网络。</p><p><strong>解决方案：</strong></p><ol><li>检查虚拟机网络适配器是否连接到虚拟交换机</li><li>创建外部虚拟交换机并绑定到物理网络适配器</li><li>检查虚拟机内部的网络配置（IP地址、DNS等）</li></ol><br><h4 id="6-3、虚拟机性能较差"><a href="#6-3、虚拟机性能较差" class="headerlink" title="6-3、虚拟机性能较差"></a><strong><font color='#10c300'>6-3、虚拟机性能较差</font></strong></h4><p><strong>问题：</strong> 虚拟机运行缓慢，响应速度慢。</p><p><strong>解决方案：</strong></p><ol><li>增加虚拟机分配的内存和处理器核心数</li><li>启用动态内存功能</li><li>将虚拟硬盘存储在 SSD 固态硬盘上</li><li>确保主机系统有足够的可用资源</li></ol><br><h4 id="6-4、与其他虚拟化软件冲突"><a href="#6-4、与其他虚拟化软件冲突" class="headerlink" title="6-4、与其他虚拟化软件冲突"></a><strong><font color='#10c300'>6-4、与其他虚拟化软件冲突</font></strong></h4><p><strong>问题：</strong> 启用 Hyper-V 后，VMware 或 VirtualBox 无法使用。</p><p><strong>解决方案：</strong></p><p>Hyper-V 会独占虚拟化功能，无法与其他虚拟化软件同时运行。如需使用其他虚拟化软件，需要暂时禁用 Hyper-V：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">Disable-WindowsOptionalFeature</span> <span class="literal">-Online</span> <span class="literal">-FeatureName</span> Microsoft<span class="literal">-Hyper-V-Hypervisor</span></span><br></pre></td></tr></table></figure><p>禁用后需要重启计算机。</p><br><h3 id="七、进阶配置与优化"><a href="#七、进阶配置与优化" class="headerlink" title="七、进阶配置与优化 "></a><strong><font color='red'>七、进阶配置与优化 </font></strong></h3><h4 id="7-1、快照功能"><a href="#7-1、快照功能" class="headerlink" title="7-1、快照功能"></a><strong><font color='#10c300'>7-1、快照功能</font></strong></h4><p>Hyper-V 支持虚拟机快照（检查点）功能，可以保存虚拟机的某个时刻的状态。在进行重要操作前创建快照，出现问题时可以快速恢复。</p><p><strong>创建快照：</strong> 在虚拟机上右键选择”<strong>检查点</strong>“。</p><br><h4 id="7-2、导出和导入虚拟机"><a href="#7-2、导出和导入虚拟机" class="headerlink" title="7-2、导出和导入虚拟机"></a><strong><font color='#10c300'>7-2、导出和导入虚拟机</font></strong></h4><p>可以导出虚拟机用于备份或迁移到其他主机：</p><p><strong>导出：</strong> 右键虚拟机 &gt; “<strong>导出</strong>“ &gt; 选择导出路径</p><p><strong>导入：</strong> 在 Hyper-V 管理器中选择”<strong>导入虚拟机</strong>“，选择导出的虚拟机文件夹。</p><br><h4 id="7-3、增强会话模式"><a href="#7-3、增强会话模式" class="headerlink" title="7-3、增强会话模式"></a><strong><font color='#10c300'>7-3、增强会话模式</font></strong></h4><p>启用增强会话模式可以获得更好的用户体验，支持剪贴板共享、文件拖放、USB 重定向等功能。</p><p>在虚拟机设置中启用”<strong>增强会话模式</strong>“选项即可。</p><br>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、Hyper-V简介&quot;&gt;&lt;a href=&quot;#一、Hyper-V简介&quot; class=&quot;headerlink&quot; title=&quot;一、Hyper-V简介 &quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、Hyper-V简介 &lt;/font&gt;&lt;/stron</summary>
      
    
    
    
    <category term="后端与运维" scheme="http://example.com/categories/%E5%90%8E%E7%AB%AF%E4%B8%8E%E8%BF%90%E7%BB%B4/"/>
    
    
    <category term="虚拟机" scheme="http://example.com/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
    
    <category term="Hyper-V" scheme="http://example.com/tags/Hyper-V/"/>
    
    <category term="Windows" scheme="http://example.com/tags/Windows/"/>
    
  </entry>
  
  <entry>
    <title>Claude Code 安装与配置指南</title>
    <link href="http://example.com/posts/claudecodeinstall.html"/>
    <id>http://example.com/posts/claudecodeinstall.html</id>
    <published>2026-02-23T01:30:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、系统要求-Prerequisites"><a href="#一、系统要求-Prerequisites" class="headerlink" title="一、系统要求 (Prerequisites) "></a><strong><font color='red'>一、系统要求 (Prerequisites) </font></strong></h3><p>在开始之前，请确保你的系统满足以下要求：</p><h4 id="1-1、硬件与系统"><a href="#1-1、硬件与系统" class="headerlink" title="1-1、硬件与系统"></a><strong><font color='#10c300'>1-1、硬件与系统</font></strong></h4><table><thead><tr><th align="left">项目</th><th align="left">要求</th></tr></thead><tbody><tr><td align="left"><strong>操作系统</strong></td><td align="left">macOS 13.0+<br>Ubuntu 20.04+ &#x2F; Debian 10+<br>Windows 10+ (需配合 WSL 1, WSL 2 或 Git for Windows)</td></tr><tr><td align="left"><strong>硬件</strong></td><td align="left">至少 4 GB RAM</td></tr><tr><td align="left"><strong>Shell环境</strong></td><td align="left">推荐使用 Bash 或 Zsh</td></tr></tbody></table><h4 id="1-2、账号与网络"><a href="#1-2、账号与网络" class="headerlink" title="1-2、账号与网络"></a><strong><font color='#10c300'>1-2、账号与网络</font></strong></h4><ul><li><strong>账号要求</strong>：需要 <strong>Claude Pro</strong> 或 <strong>Team</strong> 订阅才能使用 Claude Code</li><li><strong>网络</strong>：稳定的互联网连接（中国大陆用户需特殊网络配置，详见第五章）</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260130012622312.png" alt="SR-IOV设置示意图"></p><br><h3 id="二、安装步骤-Installation"><a href="#二、安装步骤-Installation" class="headerlink" title="二、安装步骤 (Installation) "></a><strong><font color='red'>二、安装步骤 (Installation) </font></strong></h3><p>根据你的操作系统选择对应的安装命令。</p><h4 id="2-1、macOS-Linux-WSL"><a href="#2-1、macOS-Linux-WSL" class="headerlink" title="2-1、macOS, Linux, WSL"></a><strong><font color='#10c300'>2-1、macOS, Linux, WSL</font></strong></h4><p>在终端中执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://claude.ai/install.sh | bash</span><br></pre></td></tr></table></figure><br><h4 id="2-2、Windows"><a href="#2-2、Windows" class="headerlink" title="2-2、Windows"></a><strong><font color='#10c300'>2-2、Windows</font></strong></h4><h5 id="1）PowerShell-推荐"><a href="#1）PowerShell-推荐" class="headerlink" title="1）PowerShell (推荐)"></a><strong><font color='cornflowerblue'>1）PowerShell (推荐)</font></strong></h5><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://claude.ai/install.ps1 | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure><h5 id="2）CMD"><a href="#2）CMD" class="headerlink" title="2）CMD"></a><strong><font color='cornflowerblue'>2）CMD</font></strong></h5><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://claude.ai/install.<span class="built_in">cmd</span> -o install.<span class="built_in">cmd</span> &amp;&amp; install.<span class="built_in">cmd</span> &amp;&amp; <span class="built_in">del</span> install.<span class="built_in">cmd</span></span><br></pre></td></tr></table></figure><blockquote><p><strong>⚠️ 网络注意事项：</strong><br>由于网络环境限制，国内用户在安装过程中可能会失败。<br><strong>解决方案</strong>：请确保开启网络代理工具，并启用 <strong>TUN 模式</strong> 以确保终端流量被正确代理。</p></blockquote><br><h3 id="三、环境配置-Configuration"><a href="#三、环境配置-Configuration" class="headerlink" title="三、环境配置 (Configuration) "></a><strong><font color='red'>三、环境配置 (Configuration) </font></strong></h3><h4 id="3-1、配置环境变量"><a href="#3-1、配置环境变量" class="headerlink" title="3-1、配置环境变量"></a><strong><font color='#10c300'>3-1、配置环境变量</font></strong></h4><p>安装完成后，如果系统提示无法识别 <code>claude</code> 命令，你需要手动配置环境变量。</p><ol><li><p><strong>打开系统属性</strong>：<br>按下 <code>Win + R</code>，输入 <code>sysdm.cpl</code> 并回车（或搜索“编辑系统环境变量”）。</p></li><li><p><strong>添加路径</strong>：<br>点击“高级”选项卡 -&gt; “环境变量”。在 <code>Path</code> 中添加 Claude Code 的安装路径。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260130012118216.png" alt="环境变量配置示例"></p></li></ol><br><h4 id="3-2、验证安装"><a href="#3-2、验证安装" class="headerlink" title="3-2、验证安装"></a><strong><font color='#10c300'>3-2、验证安装</font></strong></h4><p>打开新的终端窗口，执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude -v</span><br></pre></td></tr></table></figure><p>如果显示版本号，则表示配置成功。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260130012255180.png" alt="版本验证"></p><br><h3 id="四、登录与初始化-Initialization"><a href="#四、登录与初始化-Initialization" class="headerlink" title="四、登录与初始化 (Initialization) "></a><strong><font color='red'>四、登录与初始化 (Initialization) </font></strong></h3><h4 id="4-1、启动登录"><a href="#4-1、启动登录" class="headerlink" title="4-1、启动登录"></a><strong><font color='#10c300'>4-1、启动登录</font></strong></h4><p>在终端执行：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude auth login</span><br></pre></td></tr></table></figure><h4 id="4-2、配置偏好"><a href="#4-2、配置偏好" class="headerlink" title="4-2、配置偏好"></a><strong><font color='#10c300'>4-2、配置偏好</font></strong></h4><ul><li><p><strong>代码风格</strong>：按需选择或直接回车保持默认。<br><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260130013315135.png" alt="代码风格选择"></p></li><li><p><strong>登录方式</strong>：选择默认方式（通常是浏览器验证），浏览器会自动打开授权页面，确认授权即可。<br><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260130013435258.png" alt="官网授权"></p></li></ul><br><h3 id="五、常见问题排查"><a href="#五、常见问题排查" class="headerlink" title="五、常见问题排查 "></a><strong><font color='red'>五、常见问题排查 </font></strong></h3><h4 id="5-1、TUN模式无法上网-DNS-错误"><a href="#5-1、TUN模式无法上网-DNS-错误" class="headerlink" title="5-1、TUN模式无法上网 &#x2F; DNS 错误"></a><strong><font color='#10c300'>5-1、TUN模式无法上网 &#x2F; DNS 错误</font></strong></h4><p><strong>现象描述</strong>：<br>开启代理软件的 TUN 模式后，网络连接断开，但系统代理模式下正常。日志显示 <code>all DNS requests failed</code> 或 <code>tcp4 failed</code>。</p><p><strong>原因分析</strong>：<br>这通常与网卡设置有关，特别是网卡是否支持或开启了 <strong>SR-IOV</strong>（单根输入&#x2F;输出虚拟化）。</p><br><h4 id="5-2、解决方案（以-ROG-主板-Bios-为例）"><a href="#5-2、解决方案（以-ROG-主板-Bios-为例）" class="headerlink" title="5-2、解决方案（以 ROG 主板 Bios 为例）"></a><strong><font color='#10c300'>5-2、解决方案（以 ROG 主板 Bios 为例）</font></strong></h4><ol><li>重启电脑，进入 BIOS (F2 或 Del)。</li><li>进入 <strong>Advanced Mode (高级模式)</strong> -&gt; <strong>Advanced (高级)</strong>。</li><li>进入 <strong>PCI Subsystem Settings</strong> 菜单。</li><li>将 <strong>SR-IOV Support</strong> 开启 (<strong>Enabled</strong>)。</li><li>F10 保存并重启。</li></ol><br><h3 id="六、终端中使用-Claude-Code-接入阿里云百炼-GLM-5"><a href="#六、终端中使用-Claude-Code-接入阿里云百炼-GLM-5" class="headerlink" title="六、终端中使用 Claude Code 接入阿里云百炼 GLM-5"></a><strong><font color='red'>六、终端中使用 Claude Code 接入阿里云百炼 GLM-5</font></strong></h3><p>详细说明了在终端（CLI）中使用官网安装的 <code>claude</code> 命令行工具，并配置阿里云百炼 <strong>Coding Plan 专属 API Key</strong> 以原生调用 <strong>GLM-5</strong> 模型的完整步骤。这使得你无需依赖 Anthropic 官网账单便能享受到强大的 AI 辅助编程能力。</p><h4 id="6-1、前提条件"><a href="#6-1、前提条件" class="headerlink" title="6.1、前提条件"></a><strong><font color='#10c300'>6.1、前提条件</font></strong></h4><ol><li><p><strong>已经全局安装 Claude Code 核心 CL</strong></p></li><li><p><strong>跳过 Anthropic 登录</strong>：如果首次启动强制要求登录 Anthropic 账户，找到 <code>C:\Users\你的用户名\.claude.json</code> 文件，将内容设置为</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;hasCompletedOnboarding&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure></li><li><p><strong>获取 API Key</strong>：你需要进入<a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/efm/coding_plan">阿里云百炼控制台 Coding Plan 页面</a>订阅并获取专属的 API Key（格式为 <code>sk-sp-****</code>）。</p></li></ol><h4 id="6-2、全局环境变量配置（永久生效）"><a href="#6-2、全局环境变量配置（永久生效）" class="headerlink" title="6.2、全局环境变量配置（永久生效）"></a><strong><font color='#10c300'>6.2、全局环境变量配置（永久生效）</font></strong></h4><p>Claude Code 在无界面认证时需要读取终端的环境变量以发起请求。为了防止<strong>每次重启终端后配置丢失</strong>，建议使用 PowerShell 修改系统级（或用户级）环境变量。</p><p><strong><font color='cornflowerblue'>第一步：打开 PowerShell 终端运行配置命令</font></strong></p><p>粘贴并运行以下完整的环境变量设置命令：</p><figure class="highlight powershell"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 1. 配置你的 Coding Plan 专属 API Key（请替换为你自己的 key）</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;ANTHROPIC_API_KEY&quot;</span>, <span class="string">&quot;sk-sp-75af544fcd5d4cd9bf2c7e0148555a5a&quot;</span>, [<span class="type">EnvironmentVariableTarget</span>]::User)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 2. 配置 Coding Plan 专属的 Base URL</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;ANTHROPIC_BASE_URL&quot;</span>, <span class="string">&quot;https://coding.dashscope.aliyuncs.com/apps/anthropic&quot;</span>, [<span class="type">EnvironmentVariableTarget</span>]::User)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 3. 智能模型调度配置（推荐）</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 配置后无需手动切换模型，Claude Code 会根据任务难度自动在这 3 个模型间切换，帮你节省额度和提高加载速度：</span></span><br><span class="line"><span class="comment"># 复杂度极高的任务（如分析整个项目架构） -&gt; 使用最强模型 GLM-5</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;ANTHROPIC_DEFAULT_OPUS_MODEL&quot;</span>, <span class="string">&quot;glm-5&quot;</span>, [<span class="type">EnvironmentVariableTarget</span>]::User)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 日常编写大段代码、实现功能的核心任务 -&gt; 使用主模型 GLM-5</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;ANTHROPIC_DEFAULT_SONNET_MODEL&quot;</span>, <span class="string">&quot;glm-5&quot;</span>, [<span class="type">EnvironmentVariableTarget</span>]::User)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 简单的文件检索、拼写检查等打杂任务 -&gt; 使用精简极速的 qwen3-coder-next</span></span><br><span class="line">[<span class="type">Environment</span>]::SetEnvironmentVariable(<span class="string">&quot;ANTHROPIC_DEFAULT_HAIKU_MODEL&quot;</span>, <span class="string">&quot;qwen3-coder-next&quot;</span>, [<span class="type">EnvironmentVariableTarget</span>]::User)</span><br><span class="line"></span><br><span class="line"><span class="comment"># （注：配置了上方 3 个细分变量后，不要再配置全局的 ANTHROPIC_MODEL，以免被强行覆盖）</span></span><br></pre></td></tr></table></figure><p><strong>注意：</strong> 这些变量已被永久写入操作系统的<strong>用户环境变量</strong>中。</p><p><strong><font color='cornflowerblue'>第二步：彻底重启终端（非常关键！）</font></strong></p><p>由于当前正在运行的 PowerShell 或内嵌终端（如 VS Code 终端）<strong>不会自动重载</strong>底层外部环境变量：</p><ol><li>请完全关闭当前的控制台&#x2F;终端窗口（如果你在 VS Code 里面，请完全关闭整个 VS Code 软件）。</li><li><strong>重新打开一个新的终端窗口</strong>。</li></ol><p>最佳方式可直接重启电脑</p><p><strong><font color='cornflowerblue'>第三步：运行与验证</font></strong></p><p>在重新打开的新终端中进入你的代码项目目录。</p><p><strong>1.  验证变量生效</strong></p><p>在终端中执行测试命令检查：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">echo</span> <span class="variable">$env:ANTHROPIC_API_KEY</span></span><br></pre></td></tr></table></figure><p>如果成功输出你的专属 API Key（如 <code>sk-sp-75af5...</code>），说明配置已经<strong>永久生效</strong>！</p><p><strong>2.  启动终端版 Claude Code</strong></p><p>在终端直接输入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">claude</span><br></pre></td></tr></table></figure><p>此时你将跳过复杂的 Anthropic 官方验证拦截，直接在终端里启动对话交互模式，开始向 GLM-5 提问和辅助阅读源码、生成文件。</p><h4 id="6-3、（可选）体验项目级局部配置"><a href="#6-3、（可选）体验项目级局部配置" class="headerlink" title="6.3、（可选）体验项目级局部配置"></a><strong><font color='#10c300'>6.3、（可选）体验项目级局部配置</font></strong></h4><p>如果你不想污染系统环境变量，或者想在不同项目间切换不同模型，Claude Code 也支持在你的项目根目录新建一个名为 <code>settings.json</code>（或全局的 <code>~/.claude.json</code>）并写入如下配置：（建议直接配置在 <code>~/.claude.json</code>中，这是目前我自己使用的方式）</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;env&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">        <span class="attr">&quot;ANTHROPIC_API_KEY&quot;</span><span class="punctuation">:</span> <span class="string">&quot;sk-sp-75af544fcd5d4cd9bf2c7e0148555a5a&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="attr">&quot;ANTHROPIC_BASE_URL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;https://coding.dashscope.aliyuncs.com/apps/anthropic&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// 配置后无需手动切换模型，Claude Code会根据任务难度自动在这3个模型间切换，帮你节省额度和提高加载速度</span></span><br><span class="line">        <span class="comment">// 复杂度极高的任务（如分析整个项目架构） -&gt; 使用最强模型 GLM-5</span></span><br><span class="line">        <span class="attr">&quot;ANTHROPIC_DEFAULT_OPUS_MODEL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;glm-5&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// 日常编写大段代码、实现功能的核心任务 -&gt; 使用主模型 GLM-5</span></span><br><span class="line">        <span class="attr">&quot;ANTHROPIC_DEFAULT_SONNET_MODEL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;glm-5&quot;</span><span class="punctuation">,</span></span><br><span class="line">        <span class="comment">// 简单的文件检索、拼写检查等打杂任务 -&gt; 使用精简极速的 qwen3-coder-next</span></span><br><span class="line">        <span class="attr">&quot;ANTHROPIC_DEFAULT_HAIKU_MODEL&quot;</span><span class="punctuation">:</span> <span class="string">&quot;qwen3-coder-next&quot;</span></span><br><span class="line">    <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hasCompletedOnboarding&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>提示： <code>hasCompletedOnboarding: true</code> 用于跳过由于没有官方账号造成的强制登录流程。</p><h4 id="6-4、常见问题排查"><a href="#6-4、常见问题排查" class="headerlink" title="6.4、常见问题排查"></a><strong><font color='#10c300'>6.4、常见问题排查</font></strong></h4><p><strong>Q1：输入 <code>claude</code> 依然提示 <code>Could not resolve authentication method...</code>？</strong></p><ul><li><p><strong>原因：</strong> VS Code 终端仍在使用旧的环境变量进程缓存。</p></li><li><p><strong>解决：</strong> 必须完全退出并关闭 VS Code 主程序，或者在当前终端立刻运行一条<strong>临时热加载命令</strong>：</p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$env:ANTHROPIC_API_KEY</span>=<span class="string">&quot;你的API_KEY&quot;</span>; <span class="variable">$env:ANTHROPIC_BASE_URL</span>=<span class="string">&quot;https://coding.dashscope.aliyuncs.com/apps/anthropic&quot;</span>; <span class="variable">$env:ANTHROPIC_MODEL</span>=<span class="string">&quot;glm-5&quot;</span>; claude</span><br></pre></td></tr></table></figure></li></ul><p><strong>Q2：想切换到其他支持的模型？</strong></p><ul><li>阿里云百炼 Coding Plan 还支持 <code>qwen3.5-plus</code>, <code>kimi-k2.5</code>, <code>qwen3-coder-next</code> 等。</li><li>在运行中的 <code>claude</code> 会话内，只需输入 <code>/model &lt;模型名&gt;</code> 即可迅速无缝切换。例如：<code>/model qwen3.5-plus</code>。</li></ul>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、系统要求-Prerequisites&quot;&gt;&lt;a href=&quot;#一、系统要求-Prerequisites&quot; class=&quot;headerlink&quot; title=&quot;一、系统要求 (Prerequisites) &quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;re</summary>
      
    
    
    
    <category term="工具" scheme="http://example.com/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="AI" scheme="http://example.com/tags/AI/"/>
    
    <category term="Claude" scheme="http://example.com/tags/Claude/"/>
    
  </entry>
  
  <entry>
    <title>Git使用SSH拉取代码配置指南</title>
    <link href="http://example.com/posts/65464545tryt435.html"/>
    <id>http://example.com/posts/65464545tryt435.html</id>
    <published>2026-02-23T00:00:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、配置-Git-用户信息"><a href="#一、配置-Git-用户信息" class="headerlink" title="一、配置 Git 用户信息"></a><strong><font color='red'>一、配置 Git 用户信息</font></strong></h3><p>这些信息会记录在**每次 Git 提交（commit）**中，用于标识”谁提交了这段代码”。</p><figure class="highlight bash"><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">git config --global user.name <span class="string">&#x27;zhenghong&#x27;</span></span><br><span class="line">git config --global user.email <span class="string">&#x27;772198520@qq.com&#x27;</span></span><br></pre></td></tr></table></figure><br><p><strong>验证当前配置</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git config --global --list</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119231528481.png" alt="image-20260119231528481"></p><br><h3 id="二、生成-SSH-密钥"><a href="#二、生成-SSH-密钥" class="headerlink" title="二、生成 SSH 密钥"></a><strong><font color='red'>二、生成 SSH 密钥</font></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">&#x27;772198520@qq.com&#x27;</span></span><br><span class="line"><span class="comment"># 或使用更安全的 ed25519 算法（推荐）</span></span><br><span class="line">ssh-keygen -t ed25519 -C <span class="string">&#x27;772198520@qq.com&#x27;</span></span><br></pre></td></tr></table></figure><p>连续按 3 次回车（使用默认路径和空密码）</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119231535380.png" alt="image-20260119231535380"></p><br><h3 id="三、查看并复制公钥"><a href="#三、查看并复制公钥" class="headerlink" title="三、查看并复制公钥"></a><strong><font color='red'>三、查看并复制公钥</font></strong></h3><p>默认生成秘钥的位置C:\Users\zheng_hb.ssh</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">type</span> C:\Users\zheng_hb\.ssh\id_rsa.pub</span><br><span class="line"><span class="comment"># 或（如果用 ed25519）</span></span><br><span class="line"><span class="built_in">type</span> C:\Users\zheng_hb\.ssh\id_ed25519.pub</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119232637916.png" alt="image-20260119232637916"></p><br><h3 id="四、添加公钥到-Git-平台"><a href="#四、添加公钥到-Git-平台" class="headerlink" title="四、添加公钥到 Git 平台"></a><strong><font color='red'>四、添加公钥到 Git 平台</font></strong></h3><p><strong>以GitHub为例</strong></p><ol><li>登录 GitHub</li><li>点击头像 → Settings → SSH and GPG keys</li><li>点击 “New SSH key”</li><li>粘贴公钥内容，保存</li></ol><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119232647077.png" alt="image-20260119232647077"></p><br><h3 id="五、测试连接"><a href="#五、测试连接" class="headerlink" title="五、测试连接"></a><strong><font color='red'>五、测试连接</font></strong></h3><figure class="highlight bash"><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"># GitHub</span></span><br><span class="line">ssh -T git@github.com</span><br><span class="line"></span><br><span class="line"><span class="comment"># GitLab</span></span><br><span class="line">ssh -T git@gitlab.com</span><br><span class="line"></span><br><span class="line"><span class="comment"># Gitee</span></span><br><span class="line">ssh -T git@gitee.com            </span><br></pre></td></tr></table></figure><p>成功返回示例：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Hi zhbCloud! You<span class="string">&#x27;ve successfully authenticated...</span></span><br></pre></td></tr></table></figure><br><h3 id="六、克隆代码"><a href="#六、克隆代码" class="headerlink" title="六、克隆代码"></a><strong><font color='red'>六、克隆代码</font></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git <span class="built_in">clone</span> git@github.com:username/repository.git</span><br></pre></td></tr></table></figure><br><h3 id="七、如果之前克隆的是-HTTPS-仓库"><a href="#七、如果之前克隆的是-HTTPS-仓库" class="headerlink" title="七、如果之前克隆的是 HTTPS 仓库"></a><strong><font color='red'>七、如果之前克隆的是 HTTPS 仓库</font></strong></h3><p>需要将远程地址改为 SSH 格式</p><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 查看当前远程地址</span></span><br><span class="line">git remote -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查看结果</span></span><br><span class="line">origin  https://github.com/zhbCloud/仓库名.git (fetch)</span><br><span class="line">origin  https://github.com/zhbCloud/仓库名.git (push)</span><br><span class="line"></span><br><span class="line"><span class="comment"># 改为 SSH</span></span><br><span class="line">git remote set-url origin git@github.com:zhbCloud/仓库名.git</span><br><span class="line"></span><br><span class="line"><span class="comment"># 验证修改</span></span><br><span class="line">git remote -v</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改成功</span></span><br><span class="line">origin  git@github.com:zhbCloud/仓库名.git (fetch)</span><br><span class="line">origin  git@github.com:zhbCloud/仓库名.git (push)</span><br></pre></td></tr></table></figure><br><p><strong>TortoiseGit中修改</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119232657035.png" alt="image-20260119232657035"></p><br><h3 id="八、解决-TortoiseGit-SSH-认证问题"><a href="#八、解决-TortoiseGit-SSH-认证问题" class="headerlink" title="八、解决 TortoiseGit SSH 认证问题"></a><strong><font color='red'>八、解决 TortoiseGit SSH 认证问题</font></strong></h3><p>TortoiseGit 使用的是 <strong>PuTTY</strong> 的 SSH 客户端，而命令行用的是 <strong>OpenSSH</strong>，它们的密钥格式不兼容！</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119232701820.png" alt="image-20260119232701820"></p><br><p><strong>让 TortoiseGit 使用 OpenSSH</strong></p><figure class="highlight bash"><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"># 配置 TortoiseGit 使用 Git 的 SSH</span></span><br><span class="line">右键 → TortoiseGit → Settings</span><br><span class="line">→ Network</span><br><span class="line">→ SSH Client: 修改为 Git 的 ssh.exe 路径</span><br></pre></td></tr></table></figure><br><p><strong>常见ssh.exe路径</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">C:\Program Files\Git\usr\bin\ssh.exe</span><br><span class="line"><span class="comment"># 或者直接写</span></span><br><span class="line">ssh.exe</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260119232709254.png" alt="image-20260119232709254"></p><br>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、配置-Git-用户信息&quot;&gt;&lt;a href=&quot;#一、配置-Git-用户信息&quot; class=&quot;headerlink&quot; title=&quot;一、配置 Git 用户信息&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、配置 Git 用户信息&lt;/font&gt;&lt;</summary>
      
    
    
    
    <category term="工具" scheme="http://example.com/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="Git" scheme="http://example.com/tags/Git/"/>
    
    <category term="SSH" scheme="http://example.com/tags/SSH/"/>
    
    <category term="版本控制" scheme="http://example.com/tags/%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6/"/>
    
  </entry>
  
  <entry>
    <title>React MobX 全面指南 (MobX 6)</title>
    <link href="http://example.com/posts/mobx-guide-2026.html"/>
    <id>http://example.com/posts/mobx-guide-2026.html</id>
    <published>2026-02-22T15:50:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h1 id="React-MobX-全面指南-现代版-MobX-6"><a href="#React-MobX-全面指南-现代版-MobX-6" class="headerlink" title="React MobX 全面指南 (现代版 MobX 6)"></a>React MobX 全面指南 (现代版 MobX 6)</h1><blockquote><p>🚀 <strong>MobX</strong> 是一个经过战火洗礼的库，它让状态管理变得简单、可扩展。与 Redux 的不可变逻辑不同，MobX 拥抱“响应式编程”，通过透明的函数式响应让状态与 UI 自动同步。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E5%9F%BA%E7%A1%80%E7%AF%87mobx-%E6%A0%B8%E5%BF%83%E4%B8%8E%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B">基础篇：MobX 核心与快速上手</a></li><li><a href="#%E4%BA%8C%E8%BF%9B%E9%98%B6%E7%AF%87%E5%BC%82%E6%AD%A5%E5%A4%84%E7%90%86%E4%B8%8Ereact-%E6%B7%B1%E5%BA%A6%E9%9B%86%E6%88%90">进阶篇：异步处理与 React 深度集成</a></li><li><a href="#%E4%B8%89%E9%AB%98%E7%BA%A7%E7%AF%87%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96%E4%B8%8E%E6%9E%B6%E6%9E%84%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5">高级篇：性能优化与架构最佳实践</a></li><li><a href="#%E5%9B%9B%E6%80%BB%E7%BB%93mobx-vs-redux-%E5%A6%82%E4%BD%95%E9%80%89%E6%8B%A9">总结：MobX vs Redux 如何选择</a></li></ol><br><h2 id="一、-基础篇：MobX-核心与快速上手"><a href="#一、-基础篇：MobX-核心与快速上手" class="headerlink" title="一、 基础篇：MobX 核心与快速上手"></a><strong>一、 基础篇：MobX 核心与快速上手</strong></h2><h3 id="1-1-MobX-的核心哲理"><a href="#1-1-MobX-的核心哲理" class="headerlink" title="1.1 MobX 的核心哲理"></a><strong><font color='red'>1.1 MobX 的核心哲理</font></strong></h3><p>MobX 的核心思想是：<strong>任何源自应用状态的东西都应该自动地派生出来。</strong></p><p>想象一下 Excel 表格：</p><ul><li><strong>Observable (状态)</strong>：单元格里的原始数据。</li><li><strong>Computed (派生状态)</strong>：由公式计算出的结果单元格。</li><li><strong>Reactions (响应)</strong>：当数据变化时，Excel 自动重新计算公式并更新图表。</li><li><strong>Actions (动作)</strong>：你手动修改单元格数据的行为。</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260223113244196.png" alt="image-20260223113244196"></p><br><h3 id="1-2-安装"><a href="#1-2-安装" class="headerlink" title="1.2 安装"></a><strong><font color='red'>1.2 安装</font></strong></h3><p>在现代 React 项目中，你需要安装 <code>mobx</code> 核心库和 <code>mobx-react-lite</code>（专为函数式组件设计）。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install mobx mobx-react-lite</span><br></pre></td></tr></table></figure><br><h3 id="1-3-核心-API-MobX-6"><a href="#1-3-核心-API-MobX-6" class="headerlink" title="1.3 核心 API (MobX 6)"></a><strong><font color='red'>1.3 核心 API (MobX 6)</font></strong></h3><p>在 MobX 6 中，装饰器语法已不再是必需，推荐使用 <code>makeAutoObservable</code>。</p><h4 id="1）使用-makeAutoObservable"><a href="#1）使用-makeAutoObservable" class="headerlink" title="1）使用 makeAutoObservable"></a><strong><font color='#10c300'>1）使用 makeAutoObservable</font></strong></h4><p>这是目前最简单、最推荐的写法。它能自动推断属性、方法和 getter。</p><p><code>src/store/CounterStore.js</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><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">import</span> &#123; makeAutoObservable &#125; <span class="keyword">from</span> <span class="string">&quot;mobx&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">CounterStore</span> &#123;</span><br><span class="line">  count = <span class="number">0</span>; <span class="comment">// Observable: 状态</span></span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 自动将属性设为 observable，方法设为 action，getter 设为 computed</span></span><br><span class="line">    <span class="title function_">makeAutoObservable</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Action: 修改状态的方法</span></span><br><span class="line">  <span class="title function_">increment</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span>++;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">decrement</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">count</span>--;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// Computed: 派生状态（具有缓存特性）</span></span><br><span class="line">  <span class="keyword">get</span> <span class="title function_">doubleCount</span>() &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="variable language_">this</span>.<span class="property">count</span> * <span class="number">2</span>;</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">export</span> <span class="keyword">const</span> counterStore = <span class="keyword">new</span> <span class="title class_">CounterStore</span>();</span><br></pre></td></tr></table></figure><hr><br><h3 id="1-4-在-React-组件中使用"><a href="#1-4-在-React-组件中使用" class="headerlink" title="1.4 在 React 组件中使用"></a><strong><font color='red'>1.4 在 React 组件中使用</font></strong></h3><p>要在 React 中响应 MobX 状态的变化，必须使用 <code>observer</code> 高阶组件包裹你的组件。</p><p><code>src/App.jsx</code>:</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; observer &#125; <span class="keyword">from</span> <span class="string">&quot;mobx-react-lite&quot;</span>; <span class="comment">// 必须引入</span></span><br><span class="line"><span class="keyword">import</span> &#123; counterStore &#125; <span class="keyword">from</span> <span class="string">&quot;./store/CounterStore&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 observer 包裹组件，使其成为“观察者”</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">App</span> = <span class="title function_">observer</span>(<span class="function">() =&gt;</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> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">textAlign:</span> &quot;<span class="attr">center</span>&quot;, <span class="attr">marginTop:</span> &quot;<span class="attr">50px</span>&quot; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>MobX 计数器<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>当前数值: &#123;counterStore.count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>两倍数值 (Computed): &#123;counterStore.doubleCount&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> counterStore.increment()&#125;&gt;增加<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> counterStore.decrement()&#125;&gt;减少<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><blockquote><p>[!IMPORTANT]<br><strong>切记</strong>：如果没有用 <code>observer</code> 包裹组件，即使 Store 中的状态变了，组件也不会重新渲染！</p></blockquote><hr><br><h2 id="二、-进阶篇：异步处理与-React-深度集成"><a href="#二、-进阶篇：异步处理与-React-深度集成" class="headerlink" title="二、 进阶篇：异步处理与 React 深度集成"></a><strong>二、 进阶篇：异步处理与 React 深度集成</strong></h2><h3 id="2-1-异步-Action-与-runInAction"><a href="#2-1-异步-Action-与-runInAction" class="headerlink" title="2.1 异步 Action 与 runInAction"></a><strong><font color='red'>2.1 异步 Action 与 runInAction</font></strong></h3><p>在 MobX 默认的<strong>严格模式</strong>下，所有对状态的修改都必须在 <code>action</code> 中完成。但是，<code>await</code> 之后的回调并不在 action 的作用域内，因此需要使用 <code>runInAction</code>。</p><h4 id="1）实战：模拟-API-获取数据"><a href="#1）实战：模拟-API-获取数据" class="headerlink" title="1）实战：模拟 API 获取数据"></a><strong><font color='#10c300'>1）实战：模拟 API 获取数据</font></strong></h4><p><code>src/store/UserStore.js</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; makeAutoObservable, runInAction &#125; <span class="keyword">from</span> <span class="string">&quot;mobx&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">UserStore</span> &#123;</span><br><span class="line">  userInfo = <span class="literal">null</span>;</span><br><span class="line">  loading = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">makeAutoObservable</span>(<span class="variable language_">this</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="keyword">async</span> <span class="title function_">fetchUser</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">loading</span> = <span class="literal">true</span>;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;https://api.github.com/users/octocat&quot;</span>);</span><br><span class="line">      <span class="keyword">const</span> data = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line"></span><br><span class="line">      <span class="comment">// 关键点：await 之后的所有状态修改必须包裹在 runInAction 中</span></span><br><span class="line">      <span class="title function_">runInAction</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">userInfo</span> = data;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">loading</span> = <span class="literal">false</span>;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">      <span class="title function_">runInAction</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="property">loading</span> = <span class="literal">false</span>;</span><br><span class="line">      &#125;);</span><br><span class="line">    &#125;</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">export</span> <span class="keyword">const</span> userStore = <span class="keyword">new</span> <span class="title class_">UserStore</span>();</span><br></pre></td></tr></table></figure><br><h3 id="2-2-使用-React-Context-管理多个-Store-RootStore-模式"><a href="#2-2-使用-React-Context-管理多个-Store-RootStore-模式" class="headerlink" title="2.2 使用 React Context 管理多个 Store (RootStore 模式)"></a><strong><font color='red'>2.2 使用 React Context 管理多个 Store (RootStore 模式)</font></strong></h3><p>在大型项目中，建议创建一个 <code>RootStore</code> 来统一管理所有子 Store，并通过 React Context 注入，以便在组件中方便获取。</p><h4 id="1）第一步：创建-RootStore"><a href="#1）第一步：创建-RootStore" class="headerlink" title="1）第一步：创建 RootStore"></a><strong><font color='#10c300'>1）第一步：创建 RootStore</font></strong></h4><p><code>src/store/index.js</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createContext, useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; counterStore &#125; <span class="keyword">from</span> <span class="string">&quot;./CounterStore&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; userStore &#125; <span class="keyword">from</span> <span class="string">&quot;./UserStore&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">RootStore</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">counterStore</span> = counterStore;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">userStore</span> = userStore;</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">const</span> rootStore = <span class="keyword">new</span> <span class="title class_">RootStore</span>();</span><br><span class="line"><span class="comment">// 创建 Context</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">RootStoreContext</span> = <span class="title function_">createContext</span>(rootStore);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义 Hook，方便组件使用</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">useStore</span> = (<span class="params"></span>) =&gt; <span class="title function_">useContext</span>(<span class="title class_">RootStoreContext</span>);</span><br></pre></td></tr></table></figure><h4 id="2）第二步：在组件中使用"><a href="#2）第二步：在组件中使用" class="headerlink" title="2）第二步：在组件中使用"></a><strong><font color='#10c300'>2）第二步：在组件中使用</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; observer &#125; <span class="keyword">from</span> <span class="string">&quot;mobx-react-lite&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useStore &#125; <span class="keyword">from</span> <span class="string">&quot;../store&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">UserProfile</span> = <span class="title function_">observer</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; userStore, counterStore &#125; = <span class="title function_">useStore</span>(); <span class="comment">// 从 RootStore 中获取</span></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">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> userStore.fetchUser()&#125;&gt;加载用户信息<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;userStore.loading &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>加载中...<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;userStore.userInfo &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>用户名: &#123;userStore.userInfo.login&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>同时也拿到了计数器: &#123;counterStore.count&#125;<span class="tag">&lt;/<span class="name">p</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><hr><br><h2 id="三、-高级篇：性能优化与架构最佳实践"><a href="#三、-高级篇：性能优化与架构最佳实践" class="headerlink" title="三、 高级篇：性能优化与架构最佳实践"></a><strong>三、 高级篇：性能优化与架构最佳实践</strong></h2><h3 id="3-1-细粒度渲染优化"><a href="#3-1-细粒度渲染优化" class="headerlink" title="3.1 细粒度渲染优化"></a><strong><font color='red'>3.1 细粒度渲染优化</font></strong></h3><p>MobX 的性能优势在于“精准打击”。只有真正读取了被修改属性的 <code>observer</code> 组件才会重渲染。</p><p><strong>优化建议</strong>：将大组件拆分为多个小组件。</p><ul><li>❌ 如果一个长列表在父组件里循环读取属性，任何一个属性变了，整个列表都会重绘。</li><li>✅ 将列表项封装为 <code>observer(Item)</code>，这样只有被修改的那一行会动。</li></ul><br><h3 id="3-2-自动响应：reaction-与-autorun"><a href="#3-2-自动响应：reaction-与-autorun" class="headerlink" title="3.2 自动响应：reaction 与 autorun"></a><strong><font color='red'>3.2 自动响应：reaction 与 autorun</font></strong></h3><p>除了 UI 更新，MobX 还能处理副作用。</p><ul><li><strong><code>autorun</code></strong>：只要依赖的状态变了，就执行。常用于自动保存、日志上报。</li><li><strong><code>reaction</code></strong>：更精确。只有指定的属性变了，才执行。</li></ul><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="keyword">import</span> &#123; reaction &#125; <span class="keyword">from</span> <span class="string">&quot;mobx&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只有当 counterStore.count 发生变化时，才会执行后面的回调</span></span><br><span class="line"><span class="title function_">reaction</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> counterStore.<span class="property">count</span>,</span><br><span class="line">  <span class="function">(<span class="params">count, prevCount</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`数值从 <span class="subst">$&#123;prevCount&#125;</span> 变为了 <span class="subst">$&#123;count&#125;</span>`</span>);</span><br><span class="line">    <span class="keyword">if</span> (count &gt; <span class="number">10</span>) <span class="title function_">alert</span>(<span class="string">&quot;数值过高！&quot;</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h3 id="3-3-避坑指南：不要在-observer-外解构"><a href="#3-3-避坑指南：不要在-observer-外解构" class="headerlink" title="3.3 避坑指南：不要在 observer 外解构"></a><strong><font color='red'>3.3 避坑指南：不要在 observer 外解构</font></strong></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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误写法：在组件体外解构，会失去响应性</span></span><br><span class="line"><span class="keyword">const</span> &#123; count &#125; = counterStore;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyComponent</span> = <span class="title function_">observer</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>; <span class="comment">// 这里永远不会更新</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="keyword">const</span> <span class="title class_">MyComponent</span> = <span class="title function_">observer</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; count &#125; = counterStore; <span class="comment">// OK</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr><br><h2 id="四、-总结：MobX-vs-Redux-如何选择"><a href="#四、-总结：MobX-vs-Redux-如何选择" class="headerlink" title="四、 总结：MobX vs Redux 如何选择"></a><strong>四、 总结：MobX vs Redux 如何选择</strong></h2><table><thead><tr><th align="left">特性</th><th align="left">MobX</th><th align="left">Redux (RTK)</th></tr></thead><tbody><tr><td align="left"><strong>编程范式</strong></td><td align="left">面向对象、响应式</td><td align="left">函数式、不可变数据</td></tr><tr><td align="left"><strong>样板代码</strong></td><td align="left">非常少</td><td align="left">较多（即使有 RTK）</td></tr><tr><td align="left"><strong>学习曲线</strong></td><td align="left">平缓，像写普通 JS 对象</td><td align="left">较陡，Action&#x2F;Reducer 概念多</td></tr><tr><td align="left"><strong>性能</strong></td><td align="left">极其出色，细粒度依赖追踪</td><td align="left">依赖手动优化（Memo&#x2F;Reselect）</td></tr><tr><td align="left"><strong>调试</strong></td><td align="left">较难追踪（黑盒响应）</td><td align="left">非常强大（Redux DevTools 轨迹清晰）</td></tr><tr><td align="left"><strong>推荐场景</strong></td><td align="left">快速开发、复杂 UI 交互、企业后台、游戏</td><td align="left">大型团队协作、对状态流向要求极其严苛的项目</td></tr></tbody></table><hr><p><em>Happy Coding with MobX! 建议结合项目实践多次演练。</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;React-MobX-全面指南-现代版-MobX-6&quot;&gt;&lt;a href=&quot;#React-MobX-全面指南-现代版-MobX-6&quot; class=&quot;headerlink&quot; title=&quot;React MobX 全面指南 (现代版 MobX 6)&quot;&gt;&lt;/a&gt;React </summary>
      
    
    
    
    <category term="前端开发" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="React" scheme="http://example.com/tags/React/"/>
    
    <category term="MobX" scheme="http://example.com/tags/MobX/"/>
    
    <category term="State Management" scheme="http://example.com/tags/State-Management/"/>
    
  </entry>
  
  <entry>
    <title>React Redux 全面指南 (RTK)</title>
    <link href="http://example.com/posts/rtk-guide-2026.html"/>
    <id>http://example.com/posts/rtk-guide-2026.html</id>
    <published>2026-02-22T15:30:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h1 id="React-Redux-全面指南-Redux-Toolkit"><a href="#React-Redux-全面指南-Redux-Toolkit" class="headerlink" title="React Redux 全面指南 (Redux Toolkit)"></a>React Redux 全面指南 (Redux Toolkit)</h1><blockquote><p>📚 本指南旨在带你从零开始 Mastering React Redux，特别是现代标准的 <strong>Redux Toolkit (RTK)</strong> 写法。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E5%9F%BA%E7%A1%80%E7%AF%87rtk-%E6%A0%B8%E5%BF%83%E4%B8%8E%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B">基础篇：RTK 核心与快速上手</a></li><li><a href="#%E4%BA%8C%E8%BF%9B%E9%98%B6%E7%AF%87%E5%A4%8D%E6%9D%82%E7%8A%B6%E6%80%81%E4%B8%8E%E5%BC%82%E6%AD%A5%E9%80%BB%E8%BE%91">进阶篇：复杂状态与异步逻辑</a></li><li><a href="#%E4%B8%89%E9%AB%98%E7%BA%A7%E7%AF%87rtk-query-%E4%B8%8E%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96">高级篇：RTK Query 与性能优化</a></li></ol><br><h2 id="一、-基础篇：RTK-核心与快速上手"><a href="#一、-基础篇：RTK-核心与快速上手" class="headerlink" title="一、 基础篇：RTK 核心与快速上手"></a><strong>一、 基础篇：RTK 核心与快速上手</strong></h2><blockquote><p>[!NOTE]</p><ol><li><strong>Store</strong>：用来存数据的。</li><li><strong>Reducer</strong>：是一个函数，用来处理数据的。</li><li><strong>Action</strong>：是一个具有 <code>type</code> 字段的普通对象，用来描述要进行什么操作。</li><li><strong>Action Creator</strong>：创建并返回 Action 对象的函数。</li><li><strong>Dispatch</strong>：更新 State 的唯一方法，调用 <code>store.dispatch(action)</code>。</li></ol></blockquote><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20260213092945181.png" alt="image-20260213092945181"></p><br><h3 id="1-1-Redux-是什么？"><a href="#1-1-Redux-是什么？" class="headerlink" title="1.1 Redux 是什么？"></a><strong><font color='red'>1.1 Redux 是什么？</font></strong></h3><p>Redux 是一个用于 JavaScript 应用的状态容器，提供可预测的状态管理。</p><ul><li><strong>单一数据源</strong>：应用的所有状态都存储在一个对象树中。</li><li><strong>状态是只读的</strong>：唯一改变状态的方法是触发一个 <strong>Action</strong>。</li><li><strong>使用纯函数修改</strong>：编写 <strong>Reducer</strong> 来描述 Action 如何转换 State。</li></ul><blockquote><p>[!TIP]<br><strong>为什么使用 Redux Toolkit (RTK)？</strong><br>官方推荐！RTK 是现代 Redux 的标准写法。它解决了传统 Redux 配置复杂、样板代码多、需手动添加不可变逻辑等痛点。RTK 内置了 <code>Immer</code>（简化不可变更新）、<code>Thunk</code>（异步）、<code>DevTools</code> 等工具。</p></blockquote><br><h3 id="1-2-安装"><a href="#1-2-安装" class="headerlink" title="1.2 安装"></a><strong><font color='red'>1.2 安装</font></strong></h3><p>使用 Create React App 或 Vite 创建项目后，安装核心依赖：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install @reduxjs/toolkit react-redux</span><br></pre></td></tr></table></figure><br><h3 id="1-3-核心概念与实战-Counter-Example"><a href="#1-3-核心概念与实战-Counter-Example" class="headerlink" title="1.3 核心概念与实战 (Counter Example)"></a><strong><font color='red'>1.3 核心概念与实战 (Counter Example)</font></strong></h3><p>我们将通过一个计数器应用来演示最核心的 API。</p><h4 id="1）第一步：创建-Slice-切片"><a href="#1）第一步：创建-Slice-切片" class="headerlink" title="1）第一步：创建 Slice (切片)"></a><strong><font color='#10c300'>1）第一步：创建 Slice (切片)</font></strong></h4><p><strong>Slice</strong> 是 Redux 逻辑的集合（包含 State，Reducers，Actions）</p><p><code>src\redux\modules\counterSlice.js</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createSlice &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> counterSlice = <span class="title function_">createSlice</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;counter&quot;</span>, <span class="comment">// Slice 名称，用于生成 Action Type 前缀</span></span><br><span class="line">  <span class="attr">initialState</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="number">0</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">reducers</span>: &#123;</span><br><span class="line">    <span class="comment">// Redux Toolkit 允许我们在 reducer 中直接编写&quot;可变&quot;逻辑</span></span><br><span class="line">    <span class="comment">// 它底层使用 Immer 库将 these 操作转换为安全的不可变更新</span></span><br><span class="line">    <span class="attr">increment</span>: <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 获取到的是initialState</span></span><br><span class="line">      state.<span class="property">value</span> += <span class="number">1</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">decrement</span>: <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">value</span> -= <span class="number">1</span>;</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="comment">// Action Payload 在 action.payload 中</span></span><br><span class="line">    <span class="attr">incrementByAmount</span>: <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">      state.<span class="property">value</span> += action.<span class="property">payload</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><br><span class="line"><span class="comment">// 自动生成 Action Creators</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> &#123; increment, decrement, incrementByAmount &#125; = counterSlice.<span class="property">actions</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 导出 Reducer</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> counterSlice.<span class="property">reducer</span>;</span><br></pre></td></tr></table></figure><h4 id="2）第二步：配置-Store"><a href="#2）第二步：配置-Store" class="headerlink" title="2）第二步：配置 Store"></a><strong><font color='#10c300'>2）第二步：配置 Store</font></strong></h4><p>使用 <code>configureStore</code> 创建 Store，它会自动组合 Slice Reducers 并添加常用中间件。</p><p><code>src\redux\store.js</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; configureStore &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> counterSlice <span class="keyword">from</span> <span class="string">&quot;./modules/counterSlice&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> store = <span class="title function_">configureStore</span>(&#123;</span><br><span class="line">  <span class="attr">reducer</span>: &#123;</span><br><span class="line">    <span class="comment">// 通常会让 store 的 key 和 slice 的 name 保持一致，这里的 key &#x27;counter&#x27; 将决定 state 中的属性名</span></span><br><span class="line">    <span class="attr">counter</span>: counterSlice,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="3）第三步：注入-Store"><a href="#3）第三步：注入-Store" class="headerlink" title="3）第三步：注入 Store"></a><strong><font color='#10c300'>3）第三步：注入 Store</font></strong></h4><p>在应用入口文件中，使用 <code>&lt;Provider&gt;</code> 将 Store 注入到 React 组件树中。</p><p><code>src/main.jsx</code> (或 <code>index.js</code>):</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Provider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-redux&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; store &#125; <span class="keyword">from</span> <span class="string">&quot;./app/store&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;root&quot;</span>)).<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Provider</span> <span class="attr">store</span>=<span class="string">&#123;store&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">App</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">Provider</span>&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="4）第四步：在组件中使用-Hooks"><a href="#4）第四步：在组件中使用-Hooks" class="headerlink" title="4）第四步：在组件中使用 Hooks"></a><strong><font color='#10c300'>4）第四步：在组件中使用 Hooks</font></strong></h4><p>React Redux 提供了两个主要的 Hooks：</p><ul><li><strong><code>useSelector</code></strong>：从 Store 中读取数据。</li><li><strong><code>useDispatch</code></strong>：发送 Action 以触发状态更新。</li></ul><p><code>src\pages\Home.jsx</code></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><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><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useSelector, useDispatch &#125; <span class="keyword">from</span> <span class="string">&quot;react-redux&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  increment,</span><br><span class="line">  decrement,</span><br><span class="line">  incrementByAmount,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;../redux/modules/counterSlice&quot;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 读取 State：state.counter 对应 store 配置中的 reducer key</span></span><br><span class="line">  <span class="keyword">const</span> count = <span class="title function_">useSelector</span>(<span class="function">(<span class="params">state</span>) =&gt;</span> state.<span class="property">counter</span>.<span class="property">value</span>);</span><br><span class="line">  <span class="keyword">const</span> dispatch = <span class="title function_">useDispatch</span>();</span><br><span class="line">  <span class="keyword">const</span> [incrementAmount, setIncrementAmount] = <span class="title function_">useState</span>(<span class="string">&quot;2&quot;</span>);</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">div</span> <span class="attr">className</span>=<span class="string">&quot;row&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(decrement())&#125;&gt;-<span class="tag">&lt;/<span class="name">button</span>&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;value&quot;</span>&gt;</span>&#123;count&#125;<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> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(increment())&#125;&gt;+<span class="tag">&lt;/<span class="name">button</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 class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;row&quot;</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;incrementAmount&#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;(e)</span> =&gt;</span> setIncrementAmount(e.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">        /&gt;</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;()</span> =&gt;</span></span></span><br><span class="line"><span class="language-xml">            dispatch(incrementByAmount(Number(incrementAmount) || 0))</span></span><br><span class="line"><span class="language-xml">          &#125;</span></span><br><span class="line"><span class="language-xml">        &gt;</span></span><br><span class="line"><span class="language-xml">          Add Amount</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 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">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Home</span>;</span><br></pre></td></tr></table></figure><hr><br><h2 id="二、-进阶篇：复杂状态与异步逻辑"><a href="#二、-进阶篇：复杂状态与异步逻辑" class="headerlink" title="二、 进阶篇：复杂状态与异步逻辑"></a><strong>二、 进阶篇：复杂状态与异步逻辑</strong></h2><p>实际应用远比计数器复杂。我们需要处理 API 请求、加载状态以及更复杂的数据结构。</p><h3 id="2-1-异步逻辑与数据请求：createAsyncThunk"><a href="#2-1-异步逻辑与数据请求：createAsyncThunk" class="headerlink" title="2.1 异步逻辑与数据请求：createAsyncThunk"></a><strong><font color='red'>2.1 异步逻辑与数据请求：createAsyncThunk</font></strong></h3><p>对于初学者来说，Redux 的异步逻辑可能有点绕。我们先用一个生活中的例子来理解。</p><h4 id="1）为什么需要异步？"><a href="#1）为什么需要异步？" class="headerlink" title="1）为什么需要异步？"></a><strong><font color='#10c300'>1）为什么需要异步？</font></strong></h4><p>Redux 的标准 <code>dispatch</code> 是<strong>同步</strong>的：你点击按钮 -&gt; 发送 action -&gt; store 立即更新 -&gt; 页面刷新。这一切发生在一瞬间。</p><p>但这就像你去快餐店，点完汉堡如果立刻就能拿走，那就是同步。但现实中，很多操作是<strong>异步</strong>的：</p><ul><li><strong>点外卖</strong>：你下单（dispatch action），但饭不会立马到。你需要<strong>等待</strong>（loading），直到骑手送到（success）或者餐厅取消订单（failed）。</li><li><strong>API 请求</strong>：前端向服务器发起请求，服务器处理需要几百毫秒甚至几秒，这段时间内页面通常显示”加载中”。</li></ul><p>Redux Toolkit (RTK) 提供了一个强大的工具 <code>createAsyncThunk</code> 来专门处理这种”下单 -&gt; 等待 -&gt; 收到结果”的流程。它不需要你手动配置复杂的中间件。</p><h4 id="2）实战：实现一个”模拟网络请求的加法”"><a href="#2）实战：实现一个”模拟网络请求的加法”" class="headerlink" title="2）实战：实现一个”模拟网络请求的加法”"></a><strong><font color='#10c300'>2）实战：实现一个”模拟网络请求的加法”</font></strong></h4><p>假设我们有一个”异步加法”按钮，点击后需要等待 1 秒钟（模拟服务器响应），然后数字才会加 1。</p><p><strong>第一步：定义异步 Action (Thunk)</strong></p><p>在 <code>counterSlice.js</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createSlice, createAsyncThunk &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// createAsyncThunk 接收两个参数：</span></span><br><span class="line"><span class="comment">// 1. Action 的名字前缀：&#x27;counter/fetchCount&#x27;。这就好比给你的订单起个名字。</span></span><br><span class="line"><span class="comment">// 2. 一个异步函数 (payloadCreator)：在这里发送网络请求。</span></span><br><span class="line"><span class="comment">// 需要导出供组件调用</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> incrementAsync = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/fetchCount&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (amount) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 模拟发送网络请求，等待 1 秒</span></span><br><span class="line">    <span class="comment">// 这里的 Promise 就像是你在等待外卖，pending 状态</span></span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">resolve</span>(&#123; <span class="attr">data</span>: amount &#125;), <span class="number">1000</span>),</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 请求成功！返回的数据（外卖到了）</span></span><br><span class="line">    <span class="comment">// 这个返回值会自动变成 action.payload 传给 extraReducers</span></span><br><span class="line">    <span class="keyword">return</span> response.<span class="property">data</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p><strong>第二步：监听状态变化 (Pending &#x2F; Fulfilled &#x2F; Rejected)</strong></p><p>当 <code>incrementAsync</code> 被 dispatch 触发时，Redux 会自动派发三种状态的 action，就像外卖订单的状态变化：</p><ol><li><strong>pending</strong> (进行中)：外卖刚下单，正在做。</li><li><strong>fulfilled</strong> (成功)：外卖送到了。</li><li><strong>rejected</strong> (失败)：外卖被取消了（网络错误等）。</li></ol><p>我们需要在 <code>createSlice</code> 的 <code>extraReducers</code> 字段中监听这些状态，并更新 store。</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// counterSlice.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createSlice, createAsyncThunk &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// createAsyncThunk 接收两个参数：</span></span><br><span class="line"><span class="comment">// 1. Action 的名字前缀：&#x27;counter/fetchCount&#x27;。这就好比给你的订单起个名字。</span></span><br><span class="line"><span class="comment">// 2. 一个异步函数 (payloadCreator)：在这里发送网络请求。</span></span><br><span class="line"><span class="comment">// 需要导出供组件调用</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> incrementAsync = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/fetchCount&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (amount) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 模拟发送网络请求，等待 1 秒</span></span><br><span class="line">    <span class="comment">// 这里的 Promise 就像是你在等待外卖，pending 状态</span></span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve</span>) =&gt;</span></span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">resolve</span>(&#123; <span class="attr">data</span>: amount &#125;), <span class="number">1000</span>),</span><br><span class="line">    );</span><br><span class="line">    <span class="comment">// 请求成功！返回的数据（外卖到了）</span></span><br><span class="line">    <span class="comment">// 这个返回值会自动变成 action.payload 传给 extraReducers</span></span><br><span class="line">    <span class="keyword">return</span> response.<span class="property">data</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> counterSlice = <span class="title function_">createSlice</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;counter&quot;</span>,</span><br><span class="line">  <span class="attr">initialState</span>: &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span>, <span class="comment">// 状态：&#x27;idle&#x27; (空闲) | &#x27;loading&#x27; (加载中) | &#x27;failed&#x27; (失败)</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">reducers</span>: &#123;</span><br><span class="line">    <span class="comment">// 这里放普通的同步 reducers (如 increment, decrement)</span></span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// extraReducers 专门用来处理由 createAsyncThunk 生成的 action</span></span><br><span class="line">  <span class="comment">// 这里的 builder 语法不仅类型安全，而且更清晰</span></span><br><span class="line">  <span class="attr">extraReducers</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> &#123;</span><br><span class="line">    builder</span><br><span class="line">      .<span class="title function_">addCase</span>(incrementAsync.<span class="property">pending</span>, <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 1. 刚开始请求 (Pending)</span></span><br><span class="line">        state.<span class="property">status</span> = <span class="string">&quot;loading&quot;</span>; <span class="comment">// 标记状态为&quot;加载中&quot;，界面可以显示转圈圈</span></span><br><span class="line">      &#125;)</span><br><span class="line">      .<span class="title function_">addCase</span>(incrementAsync.<span class="property">fulfilled</span>, <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 2. 请求成功 (Fulfilled)</span></span><br><span class="line">        state.<span class="property">status</span> = <span class="string">&quot;idle&quot;</span>; <span class="comment">// 恢复为空闲状态</span></span><br><span class="line">        state.<span class="property">value</span> += action.<span class="property">payload</span>; <span class="comment">// 把请求回来的数据加到 value 上</span></span><br><span class="line">      &#125;)</span><br><span class="line">      .<span class="title function_">addCase</span>(incrementAsync.<span class="property">rejected</span>, <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 3. 请求失败 (Rejected)</span></span><br><span class="line">        state.<span class="property">status</span> = <span class="string">&quot;failed&quot;</span>; <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><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> counterSlice.<span class="property">reducer</span>;</span><br></pre></td></tr></table></figure><p><strong>第三步：在组件中使用</strong></p><p>组件中的写法和普通 action 一模一样，使用 <code>dispatch</code> 即可。我们可以利用 state 中的 status 来控制按钮的禁用状态。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useDispatch, useSelector &#125; <span class="keyword">from</span> <span class="string">&quot;react-redux&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; incrementAsync &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/modules/counterSlice&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> dispatch = <span class="title function_">useDispatch</span>();</span><br><span class="line">  <span class="comment">// 获取当前状态，如果是 &#x27;loading&#x27;，我们可以禁用按钮</span></span><br><span class="line">  <span class="keyword">const</span> &#123; value, status &#125; = <span class="title function_">useSelector</span>(<span class="function">(<span class="params">state</span>) =&gt;</span> state.<span class="property">counter</span>);</span><br><span class="line">  <span class="keyword">const</span> [incrementAmount, setIncrementAmount] = <span class="title function_">useState</span>(<span class="string">&quot;2&quot;</span>);</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">div</span> <span class="attr">className</span>=<span class="string">&quot;row&quot;</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;incrementAmount&#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;(e)</span> =&gt;</span> setIncrementAmount(e.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">        /&gt;</span></span><br><span class="line"><span class="language-xml">        &#123;/* 点击触发异步操作 如果正在加载，禁用按钮防止重复点击，提升用户体验 */&#125;</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;()</span> =&gt;</span> dispatch(incrementAsync(Number(incrementAmount) || 0))&#125;</span></span><br><span class="line"><span class="language-xml">          disabled=&#123;status === &quot;loading&quot;&#125;</span></span><br><span class="line"><span class="language-xml">        &gt;</span></span><br><span class="line"><span class="language-xml">          &#123;status === &quot;loading&quot; ? &quot;计算中...&quot; : &quot;异步加&quot;&#125;</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 class="language-xml">        &#123;/* 显示当前值 */&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span>&gt;</span>当前值: &#123;value&#125;<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">div</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><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Home</span>;</span><br></pre></td></tr></table></figure><p><strong>总结一下流程</strong>：</p><ol><li><strong>Dispatch</strong>: 用户点击按钮 -&gt; <code>dispatch(incrementAsync(5))</code></li><li><strong>Pending</strong>: Redux 自动触发 <code>pending</code> -&gt; <code>status</code> 变为 <code>&#39;loading&#39;</code> -&gt; 按钮变灰。</li><li><strong>Async Work</strong>: <code>incrementAsync</code> 里的 <code>async</code> 函数开始执行（等待 1 秒）。</li><li><strong>Fulfilled</strong>: 1 秒后 Promise 完成 -&gt; Redux 自动触发 <code>fulfilled</code> -&gt; <code>status</code>变回 <code>&#39;idle&#39;</code>，<code>value</code> 更新。</li><li><strong>Re-render</strong>: 组件重新渲染，显示最新的数字。</li></ol><br><h3 id="2-2-异步逻辑的代码优化"><a href="#2-2-异步逻辑的代码优化" class="headerlink" title="2.2 异步逻辑的代码优化"></a><strong><font color='red'>2.2 异步逻辑的代码优化</font></strong></h3><p>随着项目规模扩大，异步逻辑会变得臃肿。我们可以从<strong>结构分离</strong>、<strong>职责单一</strong>和<strong>逻辑复用</strong>三个维度进行优化。</p><h4 id="1-分离-Async-Thunk-定义"><a href="#1-分离-Async-Thunk-定义" class="headerlink" title="1. 分离 Async Thunk 定义"></a><strong><font color='#10c300'>1. 分离 Async Thunk 定义</font></strong></h4><p>将所有 Thunk 放在 <code>Slice</code> 文件中会导致文件过长且容易引发<strong>循环依赖</strong>。建议将异步逻辑提取到独立文件中。</p><ul><li><code>src/features/counter/counterThunks.js</code>: 定义异步操作。</li><li><code>src/features/counter/counterSlice.js</code>: 引入并处理状态。</li></ul><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="comment">// counterThunks.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createAsyncThunk &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取当天日期的历史数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getHistory = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/getHistory&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (params) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;https://jsonplaceholder.typicode.com/posts&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">    <span class="keyword">return</span> res;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="2-职责单一与精细化错误处理"><a href="#2-职责单一与精细化错误处理" class="headerlink" title="2. 职责单一与精细化错误处理"></a><strong><font color='#10c300'>2. 职责单一与精细化错误处理</font></strong></h4><ul><li><strong>职责单一</strong>：Thunk 只负责“拿数据”。复杂的数据转换逻辑应放在 <code>reducer</code> 中，保持 Thunk 清洁。</li><li><strong>错误处理</strong>：使用 <code>rejectWithValue</code> 返回自定义错误载荷，以便在界面上展示更有意义的提示。</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createAsyncThunk &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取当天日期的历史数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getHistory = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/getHistory&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (params, &#123; rejectWithValue &#125;) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(</span><br><span class="line">        <span class="string">&quot;https://jsonplaceholder.typicode.com/posts&quot;</span>,</span><br><span class="line">      );</span><br><span class="line">      <span class="keyword">const</span> res = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">      <span class="keyword">return</span> res;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">rejectWithValue</span>(err.<span class="property">response</span>.<span class="property">data</span>); <span class="comment">// 返回后端错误信息</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><h4 id="3-使用-addMatcher-减少样板代码"><a href="#3-使用-addMatcher-减少样板代码" class="headerlink" title="3. 使用 addMatcher 减少样板代码"></a><strong><font color='#10c300'>3. 使用 addMatcher 减少样板代码</font></strong></h4><p>如果多个异步操作都有相同的 Loading 或 Error 处理逻辑，可以使用 <code>addMatcher</code> 进行统一拦截，避免在每个 <code>addCase</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// counterSlice.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createSlice &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; getHistory &#125; <span class="keyword">from</span> <span class="string">&quot;../thunks/counterThunks&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> counterSlice = <span class="title function_">createSlice</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;counter&quot;</span>,</span><br><span class="line">  <span class="attr">initialState</span>: &#123;</span><br><span class="line">    <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span>, <span class="comment">// 初始状态设为 idle</span></span><br><span class="line">    <span class="attr">error</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    <span class="attr">value</span>: [],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">reducers</span>: &#123;&#125;,</span><br><span class="line">  <span class="attr">extraReducers</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> &#123;</span><br><span class="line">    builder</span><br><span class="line">      <span class="comment">// 处理具体的成功逻辑</span></span><br><span class="line">      .<span class="title function_">addCase</span>(getHistory.<span class="property">fulfilled</span>, <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">        state.<span class="property">value</span> = [];</span><br><span class="line">        state.<span class="property">value</span> = action.<span class="property">payload</span>;</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 pending 状态</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/pending&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;loading&quot;</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      )</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 fulfilled 状态（仅更新状态部分）</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/fulfilled&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;idle&quot;</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      )</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 rejected 状态</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/rejected&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;failed&quot;</span>;</span><br><span class="line">          state.<span class="property">error</span> = action.<span class="property">error</span>.<span class="property">message</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      );</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">export</span> <span class="keyword">default</span> counterSlice.<span class="property">reducer</span>;</span><br></pre></td></tr></table></figure><p><code>addMatcher</code> 在项目后期优化中非常强大，它可以让你像写“拦截器”一样统一管理全局的异步状态。</p><br><h3 id="2-3-综合案例"><a href="#2-3-综合案例" class="headerlink" title="2.3 综合案例"></a><strong><font color='red'>2.3 综合案例</font></strong></h3><p>定义异步操作<code>src\redux\modules\counterThunks.js</code></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createAsyncThunk &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取当天日期的历史数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getHistory = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/getHistory&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (params, &#123; rejectWithValue &#125;) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;/posts&quot;</span>);</span><br><span class="line">      <span class="keyword">const</span> res = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">      <span class="keyword">return</span> res;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">rejectWithValue</span>(err.<span class="property">response</span>.<span class="property">data</span>); <span class="comment">// 返回后端错误信息</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 获取评论数据</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> getComments = <span class="title function_">createAsyncThunk</span>(</span><br><span class="line">  <span class="string">&quot;counter/getComments&quot;</span>,</span><br><span class="line">  <span class="title function_">async</span> (postId, &#123; rejectWithValue &#125;) =&gt; &#123;</span><br><span class="line">    <span class="keyword">try</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">`/comments?postId=<span class="subst">$&#123;postId&#125;</span>`</span>);</span><br><span class="line">      <span class="keyword">const</span> res = <span class="keyword">await</span> response.<span class="title function_">json</span>();</span><br><span class="line">      <span class="keyword">return</span> res;</span><br><span class="line">    &#125; <span class="keyword">catch</span> (err) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">rejectWithValue</span>(err.<span class="property">response</span>.<span class="property">data</span>); <span class="comment">// 返回后端错误信息</span></span><br><span class="line">    &#125;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><p>引入并处理状态<code>src\redux\modules\counterSlice.js</code></p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createSlice &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; getHistory, getComments &#125; <span class="keyword">from</span> <span class="string">&quot;./counterThunks&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> counterSlice = <span class="title function_">createSlice</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;counter&quot;</span>,</span><br><span class="line">  <span class="attr">initialState</span>: &#123;</span><br><span class="line">    <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span>, <span class="comment">// 初始状态设为 idle</span></span><br><span class="line">    <span class="attr">value</span>: [],</span><br><span class="line">    <span class="attr">comments</span>: [],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">reducers</span>: &#123;&#125;,</span><br><span class="line">  <span class="attr">extraReducers</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> &#123;</span><br><span class="line">    builder</span><br><span class="line">      <span class="comment">// 处理具体的成功逻辑</span></span><br><span class="line">      .<span class="title function_">addCase</span>(getHistory.<span class="property">fulfilled</span>, <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">        state.<span class="property">value</span> = action.<span class="property">payload</span>;</span><br><span class="line">      &#125;)</span><br><span class="line">      .<span class="title function_">addCase</span>(getComments.<span class="property">fulfilled</span>, <span class="function">(<span class="params">state, action</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 假设评论数据存放在另一个字段</span></span><br><span class="line">        state.<span class="property">value</span> = [];</span><br><span class="line">        state.<span class="property">comments</span> = action.<span class="property">payload</span>;</span><br><span class="line">      &#125;)</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 pending 状态</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/pending&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;loading&quot;</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      )</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 fulfilled 状态（仅更新状态部分）</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/fulfilled&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;idle&quot;</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      )</span><br><span class="line">      <span class="comment">// 使用 addMatcher 统一处理所有 rejected 状态</span></span><br><span class="line">      .<span class="title function_">addMatcher</span>(</span><br><span class="line">        <span class="function">(<span class="params">action</span>) =&gt;</span> action.<span class="property">type</span>.<span class="title function_">endsWith</span>(<span class="string">&quot;/rejected&quot;</span>),</span><br><span class="line">        <span class="function">(<span class="params">state</span>) =&gt;</span> &#123;</span><br><span class="line">          state.<span class="property">status</span> = <span class="string">&quot;failed&quot;</span>;</span><br><span class="line">        &#125;,</span><br><span class="line">      );</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">export</span> <span class="keyword">default</span> counterSlice.<span class="property">reducer</span>;</span><br></pre></td></tr></table></figure><p>组件中使用<code>src\pages\Home.jsx</code></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useSelector, useDispatch &#125; <span class="keyword">from</span> <span class="string">&quot;react-redux&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; getHistory, getComments &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/modules/counterThunks&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; value, status, comments &#125; = <span class="title function_">useSelector</span>(</span><br><span class="line">    <span class="function">(<span class="params">state</span>) =&gt;</span> state.<span class="property">counterSlice</span>,</span><br><span class="line">  );</span><br><span class="line">  <span class="keyword">const</span> dispatch = <span class="title function_">useDispatch</span>();</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(getHistory(&#123; count: &quot;5&quot; &#125;))&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        数据请求</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 class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(getComments(1))&#125;&gt;获取评论<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        状态：</span></span><br><span class="line"><span class="language-xml">        &#123;status === &quot;loading&quot;</span></span><br><span class="line"><span class="language-xml">          ? &quot;加载中...&quot;</span></span><br><span class="line"><span class="language-xml">          : status === &quot;idle&quot;</span></span><br><span class="line"><span class="language-xml">            ? &quot;空闲&quot;</span></span><br><span class="line"><span class="language-xml">            : &quot;失败&quot;&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;value.map((item) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span>&gt;</span>&#123;item.title&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;comments.map((item) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span>&gt;</span>&#123;item.name&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Home</span>;</span><br></pre></td></tr></table></figure><br><h3 id="2-4-记忆化选择器：createSelector"><a href="#2-4-记忆化选择器：createSelector" class="headerlink" title="2.4 记忆化选择器：createSelector"></a><strong><font color='red'>2.4 记忆化选择器：createSelector</font></strong></h3><p>在大型应用中，我们经常需要根据原始数据计算出衍生状态（如：列表过滤、数据转换）。如果直接在 <code>useSelector</code> 中编写复杂的计算逻辑，可能会导致不必要的重渲染。</p><h4 id="1）为什么需要它？"><a href="#1）为什么需要它？" class="headerlink" title="1）为什么需要它？"></a><strong><font color='#10c300'>1）为什么需要它？</font></strong></h4><p>默认情况下，只要 Redux Store 更新，所有的 <code>useSelector</code> 都会重新运行。如果你的 Selector 返回的是一个<strong>新引用</strong>（例如使用 <code>.filter()</code>、<code>.map()</code> 或返回一个新对象），React Redux 会认为数据发生了变化，从而触发组件重渲染——即使数据内容其实没变。</p><p><code>createSelector</code> 可以**记忆（Memoize）**计算结果。只要输入参数没变，它就会直接返回缓存的结果，不会重新计算，也不会触发重渲染。</p><h4 id="2）基本用法：数据预处理"><a href="#2）基本用法：数据预处理" class="headerlink" title="2）基本用法：数据预处理"></a><strong><font color='#10c300'>2）基本用法：数据预处理</font></strong></h4><p>假设后端接口返回的用户信息中，性别是数字代码（1 代表男，2 代表女）。我们希望在组件中直接拿到文字。</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><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createSlice, createSelector &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> userSlice = <span class="title function_">createSlice</span>(&#123;</span><br><span class="line">  <span class="attr">name</span>: <span class="string">&quot;user&quot;</span>,</span><br><span class="line">  <span class="attr">initialState</span>: &#123;</span><br><span class="line">    <span class="attr">gender</span>: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">    <span class="attr">hobby</span>: [</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">name</span>: <span class="string">&quot;看书&quot;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">name</span>: <span class="string">&quot;写代码&quot;</span> &#125;,</span><br><span class="line">      &#123; <span class="attr">id</span>: <span class="number">3</span>, <span class="attr">name</span>: <span class="string">&quot;打游戏&quot;</span> &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">reducers</span>: &#123;&#125;,</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">genderFn</span> = (<span class="params">state</span>) =&gt; state.<span class="property">user</span>.<span class="property">gender</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">hobbyFn</span> = (<span class="params">state</span>) =&gt; state.<span class="property">user</span>.<span class="property">hobby</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 只有 gender 或 hobby 变化时，才会执行下面逻辑</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> selectUser = <span class="title function_">createSelector</span>(</span><br><span class="line">  [genderFn, hobbyFn],</span><br><span class="line">  <span class="function">(<span class="params">gender, hobby</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;正在转换数据...&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> genderText = gender === <span class="string">&quot;1&quot;</span> ? <span class="string">&quot;男&quot;</span> : <span class="string">&quot;女&quot;</span>;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">gender</span>: genderText,</span><br><span class="line">      <span class="attr">hobbyList</span>: hobby.<span class="title function_">filter</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> item.<span class="property">id</span> === <span class="number">1</span>),</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useSelector &#125; <span class="keyword">from</span> <span class="string">&quot;react-redux&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; selectUser &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/modules/userSlice&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; gender, hobbyList &#125; = <span class="title function_">useSelector</span>(selectUser);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>性别：&#123;gender&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span> // 直接使用处理好的属性</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">span</span>&gt;</span>爱好：<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;hobbyList.map((item) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">span</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span>&gt;</span>&#123;item.name&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Home</span>;</span><br></pre></td></tr></table></figure><h4 id="3）传参的标准写法"><a href="#3）传参的标准写法" class="headerlink" title="3）传参的标准写法"></a><strong><font color='#10c300'>3）传参的标准写法</font></strong></h4><p>有时我们需要根据组件传入的参数（如 ID）来动态查询数据。<strong>标准做法是将参数作为 Selector 的一个输入项。</strong></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="comment">// 3.1 定义获取参数的选择器</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">selectItemId</span> = (<span class="params">state, itemId</span>) =&gt; itemId;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3.2 在 createSelector 中引用该参数</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> selectItemById = <span class="title function_">createSelector</span>(</span><br><span class="line">  [<span class="function">(<span class="params">state</span>) =&gt;</span> state.<span class="property">user</span>.<span class="property">hobby</span>, selectItemId], <span class="comment">// 获取外部传入的参数</span></span><br><span class="line">  <span class="function">(<span class="params">hobby, id</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 这里的 id 就是上面获取参数选择器对应的 id</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`正在查找 ID 为 <span class="subst">$&#123;id&#125;</span> 的数据...`</span>);</span><br><span class="line">    <span class="keyword">return</span> hobby.<span class="title function_">find</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> item.<span class="property">id</span> === id);</span><br><span class="line">  &#125;,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 传递参数：useSelector((state) =&gt; selector(state, param))</span></span><br><span class="line"><span class="keyword">const</span> item = <span class="title function_">useSelector</span>(<span class="function">(<span class="params">state</span>) =&gt;</span> <span class="title function_">selectItemById</span>(state, props.<span class="property">id</span>));</span><br></pre></td></tr></table></figure><h4 id="4）高级用法：工厂函数模式-Factory-Function"><a href="#4）高级用法：工厂函数模式-Factory-Function" class="headerlink" title="4）高级用法：工厂函数模式 (Factory Function)"></a><strong><font color='#10c300'>4）高级用法：工厂函数模式 (Factory Function)</font></strong></h4><p>虽然上述传参方式在逻辑上是通用的，但在 <strong>“一个页面渲染多个同类组件”</strong> 时，单例 Selector 的缓存深度只有 1，会导致不同组件实例之间互相竞争缓存，产生不必要的重计算。这时需要使用工厂函数。</p><p><strong>标准工厂模式写法：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 工厂函数：每次调用都返回一个“全新且独立”的 Selector 实例</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">makeSelectTodoById</span> = (<span class="params"></span>) =&gt;</span><br><span class="line">  <span class="title function_">createSelector</span>(</span><br><span class="line">    [</span><br><span class="line">      <span class="function">(<span class="params">state</span>) =&gt;</span> state.<span class="property">todos</span>.<span class="property">list</span>,</span><br><span class="line">      <span class="function">(<span class="params">state, todoId</span>) =&gt;</span> todoId, <span class="comment">// 动态插槽提取参数</span></span><br><span class="line">    ],</span><br><span class="line">    <span class="function">(<span class="params">todos, id</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`实例正在查找 ID 为 <span class="subst">$&#123;id&#125;</span> 的待办事项...`</span>);</span><br><span class="line">      <span class="keyword">return</span> todos.<span class="title function_">find</span>(<span class="function">(<span class="params">todo</span>) =&gt;</span> todo.<span class="property">id</span> === id);</span><br><span class="line">    &#125;,</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 💡 深度探讨：为什么不直接写成 makeSelectTodoById(id) ?</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * ❌ 错误写法：const make = (id) =&gt; createSelector(...)</span></span><br><span class="line"><span class="comment"> * 原因：这会导致该实例被“锁死”在特定 ID 上，丧失了作为选择器工具的通用性和灵活性。</span></span><br><span class="line"><span class="comment"> * 正确写法：实例应该是通用的插槽，具体的 ID 在组件 useSelector 时动态传入。</span></span><br><span class="line"><span class="comment"> */</span></span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">TodoItem</span>(<span class="params">&#123; id &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// 1. 使用 useMemo 确保 Selector 实例在组件生命周期内保持唯一</span></span><br><span class="line">  <span class="keyword">const</span> selectTodoById = <span class="title function_">useMemo</span>(makeSelectTodoById, []);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 动态传参：为该组件专属的实例注入当前 id</span></span><br><span class="line">  <span class="keyword">const</span> todo = <span class="title function_">useSelector</span>(<span class="function">(<span class="params">state</span>) =&gt;</span> <span class="title function_">selectTodoById</span>(state, id));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;todo?.text&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5）最佳实践总结"><a href="#5）最佳实践总结" class="headerlink" title="5）最佳实践总结"></a><strong><font color='#10c300'>5）最佳实践总结</font></strong></h4><ul><li><strong>位置</strong>：尽量在对应的 <code>Slice</code> 文件中定义 Selector，保持逻辑内聚。</li><li><strong>职责</strong>：将“原始数据获取”与“数据预处理”分开调用。</li><li><strong>性能</strong>：不要在组件内部使用 <code>createSelector</code>（每次渲染都会创建新实例），应在组件外部定义。</li></ul><h3 id="2-5-Redux-DevTools"><a href="#2-5-Redux-DevTools" class="headerlink" title="2.5 Redux DevTools"></a><strong><font color='red'>2.5 Redux DevTools</font></strong></h3><p>Redux 最强大的特性之一是调试体验。</p><ul><li>安装 Chrome 扩展程序 <strong>Redux DevTools</strong>。</li><li>RTK 的 <code>configureStore</code> 默认开启 DevTools。</li><li>你可以看到每一个 Action 的触发时间、Payload 内容以及 State 的差异 (Diff)。</li><li><strong>时间旅行 (Time Travel)</strong>：你可以点击 “Jump” 跳转到任意历史状态，重现 Bug 现场。</li></ul><hr><br><h2 id="三、高级篇：RTK-Query-与性能优化"><a href="#三、高级篇：RTK-Query-与性能优化" class="headerlink" title="三、高级篇：RTK Query 与性能优化"></a><strong>三、高级篇：RTK Query 与性能优化</strong></h2><p>RTK Query (RTKQ) 是 RTK 内置的一个强大的数据获取和缓存工具。它不仅帮你发请求，最核心的是它能<strong>自动管理缓存状态</strong>。</p><blockquote><p>[!NOTE]<br><strong>为什么要用它？</strong><br>在没有 RTKQ 之前，我们需要手动管理：请求加载状态 (<code>isLoading</code>)、错误处理 (<code>error</code>)、<code>useEffect</code> 中的请求触发、以及最头疼的“跨页面同步缓存数据”。RTKQ 把这些都自动完成了。</p></blockquote><h3 id="3-1-基础用法"><a href="#3-1-基础用法" class="headerlink" title="3.1 基础用法"></a><strong><font color='red'>3.1 基础用法</font></strong></h3><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><span class="line"><span class="string">`步骤一：创建api接口`</span>;</span><br><span class="line"><span class="comment">// src\redux\api\todoApi.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createApi, fetchBaseQuery &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit/query/react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> todoApi = <span class="title function_">createApi</span>(&#123;</span><br><span class="line">  <span class="comment">// 导出供 Store 中间件使用</span></span><br><span class="line">  <span class="attr">reducerPath</span>: <span class="string">&quot;createApi&quot;</span>, <span class="comment">// 在 store 中的 key</span></span><br><span class="line">  <span class="attr">baseQuery</span>: <span class="title function_">fetchBaseQuery</span>(&#123; <span class="attr">baseUrl</span>: <span class="string">&quot;/api&quot;</span> &#125;), <span class="comment">//配置基础路径</span></span><br><span class="line">  <span class="attr">endpoints</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> (&#123;</span><br><span class="line">    <span class="comment">// 配置请求的详细信息</span></span><br><span class="line">    <span class="attr">getTodos</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">      <span class="comment">//默认构建的是get请求</span></span><br><span class="line">      <span class="attr">query</span>: <span class="function">() =&gt;</span> <span class="string">&quot;/todos&quot;</span>, <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="variable language_">console</span>.<span class="title function_">log</span>(todoApi);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自动生成 hooks 供组件使用</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> &#123; useGetTodosQuery &#125; = todoApi;</span><br><span class="line"></span><br><span class="line"><span class="string">`步骤二：配置 Store 中间件`</span>;</span><br><span class="line"><span class="comment">// src\redux\store.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; configureStore &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; todoApi &#125; <span class="keyword">from</span> <span class="string">&quot;./api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> store = <span class="title function_">configureStore</span>(&#123;</span><br><span class="line">  <span class="attr">reducer</span>: &#123;</span><br><span class="line">    <span class="comment">// 必须添加 api 的 reducer</span></span><br><span class="line">    [todoApi.<span class="property">reducerPath</span>]: todoApi.<span class="property">reducer</span>,</span><br><span class="line">    [xxxx.<span class="property">reducerPath</span>]: xxxx.<span class="property">reducer</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 必须添加 api 的中间件，用于启用缓存、轮询、失效等功能</span></span><br><span class="line">  <span class="attr">middleware</span>: <span class="function">(<span class="params">gDM</span>) =&gt;</span></span><br><span class="line">    <span class="comment">// 多参数写法（推荐，更整洁）</span></span><br><span class="line">    <span class="title function_">gDM</span>().<span class="title function_">concat</span>(todoApi.<span class="property">middleware</span>, xxxx.<span class="property">middleware</span>),</span><br><span class="line">  <span class="comment">// 链式调用写法</span></span><br><span class="line">  <span class="comment">// gDM().concat(todoApi.middleware).concat(xxxx.middleware)</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="string">`步骤三：组件中使用 Hooks`</span>;</span><br><span class="line"><span class="comment">// src\pages\Home.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; useGetTodosQuery &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; data, isLoading, error &#125; = <span class="title function_">useGetTodosQuery</span>();</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;isLoading &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>加载中...<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;error &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>加载失败<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;data &amp;&amp; data.map((todo) =&gt; <span class="tag">&lt;<span class="name">p</span> <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span>&gt;</span>&#123;todo.title&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>)&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Home</span>;</span><br></pre></td></tr></table></figure><h3 id="3-2-RTK-Query-核心概念详解"><a href="#3-2-RTK-Query-核心概念详解" class="headerlink" title="3.2 RTK Query 核心概念详解"></a><strong><font color='red'>3.2 RTK Query 核心概念详解</font></strong></h3><h4 id="1）获取数据：Query-查询操作"><a href="#1）获取数据：Query-查询操作" class="headerlink" title="1）获取数据：Query (查询操作)"></a><strong><font color='#10c300'>1）获取数据：Query (查询操作)</font></strong></h4><p>Query 是 RTK Query 中最基础的数据获取方式，主要用于从服务器端拉取状态并缓存在本地。</p><p><strong>特点与用法：</strong></p><ul><li>默认情况下，对应的 <code>useXXXQuery</code> Hook 在组件挂载时会<strong>自动发起请求</strong>。</li><li>它返回一个包含 <code>data</code> (数据), <code>isLoading</code> (初次加载状态), <code>isFetching</code> (每次请求状态), <code>error</code> (错误信息) 等丰富属性的对象。</li><li>绝大多数 Query 是 <code>GET</code> 请求，但有些特殊场景（如复杂的搜索查询需要传递大量参数体）也会使用 <code>POST</code> 请求作为 Query。</li></ul><p><strong>代码示例拆解：</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 todoApi.js 中定义</span></span><br><span class="line"><span class="attr">endpoints</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> (&#123;</span><br><span class="line">  <span class="comment">// 基础用法：默认发无参的 GET 请求</span></span><br><span class="line">  <span class="attr">getTodos</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">    <span class="attr">query</span>: <span class="function">() =&gt;</span> <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">  &#125;),</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 带参 GET 请求 (如：/todos?status=completed)</span></span><br><span class="line">  <span class="attr">getTodosByStatus</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">    <span class="attr">query</span>: <span class="function">(<span class="params">status</span>) =&gt;</span> <span class="string">`/todos?status=<span class="subst">$&#123;status&#125;</span>`</span>,</span><br><span class="line">  &#125;),</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 特殊用法：使用 POST 进行复杂的查询</span></span><br><span class="line">  <span class="comment">// 注意：虽然是发送 POST，但因为是&quot;获取查看&quot;数据而不是&quot;修改&quot;系统状态，所以依然用 builder.query</span></span><br><span class="line"><span class="attr">getTodosById</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">    <span class="attr">query</span>: <span class="function">(<span class="params">id</span>) =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&quot;/posts&quot;</span>,</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">        <span class="attr">body</span>: &#123; <span class="attr">postId</span>: id &#125;</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">export</span> <span class="keyword">const</span> &#123; useGetTodosQuery, useGetTodosByStatusQuery, useGetTodosByIdQuery &#125; = todoApi;</span><br><span class="line">    </span><br><span class="line"></span><br><span class="line"><span class="string">`组件中使用`</span></span><br><span class="line"><span class="comment">// 1. 无参 GET 用法：组件一挂载就自动发请求拉列表</span></span><br><span class="line"><span class="keyword">const</span> &#123; <span class="attr">data</span>: allTodos, isLoading &#125; = <span class="title function_">useGetTodosQuery</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. 带参 GET 用法：参数如果变化，RTK Query 会自动发新请求</span></span><br><span class="line"><span class="keyword">const</span> [status, setStatus] = <span class="title function_">useState</span>(<span class="string">&quot;completed&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> &#123; <span class="attr">data</span>: filteredTodos &#125; = <span class="title function_">useGetTodosByStatusQuery</span>(status);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 3. POST 查询用法：将复杂的检索条件(如包含各种过滤项的对象)传递过去</span></span><br><span class="line"><span class="keyword">const</span> searchFilters = &#123; <span class="attr">keyword</span>: <span class="string">&quot;React&quot;</span>, <span class="attr">date</span>: <span class="string">&quot;today&quot;</span> &#125;;</span><br><span class="line"><span class="keyword">const</span> &#123; <span class="attr">data</span>: searchResults, isFetching &#125; = <span class="title function_">useGetTodosByIdQuery</span>(searchFilters);</span><br></pre></td></tr></table></figure><br><h4 id="2）修改数据：Mutation-变更操作"><a href="#2）修改数据：Mutation-变更操作" class="headerlink" title="2）修改数据：Mutation (变更操作)"></a><strong><font color='#10c300'>2）修改数据：Mutation (变更操作)</font></strong></h4><p>在 RTK Query 中，所有的 API 端点 (endpoints) 分为两类：</p><ul><li><strong>Query (查询)</strong>：用于获取数据，在组件挂载时自动触发（如 GET 请求）。对应的 Hook 叫 <code>useXXXQuery</code>。</li><li><strong>Mutation (变更)</strong>：用于修改服务器数据（如 POST、PUT、DELETE 请求）。对应的 Hook 叫 <code>useXXXMutation</code>。</li></ul><p><strong>特点与用法：</strong></p><ul><li><code>mutation</code> <strong>不会在组件挂载时自动触发</strong>。它会返回一个包含两个元素的数组：<code>[触发函数的引用 (trigger), 包含状态的对象 (result)]</code>。</li><li>它通常绑定到表单提交或按钮点击事件上。</li></ul><p><strong>代码示例拆解：</strong></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在 todoApi.js 中定义</span></span><br><span class="line"><span class="attr">addTodo</span>: builder.<span class="title function_">mutation</span>(&#123;</span><br><span class="line">  <span class="comment">// query 接收调用触发函数时传入的参数 (newTodo)</span></span><br><span class="line">  <span class="attr">query</span>: <span class="function">(<span class="params">newTodo</span>) =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">url</span>: <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">    <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>, <span class="comment">// 明确指定是发送 POST 请求</span></span><br><span class="line">    <span class="attr">body</span>: newTodo, <span class="comment">// 发送给后端的请求体数据</span></span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="attr">addUser</span>: builder.<span class="title function_">mutation</span>(&#123;</span><br><span class="line">    <span class="attr">query</span>: <span class="function">(<span class="params">user</span>) =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&quot;/users&quot;</span>,</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">        <span class="attr">body</span>: user</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">export</span> <span class="keyword">const</span> &#123; useAddUserMutation &#125; = todoApi;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="string">`组件中使用`</span></span><br><span class="line"><span class="keyword">import</span> &#123; useAddUserMutation &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [addUser, &#123; <span class="attr">isLoading</span>: addUserLoading, <span class="attr">isSuccess</span>: addUserSuccess, <span class="attr">error</span>: addUserError &#125;] = <span class="title function_">useAddUserMutation</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">fn</span> = <span class="keyword">async</span>(<span class="params"></span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">addUser</span>(&#123; <span class="attr">name</span>: <span class="string">&quot;test&quot;</span>, <span class="attr">age</span>: <span class="number">18</span> &#125;)</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(res);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">p</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;fn&#125;</span>&gt;</span>添加用户<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            &#123;addUserLoading &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>添加中...<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">            &#123;addUserSuccess &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>添加成功<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">            &#123;addUserError &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>添加失败<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h4 id="4）手动触发查询：useLazyXXXQuery"><a href="#4）手动触发查询：useLazyXXXQuery" class="headerlink" title="4）手动触发查询：useLazyXXXQuery"></a><strong><font color='#10c300'>4）手动触发查询：useLazyXXXQuery</font></strong></h4><p>默认情况下，<code>useXXXQuery</code> 在组件挂载时就会<strong>立即、自动</strong>发起网络请求。但在某些场景下（例如：点击按钮搜索、展开面板时才加载详情），我们希望<strong>手动</strong>去触发这个 GET 请求。这时候就需要用到懒加载钩子 <code>useLazyXXXQuery</code>。</p><p><strong>特点与用法：</strong></p><ul><li>类似于 Mutation 的 Hook 设计，它返回一个数组 <code>[触发函数(trigger), 查询状态结果(result)]</code>。</li><li>不会在组件挂载时自动发送请求。</li><li>当你需要发送请求时，调用触发函数，并可以传递查询参数。</li></ul><p><strong>代码示例拆解：</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 注意导出名字的变化：从 todoApi 自动生成的 hooks 会有一个 `useLazy` 开头的版本</span></span><br><span class="line"><span class="keyword">import</span> &#123; useLazyGetTodosQuery &#125; <span class="keyword">from</span> <span class="string">&quot;../redux/api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SearchPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 返回数组：第一个是手动发请求的函数，第二个是包含请求结果和状态的对象</span></span><br><span class="line">  <span class="keyword">const</span> [triggerGetTodos, &#123; data, isLoading, isFetching &#125;] = <span class="title function_">useLazyGetTodosQuery</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleSearch</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 点击按钮时，才手动触发网络请求</span></span><br><span class="line">    <span class="title function_">triggerGetTodos</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">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleSearch&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;isLoading || isFetching ? &quot;加载中...&quot; : &quot;点击加载列表&quot;&#125;</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 class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;data &amp;&amp; data.map((todo) =&gt; <span class="tag">&lt;<span class="name">p</span> <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span>&gt;</span>&#123;todo.title&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>)&#125;</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><br><h4 id="4）数据转换：transformResponse"><a href="#4）数据转换：transformResponse" class="headerlink" title="4）数据转换：transformResponse"></a><strong><font color='#10c300'>4）数据转换：transformResponse</font></strong></h4><p>有时后端返回的数据格式并不符合前端的使用要求（比如外面包裹了一层特定的结构，或者缺少某个字段）。<code>transformResponse</code> 允许你在数据被存入 Redux 缓存之前，提前“拦截”并转换它。</p><p><strong>为什么要用？</strong><br>将处理后端“奇葩”数据结构的操作集中在 API 切片里，保持前端组件的数据逻辑纯粹。这样能避免你在每个组件 <code>useSelector</code> 获取数据时都要反复去写 <code>.map()</code> 或解构特定字段。</p><p><strong>代码示例拆解：</strong></p><p>假设后端返回的列表数据长这样：<br><code>&#123; code: 200, message: &quot;成功&quot;, data: &#123; list: [ &#123;id:1, title:&quot;写代码&quot;&#125; ] &#125; &#125;</code></p><p>而我们只想在页面中直接使用 <code>list</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></pre></td><td class="code"><pre><span class="line"><span class="attr">getTodos</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">  <span class="attr">query</span>: <span class="function">() =&gt;</span> <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">  <span class="comment">// 转换响应结果（就像 axios 的响应拦截器）</span></span><br><span class="line">  <span class="attr">transformResponse</span>: <span class="function">(<span class="params">response, meta, arg</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// response 就是后端直接抛回来的原始 JSON 对象</span></span><br><span class="line">    <span class="comment">// 我们只把最核心的 data.list return 出去</span></span><br><span class="line">    <span class="comment">// 这样组件里通过 useGetTodosQuery() 解构出来的 data 就是这个干净的数组！</span></span><br><span class="line">    <span class="keyword">return</span> response.<span class="property">data</span>.<span class="property">list</span>;</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><br><h4 id="5）缓存操作：providesTags-与-invalidatesTags"><a href="#5）缓存操作：providesTags-与-invalidatesTags" class="headerlink" title="5）缓存操作：providesTags 与 invalidatesTags"></a><strong><font color='#10c300'>5）缓存操作：providesTags 与 invalidatesTags</font></strong></h4><p>这是 RTK Query 避免手动写 <code>dispatch</code> 来刷新数据的核心“黑科技”：基于标签 (Tags) 的自动化缓存失效系统。</p><p>把它想象成一个**“贴标签与撕标签”**的游戏：</p><ul><li><strong><code>providesTags</code></strong>：用来**“贴标签”**。告诉 RTKQ，当前通过查询获得回来的这份数据，叫什么名字。</li><li><strong><code>invalidatesTags</code></strong>：用来**“撕标签”** (宣告失效)。当执行增删改等 mutating 操作后，告诉 RTKQ 哪些标签的数据已经过时了，必须重新拉取。</li></ul><p><strong>案例生动解析：</strong></p><p><strong>第一步：查询列表时贴标签</strong></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="attr">getTodos</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">  <span class="attr">query</span>: <span class="function">() =&gt;</span> <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">  <span class="comment">// 给这个列表数据贴上一个叫 &quot;Todos&quot; 的特定标签（且指定 id 为 &#x27;LIST&#x27; 代表整个列表）</span></span><br><span class="line">  <span class="attr">providesTags</span>: [&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;],</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>此时，RTKQ 内部的记事本上写着：<em>“用 <code>getTodos</code> 获取的数据，被贴上了 <code>Todos-LIST</code> 的标签。如果以后谁撕毁了这个标签，我就得再发一次 <code>GET /todos</code> 重新拿数据。”</em></p><p><strong>第二步：添加数据时撕标签</strong></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="attr">addTodo</span>: builder.<span class="title function_">mutation</span>(&#123;</span><br><span class="line">  <span class="attr">query</span>: <span class="function">(<span class="params">newTodo</span>) =&gt;</span> (&#123; ... &#125;),</span><br><span class="line">  <span class="comment">// 添加成功后，撕毁 &#x27;Todos&#x27; 的 &#x27;LIST&#x27; 标签</span></span><br><span class="line">  <span class="attr">invalidatesTags</span>: [&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;]</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>当你调用 <code>addTodo</code> 并在服务端创建成功后，RTKQ 看到你要“撕毁” <code>Todos-LIST</code> 标签。它会去找记事本，发现 <code>getTodos</code> 绑定了这个标签数据，于是它会<strong>自动且静默地</strong>在此刻触发一次 <code>getTodos</code>，实现了前端列表的无缝自动刷新。</p><blockquote><p>如果要做到更细节粒度的缓存控制（比如只修改列表中某一项的数据，不全列表拉取），可以通过给每个具体的 item 贴带 <code>id</code> 的标签（如 <code>&#123; type: &quot;Todos&quot;, id: todo.id &#125;</code>）来实现。这也是为什么我们在前文基础代码里要写 <code>...result.map((&#123; id &#125;) =&gt; (&#123; type: &quot;Todos&quot;, id &#125;))</code> 这样复杂的结构，这正是为了极致的单条数据按需更新。</p></blockquote><br><h3 id="3-3-实战：构建一个-Todo-CRUD-API"><a href="#3-3-实战：构建一个-Todo-CRUD-API" class="headerlink" title="3.3 实战：构建一个 Todo CRUD API"></a><strong><font color='red'>3.3 实战：构建一个 Todo CRUD API</font></strong></h3><p>我们将创建一个完整的待办事项管理 API 接口。</p><p><code>src/redux/api/todoApi.js</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createApi, fetchBaseQuery &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit/query/react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> todoApi = <span class="title function_">createApi</span>(&#123;</span><br><span class="line">  <span class="attr">reducerPath</span>: <span class="string">&quot;todoApi&quot;</span>, <span class="comment">// 在 store 中的 key</span></span><br><span class="line">  <span class="attr">baseQuery</span>: <span class="title function_">fetchBaseQuery</span>(&#123; <span class="attr">baseUrl</span>: <span class="string">&quot;http://localhost:3000&quot;</span> &#125;), <span class="comment">// 基础 URL</span></span><br><span class="line">  <span class="comment">// 🏷️ 定义标签：用于标记哪些数据是相关的，方便后面自动刷新</span></span><br><span class="line">  <span class="attr">tagTypes</span>: [<span class="string">&quot;Todos&quot;</span>],</span><br><span class="line"></span><br><span class="line">  <span class="attr">endpoints</span>: <span class="function">(<span class="params">builder</span>) =&gt;</span> (&#123;</span><br><span class="line">    <span class="comment">// 1. 查询列表 (query)</span></span><br><span class="line">    <span class="attr">getTodos</span>: builder.<span class="title function_">query</span>(&#123;</span><br><span class="line">      <span class="attr">query</span>: <span class="function">() =&gt;</span> <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">      <span class="comment">// 将返回的列表标记为 &#x27;Todos&#x27; 标签，且 ID 为 &#x27;LIST&#x27;</span></span><br><span class="line">      <span class="attr">providesTags</span>: <span class="function">(<span class="params">result</span>) =&gt;</span> <span class="comment">// 获取接口返回的</span></span><br><span class="line">        result ? [...result.<span class="title function_">map</span>(<span class="function">(<span class="params">&#123; id &#125;</span>) =&gt;</span> (&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, id &#125;)),&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;]: [&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;]</span><br><span class="line">    &#125;),</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 2. 添加数据 (mutation)</span></span><br><span class="line">    <span class="attr">addTodo</span>: builder.<span class="title function_">mutation</span>(&#123;</span><br><span class="line">      <span class="attr">query</span>: <span class="function">(<span class="params">newTodo</span>) =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">&quot;/todos&quot;</span>,</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>,</span><br><span class="line">        <span class="attr">body</span>: newTodo,</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="comment">// 关键：添加后使 &#x27;LIST&#x27; 标签失效，触发列表自动重新请求</span></span><br><span class="line">      <span class="attr">invalidatesTags</span>: [&#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;],</span><br><span class="line">    &#125;),</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3. 删除数据 (mutation)</span></span><br><span class="line">    <span class="attr">deleteTodo</span>: builder.<span class="title function_">mutation</span>(&#123;</span><br><span class="line">      <span class="attr">query</span>: <span class="function">(<span class="params">id</span>) =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">url</span>: <span class="string">`/todos/<span class="subst">$&#123;id&#125;</span>`</span>,</span><br><span class="line">        <span class="attr">method</span>: <span class="string">&quot;DELETE&quot;</span>,</span><br><span class="line">      &#125;),</span><br><span class="line">      <span class="comment">// 删除后，让该特定 ID 的数据和列表都失效</span></span><br><span class="line">      <span class="attr">invalidatesTags</span>: <span class="function">(<span class="params">result, error, id</span>) =&gt;</span> [</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, id &#125;,</span><br><span class="line">        &#123; <span class="attr">type</span>: <span class="string">&quot;Todos&quot;</span>, <span class="attr">id</span>: <span class="string">&quot;LIST&quot;</span> &#125;,</span><br><span class="line">      ],</span><br><span class="line">    &#125;),</span><br><span class="line">  &#125;),</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自动生成的 Hooks (格式：use + 端点名 + Query/Mutation)</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> &#123; useGetTodosQuery, useAddTodoMutation, useDeleteTodoMutation &#125; =</span><br><span class="line">  todoApi;</span><br></pre></td></tr></table></figure><h4 id="2）配置-Store-中间件"><a href="#2）配置-Store-中间件" class="headerlink" title="2）配置 Store 中间件"></a><strong><font color='#10c300'>2）配置 Store 中间件</font></strong></h4><p>RTKQ 需要专门的 Reducer 和中间件来处理缓存管理。</p><p><code>src/redux/store.js</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; configureStore &#125; <span class="keyword">from</span> <span class="string">&quot;@reduxjs/toolkit&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; todoApi &#125; <span class="keyword">from</span> <span class="string">&quot;./api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> store = <span class="title function_">configureStore</span>(&#123;</span><br><span class="line">  <span class="attr">reducer</span>: &#123;</span><br><span class="line">    <span class="comment">// 必须添加 api 的 reducer</span></span><br><span class="line">    [todoApi.<span class="property">reducerPath</span>]: todoApi.<span class="property">reducer</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 必须添加 api 的中间件，用于启用缓存、轮询、失效等功能</span></span><br><span class="line">  <span class="attr">middleware</span>: <span class="function">(<span class="params">getDefaultMiddleware</span>) =&gt;</span></span><br><span class="line">    <span class="title function_">getDefaultMiddleware</span>().<span class="title function_">concat</span>(todoApi.<span class="property">middleware</span>),</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="3）在组件中使用-Hooks"><a href="#3）在组件中使用-Hooks" class="headerlink" title="3）在组件中使用 Hooks"></a><strong><font color='#10c300'>3）在组件中使用 Hooks</font></strong></h4><p>RTKQ 返回的 Hook 包含了所有你需要的状态，无需额外定义 <code>useState</code>。</p><p><code>src/pages/TodoPage.jsx</code></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><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><span class="line">38</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;</span><br><span class="line">  useGetTodosQuery,</span><br><span class="line">  useAddTodoMutation,</span><br><span class="line">  useDeleteTodoMutation,</span><br><span class="line">&#125; <span class="keyword">from</span> <span class="string">&quot;../redux/api/todoApi&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">TodoPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 1. 获取数据：自动触发请求，且带有丰富的状态</span></span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">data</span>: todos, error, isLoading, isFetching &#125; = <span class="title function_">useGetTodosQuery</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 2. 变更操作：返回一个触发函数 (trigger) 和操作状态 (rest)</span></span><br><span class="line">  <span class="keyword">const</span> [addTodo, &#123; <span class="attr">isLoading</span>: isAdding &#125;] = <span class="title function_">useAddTodoMutation</span>();</span><br><span class="line">  <span class="keyword">const</span> [deleteTodo] = <span class="title function_">useDeleteTodoMutation</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isLoading) <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>初次加载中...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  <span class="keyword">if</span> (error) <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>请求出错: &#123;error.message&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</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">h3</span>&gt;</span>待办事项 &#123;isFetching &amp;&amp; &quot; (同步中...)&quot;&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></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">disabled</span>=<span class="string">&#123;isAdding&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> addTodo(&#123; title: &quot;新任务&quot;, completed: false &#125;)&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        &#123;isAdding ? &quot;添加中...&quot; : &quot;添加任务&quot;&#125;</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 class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;todos.map((todo) =&gt; (</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            &#123;todo.title&#125;</span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> deleteTodo(todo.id)&#125;&gt;删除<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        ))&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ul</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><h4 id="4）核心机制详解：Tags-标签-系统"><a href="#4）核心机制详解：Tags-标签-系统" class="headerlink" title="4）核心机制详解：Tags (标签) 系统"></a><strong><font color='#10c300'>4）核心机制详解：Tags (标签) 系统</font></strong></h4><p>这是 RTK Query 的灵魂。它通过“发布-订阅”模式管理缓存：</p><ol><li><strong><code>providesTags</code> (发布)</strong>：告诉 RTKQ 这份数据贴了什么标签（比如 <code>Todos</code>）。</li><li><strong><code>invalidatesTags</code> (撤销)</strong>：当执行修改操作后，高速 RTKQ 哪些标签过期了。</li><li><strong>自动重刷新</strong>：RTKQ 发现当前页面正在使用的 <code>getTodos</code> 订阅了 <code>Todos</code> 标签，而该标签由于 <code>addTodo</code> 变成了“失效”状态，于是 RTKQ 会<strong>自动</strong>重新发起网络请求，同步最新的列表。</li></ol><blockquote><p>[!TIP]<br><strong>缓存自动清理</strong><br>如果没有任何组件订阅某个 API 端点的数据（即没在任何地方调用该 Hook），RTKQ 会在一段时间后（默认 60 秒）自动从内存中删除这些垃圾数据。</p></blockquote><br><h3 id="3-4-性能优化：Memoization"><a href="#3-4-性能优化：Memoization" class="headerlink" title="3.4 性能优化：Memoization"></a><strong><font color='red'>3.4 性能优化：Memoization</font></strong></h3><p>每当 Redux Store 更新时，所有使用 <code>useSelector</code> 的组件都会重新计算其 Selector。</p><ul><li><strong>避免引用陷阱</strong>：如果在 Selector 中返回了新数组或对象（如 <code>state.items.filter(...)</code>），必须使用记忆化工具。</li><li><strong>推荐方案</strong>：使用 <strong><code>createSelector</code></strong>。它不仅能缓存昂贵的计算结果，还能通过保持引用一致性来防止子组件的不必要重渲染。</li></ul><blockquote><p>[!TIP]<br>详细的 <code>createSelector</code> 配置与代码片段请参考前文：<a href="#24-%E8%AE%B0%E5%BF%86%E5%8C%96%E9%80%89%E6%8B%A9%E5%99%A8createselector">2.4 记忆化选择器：createSelector</a>。</p></blockquote><br><h3 id="3-5-目录结构最佳实践"><a href="#3-5-目录结构最佳实践" class="headerlink" title="3.5 目录结构最佳实践"></a><strong><font color='red'>3.5 目录结构最佳实践</font></strong></h3><p>推荐基于<strong>功能 (Feature-based)</strong> 的文件夹结构，而不是按文件类型（actions&#x2F;reducers）分类。</p><figure class="highlight text"><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">├── 📁 src/</span><br><span class="line">│   ├── 📁 app/                # 全局配置 (Store, 路由配置)</span><br><span class="line">│   │   └── store.js</span><br><span class="line">│   ├── 📁 assets/             # 静态资源 (图片, 字体, 全局样式)</span><br><span class="line">│   ├── 📁 components/         # 通用基础组件 (非业务 logic)</span><br><span class="line">│   ├── 📁 features/           # 业务功能模块 (Slice + 业务组件 + API)</span><br><span class="line">│   │   └── 📁 counter/        # 示例功能</span><br><span class="line">│   │       ├── counterSlice.js</span><br><span class="line">│   │       └── Counter.jsx</span><br><span class="line">│   ├── 📁 hooks/              # 通用 Hooks (useAuth, useTheme 等)</span><br><span class="line">│   ├── 📁 pages/              # 页面级组件 (路由入口)</span><br><span class="line">│   ├── 📁 utils/              # 工具函数 (formatDate, validators 等)</span><br><span class="line">│   ├── App.jsx                # 根组件</span><br><span class="line">│   └── index.js               # 入口文件</span><br></pre></td></tr></table></figure><p><strong>核心说明</strong>：</p><ul><li><strong>Feature-first</strong>: <code>features/</code> 目录是核心。将一个功能的所有相关代码（Slice, 组件, API）放在同一个文件夹下，保持高内聚。</li><li><strong>Layered Structure</strong>: <code>components/</code> 存放纯 UI 组件，<code>pages/</code> 存放页面容器，<code>features/</code> 存放业务逻辑，层次分明。</li></ul><br><h2 id="四、总结"><a href="#四、总结" class="headerlink" title="四、总结"></a>四、总结</h2><ol><li><strong>始终使用 Redux Toolkit</strong>：不要再手写传统的 Redux 样板代码。</li><li><strong>State 范式化</strong>：尽量保持 State 扁平化，避免深层嵌套。</li><li><strong>不要把所有数据都放进 Redux</strong>：<ul><li>Form state -&gt; Local state (<code>useState</code>)</li><li>Server cache -&gt; RTK Query</li><li>Global UI state &#x2F; Shared data -&gt; Redux Slice</li></ul></li><li><strong>TypeScript</strong>：RTK 对 TypeScript 支持极佳，利用它可以获得强大的类型提示。</li></ol><hr><p><em>Happy Coding with React &amp; Redux!</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;React-Redux-全面指南-Redux-Toolkit&quot;&gt;&lt;a href=&quot;#React-Redux-全面指南-Redux-Toolkit&quot; class=&quot;headerlink&quot; title=&quot;React Redux 全面指南 (Redux Toolkit)</summary>
      
    
    
    
    <category term="前端开发" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="React" scheme="http://example.com/tags/React/"/>
    
    <category term="Redux" scheme="http://example.com/tags/Redux/"/>
    
    <category term="RTK" scheme="http://example.com/tags/RTK/"/>
    
  </entry>
  
  <entry>
    <title>React Router 全面指南 (v6.4+ &amp; v7)</title>
    <link href="http://example.com/posts/e2d3b4a5wq.html"/>
    <id>http://example.com/posts/e2d3b4a5wq.html</id>
    <published>2026-02-11T11:00:00.000Z</published>
    <updated>2026-04-02T08:39:33.922Z</updated>
    
    <content type="html"><![CDATA[<h1 id="React-Router-全面指南-v6-4-v7"><a href="#React-Router-全面指南-v6-4-v7" class="headerlink" title="React Router 全面指南 (v6.4+ &amp; v7)"></a>React Router 全面指南 (v6.4+ &amp; v7)</h1><blockquote><p>📚 本指南旨在帮助开发者掌握 React Router 的最新特性 (v6.4+ Data APIs) 及企业级封装写法。</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80%E5%BC%95%E8%A8%80">引言</a></li><li><a href="#%E4%BA%8C%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B-data-router">快速上手 (Data Router)</a></li><li><a href="#%E4%B8%89%E8%B7%AF%E7%94%B1%E6%A0%B8%E5%BF%83%E5%9F%BA%E7%A1%80-routing">路由核心基础 (Routing)</a></li><li><a href="#%E5%9B%9Bdata-api-%E8%BF%9B%E9%98%B6">Data API (进阶)</a></li><li><a href="#%E4%BA%94%E5%B0%81%E8%A3%85%E5%BC%8F%E8%B7%AF%E7%94%B1%E9%85%8D%E7%BD%AE-best-practice">封装式路由配置</a></li><li><a href="#%E5%85%AD%E8%BF%9B%E9%98%B6%E4%B8%8E%E4%BC%98%E5%8C%96">进阶与优化</a></li><li><a href="#%E4%B8%83%E5%B8%B8%E8%A7%81%E8%AF%AF%E5%8C%BA-common-pitfalls">常见误区 (Common Pitfalls)</a></li><li><a href="#%E5%85%AB%E6%80%BB%E7%BB%93">总结</a></li></ol><br><h2 id="一、引言"><a href="#一、引言" class="headerlink" title="一、引言"></a><strong>一、引言</strong></h2><h3 id="1-1-为什么需要路由？-SPA-vs-MPA"><a href="#1-1-为什么需要路由？-SPA-vs-MPA" class="headerlink" title="1.1 为什么需要路由？ (SPA vs MPA)"></a><strong><font color='red'>1.1 为什么需要路由？ (SPA vs MPA)</font></strong></h3><p>在传统的多页应用 (MPA) 中，每次点击链接，浏览器都会向服务器请求一个新的 HTML 页面，导致页面刷新（白屏闪烁）。</p><p><strong>React Router</strong> 帮助我们构建 <strong>单页应用 (SPA)</strong>：</p><ol><li><strong>无刷新跳转</strong>：点击链接时，只通过 JavaScript 更新页面内容，无需重新加载整个页面。</li><li><strong>体验如原生应用</strong>：丝滑的页面切换效果。</li><li><strong>URL 与 UI 同步</strong>：通过 URL (<code>/about</code>) 直接定位到特定组件 (<code>&lt;About /&gt;</code>)。</li></ol><br><h3 id="1-2-React-Router-v6-4-Data-APIs"><a href="#1-2-React-Router-v6-4-Data-APIs" class="headerlink" title="1.2 React Router v6.4+ (Data APIs)"></a><strong><font color='red'>1.2 React Router v6.4+ (Data APIs)</font></strong></h3><p>这是 React Router 的一次革命性更新，引入了 <strong>Data APIs</strong>。这不仅是定义路由的新方式，更解决了 React 应用中常见的“加载瀑布流”问题。</p><p><strong>核心优势：</strong></p><ul><li><strong>并行加载</strong>：路由与数据获取并行进行，极大提升首屏速度。</li><li><strong>配置化路由</strong>：使用对象数组 (<code>createBrowserRouter</code>) 管理路由，比 JSX 写法更清晰、更易维护。</li><li><strong>自动状态管理</strong>：内置 <code>loading</code>、<code>error</code> 状态处理，减少大量样板代码。</li></ul><br><h3 id="1-3-React-Router-v7-三种使用模式"><a href="#1-3-React-Router-v7-三种使用模式" class="headerlink" title="1.3 React Router v7+三种使用模式"></a><strong><font color='red'>1.3 React Router v7+三种使用模式</font></strong></h3><p>React Router 的这三种模式反映了它从一个简单的组件库演变为功能完备的数据框架的历程。</p><h3 id="1-三种模式的区别"><a href="#1-三种模式的区别" class="headerlink" title="1. 三种模式的区别"></a>1. 三种模式的区别</h3><table><thead><tr><th align="left">模式</th><th align="left">核心理念</th><th align="left">使用场景</th></tr></thead><tbody><tr><td align="left"><strong>Declarative (声明式)</strong></td><td align="left">传统的组件化路由，使用 <code>&lt;Routes&gt;</code> 和 <code>&lt;Route&gt;</code> 直接写在 JSX 中。</td><td align="left">简单的 CSR 应用，不需要路由驱动的数据预加载。</td></tr><tr><td align="left"><strong>Data (数据驱动)</strong></td><td align="left"><strong>React Router v6.4+ 推荐方式</strong>。使用 <code>createBrowserRouter</code> 定义路由对象。支持 <code>loader</code>（预加载数据）、<code>action</code>（数据修改）和 <code>errorElement</code>。</td><td align="left">中大型应用，希望通过路由并行加载数据、处理表单提交和错误边界。</td></tr><tr><td align="left"><strong>Framework (框架模式)</strong></td><td align="left"><strong>React Router v7 (原 Remix)</strong>。将 React Router 作为全栈框架使用，处理 SSR（服务端渲染）、静态生成等。</td><td align="left">需要 SSR、SEO 优化或复杂全栈逻辑的项目。</td></tr></tbody></table><hr><br><h2 id="二、快速上手-Data-Router"><a href="#二、快速上手-Data-Router" class="headerlink" title="二、快速上手 (Data Router)"></a><strong>二、快速上手 (Data Router)</strong></h2><h3 id="2-1-安装"><a href="#2-1-安装" class="headerlink" title="2.1 安装"></a><strong><font color='red'>2.1 安装</font></strong></h3><figure class="highlight bash"><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"># 安装最新版 React Router DOM (包含了核心 React Router)</span></span><br><span class="line">npm install react-router-dom</span><br></pre></td></tr></table></figure><blockquote><p><strong>提示</strong>：安装完成后，检查 <code>package.json</code> 中的 <code>dependencies</code>，确认版本号 <code>&gt;= 6.4</code>。</p></blockquote><br><h3 id="2-2-基本使用"><a href="#2-2-基本使用" class="headerlink" title="2.2 基本使用"></a><strong><font color='red'>2.2 基本使用</font></strong></h3><p>与传统的组件式 (<code>&lt;BrowserRouter&gt;</code>) 不同，新版推荐使用对象配置。</p><p><strong><code>src\router\index.js</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createBrowserRouter &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 创建路由器实例 (Router Instance)</span></span><br><span class="line"><span class="comment">// 这里定义了 URL 路径与 React 组件的映射关系</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>, <span class="comment">// 访问根路径 http://localhost:5173/ 时</span></span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Hello world!<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>, <span class="comment">// 渲染这个 JSX</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/about&quot;</span>, <span class="comment">// 访问 http://localhost:5173/about 时</span></span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>About<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>, <span class="comment">// 渲染 About 组件</span></span><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br></pre></td></tr></table></figure><p><strong><code>src\App.jsx</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">RouterProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&quot;./router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">&#123;<span class="comment">/* RouterProvider 组件用于包裹整个应用或路由部分，能直接生成路由界面 */</span>&#125;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br><h2 id="三、路由核心基础-Routing"><a href="#三、路由核心基础-Routing" class="headerlink" title="三、路由核心基础 (Routing)"></a><strong>三、路由核心基础 (Routing)</strong></h2><h3 id="3-1-数据加载-Loader"><a href="#3-1-数据加载-Loader" class="headerlink" title="3.1 数据加载 (Loader)"></a><strong><font color='red'>3.1 数据加载 (Loader)</font></strong></h3><blockquote><p><strong>注意</strong>: 这里展示的是基础概念，更深入的 Data API 用法请参考 <a href="#%E5%9B%9B-data-api-%E8%BF%9B%E9%98%B6">第四章 Data API</a>。</p></blockquote><p>这是 Data APIs 的基础功能。它允许路由在渲染组件<strong>之前</strong>并行加载数据，彻底解决了 React 应用中常见的“瀑布流加载”问题。</p><p><strong><font color='#00A6ED'>为什么使用 loader 而不是 verifyEffect？</font></strong></p><ul><li><strong>传统方式 (useEffect)</strong>: <code>加载组件 JS</code> -&gt; <code>渲染组件(Loading)</code> -&gt; <code>发起 Fetch</code> -&gt; <code>等待数据</code> -&gt; <code>渲染真实内容</code>。</li><li><strong>Loader 方式</strong>: <code>点击链接</code> -&gt; <code>并行加载组件 JS 和数据</code> -&gt; <code>渲染真实内容</code>。</li></ul><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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 定义数据加载函数 (Loader)</span></span><br><span class="line"><span class="comment">// 该函数会在路由跳转时自动执行，支持异步操作</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> <span class="title function_">homeLoader</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;/api/user&quot;</span>);</span><br><span class="line">  <span class="keyword">if</span> (!res.<span class="property">ok</span>) <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Failed to load&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> res.<span class="title function_">json</span>(); <span class="comment">// 返回的数据可以在组件中通过 useLoaderData 获取</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">const</span> routes = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Home</span> /&gt;</span></span>,</span><br><span class="line">    <span class="attr">loader</span>: homeLoader, <span class="comment">// &lt;--- 绑定 loader</span></span><br><span class="line">    <span class="attr">errorElement</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">ErrorPage</span> /&gt;</span></span>, <span class="comment">// 自动捕获 loader 抛出的错误</span></span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/user/:id&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">User</span> /&gt;</span></span>,</span><br><span class="line">    <span class="comment">// loader 函数接收一个 params 对象</span></span><br><span class="line">    <span class="attr">loader</span>: <span class="title function_">async</span> (&#123; params &#125;) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="title function_">fetch</span>(<span class="string">`/api/user/<span class="subst">$&#123;params.id&#125;</span>`</span>);</span><br><span class="line">      <span class="keyword">return</span> response.<span class="title function_">json</span>()</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/detail/:id&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Detail</span> /&gt;</span></span>,</span><br><span class="line">    <span class="attr">loader</span>: <span class="title function_">async</span> (&#123; params &#125;) =&gt; &#123;</span><br><span class="line">      <span class="keyword">const</span> response = <span class="title function_">fetch</span>(<span class="string">`/api/user/<span class="subst">$&#123;params.id&#125;</span>`</span>);</span><br><span class="line">      <span class="keyword">const</span> res = response.<span class="title function_">json</span>()</span><br><span class="line">      <span class="keyword">if</span>(!res.<span class="property">token</span>) &#123; <span class="comment">// 重定向</span></span><br><span class="line">          <span class="keyword">throw</span> <span class="title function_">redirect</span>(<span class="string">&#x27;/login&#x27;</span>)</span><br><span class="line">      &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">          <span class="keyword">return</span> res</span><br><span class="line">      &#125;</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br></pre></td></tr></table></figure><p><strong>在组件中获取数据：</strong></p><p>组件内部无需处理 <code>loading</code> 状态，因为 loader 执行完才会渲染组件（除非使用了 <code>defer</code>）。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useLoaderData &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Home</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="title function_">useLoaderData</span>(); <span class="comment">//直接获取 loader 返回的数据</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>Welcome, &#123;user.name&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-2-路由跳转-Navigation"><a href="#3-2-路由跳转-Navigation" class="headerlink" title="3.2 路由跳转 (Navigation)"></a><strong><font color='red'>3.2 路由跳转 (Navigation)</font></strong></h3><p>React Router 提供了两种主要的跳转方式：声明式和编程式。</p><h4 id="1）声明式跳转"><a href="#1）声明式跳转" class="headerlink" title="1）声明式跳转"></a><strong><font color='#10c300'>1）声明式跳转</font></strong></h4><p>用于构建用户界面中的导航链接，类似于 HTML 的 <code>&lt;a&gt;</code> 标签，但不会刷新页面。</p><ul><li><strong><code>&lt;Link&gt;</code></strong>: 基础链接组件。</li><li><strong><code>&lt;NavLink&gt;</code></strong>: 特殊的 Link，自动感知是否处于激活状态（常用于菜单）。</li></ul><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Link</span>, <span class="title class_">NavLink</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Menu</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">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 基础跳转 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/about&quot;</span>&gt;</span>关于我们<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* 带有激活样式的跳转 (Active Style) */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">NavLink</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">to</span>=<span class="string">&quot;/home&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">className</span>=<span class="string">&#123;(&#123;</span> <span class="attr">isActive</span> &#125;) =&gt;</span> (isActive ? &quot;active&quot; : &quot;&quot;)&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        首页</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">NavLink</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* replace: 替换当前历史记录，不留痕迹 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/login&quot;</span> <span class="attr">replace</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        登录 (Replace)</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）编程式跳转"><a href="#2）编程式跳转" class="headerlink" title="2）编程式跳转"></a><strong><font color='#10c300'>2）编程式跳转</font></strong></h4><p><code>useNavigate</code>在事件处理函数 (Event Handler) 或副作用 (Effect) 中执行跳转。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useNavigate &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">LoginPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> navigate = <span class="title function_">useNavigate</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleLogin</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 基础跳转</span></span><br><span class="line">    <span class="title function_">navigate</span>(<span class="string">&quot;/dashboard&quot;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 替换模式 (Replace)</span></span><br><span class="line">    <span class="title function_">navigate</span>(<span class="string">&quot;/home&quot;</span>, &#123; <span class="attr">replace</span>: <span class="literal">true</span> &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 后退/前进</span></span><br><span class="line">    <span class="title function_">navigate</span>(-<span class="number">1</span>); <span class="comment">// 后退一步</span></span><br><span class="line">    <span class="title function_">navigate</span>(<span class="number">1</span>); <span class="comment">// 前进一步</span></span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleLogin&#125;</span>&gt;</span>登录<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-3-参数传递-Params"><a href="#3-3-参数传递-Params" class="headerlink" title="3.3 参数传递 (Params)"></a><strong><font color='red'>3.3 参数传递 (Params)</font></strong></h3><p>这是开发中最常见的需求，React Router 支持三种主要的传参方式。</p><h4 id="1）路径参数-Path-Params"><a href="#1）路径参数-Path-Params" class="headerlink" title="1）路径参数 (Path Params)"></a><strong><font color='#10c300'>1）路径参数 (Path Params)</font></strong></h4><p>参数直接拼接在 URL 中，如 <code>/user/123</code>。</p><ul><li><strong>优点</strong>：刷新页面参数不丢失，语义清晰。</li><li><strong>定义</strong>：需要在路由配置中定义占位符 <code>:id</code>。</li></ul><p><strong>1️⃣路由定义：</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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// router/index.jsx</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&quot;/user/:id&quot;</span>, <span class="comment">// :id 是参数占位符</span></span><br><span class="line">  <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">UserPage</span> /&gt;</span></span>,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2️⃣跳转传参：</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></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Link</span> to=<span class="string">&quot;/user/123&quot;</span>&gt;查看用户 <span class="number">123</span>&lt;/<span class="title class_">Link</span>&gt;;</span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="title function_">navigate</span>(<span class="string">&quot;/user/123&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>3️⃣获取参数：</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><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useParams &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">UserPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; id &#125; = <span class="title function_">useParams</span>(); <span class="comment">// 获取到的 id 为 string 类型 &quot;123&quot;</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>User ID: &#123;id&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）查询参数-Query-Params"><a href="#2）查询参数-Query-Params" class="headerlink" title="2）查询参数 (Query Params)"></a><strong><font color='#10c300'>2）查询参数 (Query Params)</font></strong></h4><p>参数以 key&#x3D;value 形式拼接在 ? 后，如 <code>/list?page=1&amp;sort=desc</code>。</p><ul><li><strong>优点</strong>：无需修改路由定义，灵活多变。</li></ul><p><strong>1️⃣跳转传参：</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></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Link</span> to=<span class="string">&quot;/list?page=1&amp;sort=desc&quot;</span>&gt;商品列表&lt;/<span class="title class_">Link</span>&gt;;</span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="title function_">navigate</span>(<span class="string">&quot;/list?page=1&amp;sort=desc&quot;</span>);</span><br></pre></td></tr></table></figure><p><strong>2️⃣获取参数：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useSearchParams &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ListPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [searchParams, setSearchParams] = <span class="title function_">useSearchParams</span>();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 获取</span></span><br><span class="line">  <span class="keyword">const</span> page = searchParams.<span class="title function_">get</span>(<span class="string">&quot;page&quot;</span>); <span class="comment">// &quot;1&quot;</span></span><br><span class="line">  <span class="keyword">const</span> sort = searchParams.<span class="title function_">get</span>(<span class="string">&quot;sort&quot;</span>); <span class="comment">// &quot;desc&quot;</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 修改 (这会更新 URL 并重新渲染组件)</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">changeSort</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setSearchParams</span>(&#123; <span class="attr">page</span>: <span class="number">1</span>, <span class="attr">sort</span>: <span class="string">&quot;asc&quot;</span> &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Page: &#123;page&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）隐式状态-State"><a href="#3）隐式状态-State" class="headerlink" title="3）隐式状态 (State)"></a><strong><font color='#10c300'>3）隐式状态 (State)</font></strong></h4><p>参数不显示在 URL 中，存储在 history state 对象里。</p><ul><li><strong>优点</strong>：可传递对象、数组等复杂数据，URL 干净。</li><li><strong>缺点</strong>：<strong>刷新页面不丢失</strong>（很多误区认为会丢失，实际上 History State 刷新后依然存在），但<strong>复制链接给别人会丢失数据</strong>。</li></ul><p><strong>1️⃣跳转传参：</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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">Link</span> to=<span class="string">&quot;/profile&quot;</span> state=&#123;&#123; <span class="attr">role</span>: <span class="string">&quot;admin&quot;</span>, <span class="attr">code</span>: <span class="number">9527</span> &#125;&#125;&gt;</span><br><span class="line">  个人中心</span><br><span class="line">&lt;/<span class="title class_">Link</span>&gt;;</span><br><span class="line"><span class="comment">// 或者</span></span><br><span class="line"><span class="title function_">navigate</span>(<span class="string">&quot;/profile&quot;</span>, &#123; <span class="attr">state</span>: &#123; <span class="attr">role</span>: <span class="string">&quot;admin&quot;</span>, <span class="attr">code</span>: <span class="number">9527</span> &#125; &#125;);</span><br></pre></td></tr></table></figure><p><strong>2️⃣获取参数：</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><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="keyword">import</span> &#123; useLocation &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ProfilePage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> location = <span class="title function_">useLocation</span>();</span><br><span class="line">  <span class="keyword">const</span> &#123; role, code &#125; = location.<span class="property">state</span> || &#123;&#125;; <span class="comment">// 记得处理空值，防止直接访问报错</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Role: &#123;role&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-4-嵌套路由与-Outlet"><a href="#3-4-嵌套路由与-Outlet" class="headerlink" title="3.4 嵌套路由与 Outlet"></a><strong><font color='red'>3.4 嵌套路由与 Outlet</font></strong></h3><p>嵌套路由是 React Router 最核心、最强大的功能。它让我们能够将 UI 的嵌套结构与 URL 的路径结构完美对应。</p><h4 id="1）什么是嵌套路由？"><a href="#1）什么是嵌套路由？" class="headerlink" title="1）什么是嵌套路由？"></a><strong><font color='#10c300'>1）什么是嵌套路由？</font></strong></h4><p>在大多数应用中，界面通常是由多层嵌套的组件组成的。例如：</p><ul><li><strong>URL</strong>: <code>/dashboard/settings</code></li><li><strong>UI</strong>: 页面整体 -&gt; 侧边栏布局 (<code>Parent</code>) -&gt; 设置面板 (<code>Child</code>)</li></ul><p>React Router 通过 <strong>嵌套路由</strong> 自动帮我们将这种 URL 映射为组件层级：<br><code>/dashboard</code> -&gt; 渲染 <code>&lt;DashboardLayout /&gt;</code><br><code>/dashboard/settings</code> -&gt; 在 <code>&lt;DashboardLayout&gt;</code> 内部渲染 <code>&lt;Settings /&gt;</code></p><h4 id="2）嵌套配置"><a href="#2）嵌套配置" class="headerlink" title="2）嵌套配置"></a><strong><font color='#10c300'>2）嵌套配置</font></strong></h4><p>我们在路由配置中使用 <code>children</code> 数组来定义子路由。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/router/index.jsx</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/dashboard&quot;</span>, <span class="comment">// 1. 父路由路径</span></span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">DashboardLayout</span> /&gt;</span></span>, <span class="comment">// 2. 父组件（通常是布局文件）</span></span><br><span class="line">    <span class="comment">// 3. 定义子路由数组</span></span><br><span class="line">    <span class="attr">children</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">index</span>: <span class="literal">true</span>, <span class="comment">// 默认子路由 (访问 /dashboard 时显示，默认子路由里面不能再嵌套子路由)</span></span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Stats</span> /&gt;</span></span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&quot;settings&quot;</span>, <span class="comment">// 子路由路径 (注意：不要加 / )</span></span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Settings</span> /&gt;</span></span>, <span class="comment">// 访问 /dashboard/settings 时显示</span></span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br></pre></td></tr></table></figure><h4 id="3）父组件如何渲染子路由？"><a href="#3）父组件如何渲染子路由？" class="headerlink" title="3）父组件如何渲染子路由？"></a><strong><font color='#10c300'>3）父组件如何渲染子路由？</font></strong></h4><p>光配置了路由还不够，<strong>父组件必须明确指定“子组件显示在哪里”</strong>。如果不写 <code>&lt;Outlet /&gt;</code>，子路由的内容将不会渲染。</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><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">// src/layouts/DashboardLayout.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Outlet</span>, <span class="title class_">Link</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">DashboardLayout</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> <span class="attr">className</span>=<span class="string">&quot;layout&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 侧边栏导航 (始终显示) */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/dashboard&quot;</span>&gt;</span>概览<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/dashboard/settings&quot;</span>&gt;</span>设置<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">main</span> <span class="attr">className</span>=<span class="string">&quot;content&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;/* 🔥 关键点：子路由匹配到的组件 (Stats 或 Settings) 将会填入这个位置 */&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Outlet</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">main</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><h4 id="4）总结：路由匹配流程"><a href="#4）总结：路由匹配流程" class="headerlink" title="4）总结：路由匹配流程"></a><strong><font color='#10c300'>4）总结：路由匹配流程</font></strong></h4><p>当用户访问 <code>/dashboard/settings</code> 时：</p><ol><li>React Router 匹配到 <code>/dashboard</code> -&gt; 渲染 <code>&lt;DashboardLayout&gt;</code>。</li><li>React Router 继续匹配 <code>settings</code> -&gt; 找到 <code>&lt;Settings&gt;</code> 组件。</li><li>它将 <code>&lt;Settings&gt;</code> 组件作为 children 传递给 <code>&lt;Outlet /&gt;</code>，最终页面结构如下：</li></ol><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></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">DashboardLayout</span>&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">nav</span>&gt;</span>...<span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Settings</span> /&gt;</span> &#123;/* &lt;-- 这里就是 Outlet 渲染的内容 */&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">main</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">DashboardLayout</span>&gt;</span></span></span><br></pre></td></tr></table></figure><br><h3 id="3-5-路由懒加载"><a href="#3-5-路由懒加载" class="headerlink" title="3.5 路由懒加载"></a><strong><font color='red'>3.5 路由懒加载</font></strong></h3><p>随着应用规模的增长，打包后的 JavaScript 文件（Chunk）会变得越来越大，导致首屏加载缓慢。 <strong>路由懒加载</strong> 是优化 React 应用性能的关键手段。</p><h4 id="1）为什么要懒加载？"><a href="#1）为什么要懒加载？" class="headerlink" title="1）为什么要懒加载？"></a><strong><font color='#10c300'>1）为什么要懒加载？</font></strong></h4><ul><li><strong>默认行为</strong>：构建工具（如 Viper&#x2F;Webpack）会将所有页面组件打包进一个巨大的 JS 文件中。即使有些页面用户可能永远不会访问，浏览器也必须先下载这些代码。</li><li><strong>懒加载行为</strong>：将代码分割成多个小块（Chunks）。<strong>只有当用户点击跳转到特定路由时</strong>，浏览器才去下载该页面对应的 JS 代码。</li></ul><h4 id="2）核心-API：React-lazy-Suspense"><a href="#2）核心-API：React-lazy-Suspense" class="headerlink" title="2）核心 API：React.lazy &amp; Suspense"></a><strong><font color='#10c300'>2）核心 API：React.lazy &amp; Suspense</font></strong></h4><p>React 原生提供了 <code>lazy</code> 函数来实现动态导入，配合 <code>Suspense</code> 组件处理加载状态。</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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; lazy, <span class="title class_">Suspense</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createBrowserRouter &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 1. 使用 lazy 动态导入组件 (注意：不要在组件内部声明)</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">About</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./pages/About&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">//  2. 使用 Suspense 包裹懒加载组件，fallback 用于显示加载中状态</span></span><br><span class="line"><span class="comment">// 方式1：</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/about&quot;</span>,</span><br><span class="line">    <span class="attr">element</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">div</span>&gt;</span>Loading...<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">About</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><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式2：封装 Suspens(HOC高阶函数)</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">withSuspense</span> = (<span class="params">Component</span>) =&gt; (</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">Loading</span> /&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Component</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><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/about&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="title function_">withSuspense</span>(<span class="title class_">About</span>),</span><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式3：可在整个路由入口使用Suspense 包裹所有路由组件(前提全部路由懒加载)</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">div</span>&gt;</span>Loading...<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#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></pre></td></tr></table></figure><blockquote><p><strong>注意</strong>：如果不使用 <code>Suspense</code> 包裹，React 在等待组件加载时会抛出错误，导致应用崩溃。</p></blockquote><br><h3 id="3-6-路由模式"><a href="#3-6-路由模式" class="headerlink" title="3.6 路由模式"></a><strong><font color='red'>3.6 路由模式</font></strong></h3><p>React Router 主要支持两种路由模式：<strong>History 模式</strong> 和 <strong>Hash 模式</strong>。在 Data API (v6.4+) 中，它们分别对应 <code>createBrowserRouter</code> 和 <code>createHashRouter</code>。</p><h4 id="1）History-模式-推荐"><a href="#1）History-模式-推荐" class="headerlink" title="1）History 模式 (推荐)"></a><strong><font color='#10c300'>1）History 模式 (推荐)</font></strong></h4><p>这是现代 Web 应用的标准模式，使用 HTML5 History API (<code>pushState</code>, <code>replaceState</code>) 来管理 URL。</p><ul><li><strong>表现</strong>：URL 看起来像标准的路径，例如 <code>example.com/user/123</code>。</li><li><strong>优点</strong>：URL 美观，符合用户习惯；SEO 友好。</li><li><strong>缺点</strong>：<strong>需要服务器配置支持</strong>。因为是单页应用，当用户直接访问深层路径（如 <code>/user/123</code>）或刷新页面时，服务器必须返回 <code>index.html</code>，否则会出现 404 错误。</li></ul><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><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="keyword">import</span> &#123; createBrowserRouter, <span class="title class_">RouterProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  <span class="comment">// ...路由配置</span></span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#125;</span> /&gt;</span></span>;</span><br></pre></td></tr></table></figure><p><strong>服务器配置示例 (Nginx)：</strong></p><figure class="highlight nginx"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="section">location</span> / &#123;</span><br><span class="line">  <span class="attribute">try_files</span> <span class="variable">$uri</span> <span class="variable">$uri</span>/ /index.html;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）Hash-模式"><a href="#2）Hash-模式" class="headerlink" title="2）Hash 模式"></a><strong><font color='#10c300'>2）Hash 模式</font></strong></h4><p>使用 URL 的 Hash 部分 (<code>#</code>) 来模拟一个完整的 URL。</p><ul><li><strong>表现</strong>：URL 带有 <code>#</code> 号，例如 <code>example.com/#/user/123</code>。</li><li><strong>优点</strong>：兼容性极好（支持老旧浏览器）；<strong>不需要服务器额外配置</strong>（因为 <code>#</code> 后面的内容不会发送给服务器）。</li><li><strong>缺点</strong>：URL 不够美观；SEO 较差；某些第三方登录回调可能不支持 Hash 路径。</li></ul><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><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="keyword">import</span> &#123; createHashRouter, <span class="title class_">RouterProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createHashRouter</span>([</span><br><span class="line">  <span class="comment">// ...路由配置</span></span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#125;</span> /&gt;</span></span>;</span><br></pre></td></tr></table></figure><h4 id="3）如何选择？"><a href="#3）如何选择？" class="headerlink" title="3）如何选择？"></a><strong><font color='#10c300'>3）如何选择？</font></strong></h4><table><thead><tr><th align="left">特性</th><th align="left">History 模式 (<code>createBrowserRouter</code>)</th><th align="left">Hash 模式 (<code>createHashRouter</code>)</th></tr></thead><tbody><tr><td align="left"><strong>URL 外观</strong></td><td align="left">美观 (<code>/path</code>)</td><td align="left">带井号 (<code>/#/path</code>)</td></tr><tr><td align="left"><strong>服务器配置</strong></td><td align="left"><strong>需要</strong> (Nginx&#x2F;Apache rewrite)</td><td align="left"><strong>不需要</strong></td></tr><tr><td align="left"><strong>SEO</strong></td><td align="left">友好</td><td align="left">较差</td></tr><tr><td align="left"><strong>部署难度</strong></td><td align="left">中</td><td align="left">低</td></tr><tr><td align="left"><strong>推荐场景</strong></td><td align="left">公网项目、企业级应用</td><td align="left">内部工具、演示 Demo、无法控制服务器配置时</td></tr></tbody></table><h4 id="4）拓展"><a href="#4）拓展" class="headerlink" title="4）拓展"></a><strong><font color='#10c300'>4）拓展</font></strong></h4><p>1️⃣History 模式下为什么需要服务器配置支持，为什么当用户直接访问深层路径（如 <code>/user/123</code>）或刷新页面时，服务器必须返回 <code>index.html</code>，否则会出现 404 错误。</p><p>答：当你直接在浏览器地址栏输入 <a href="http://example.com/user/123">http://example.com/user/123</a> 并回车（或按刷新键）：</p><ol><li>浏览器：这被视为一次全新的访问。浏览器会老老实实向服务器发送一个 HTTP GET 请求：”请给我 &#x2F;user&#x2F;123 这个文件”。</li><li><strong>服务器</strong>：去它的硬盘文件系统里找<ul><li>它找有没有 user 文件夹？没有。</li><li>它找有没有 user&#x2F;123 文件？没有。</li><li>它找有没有 user&#x2F;123.html？也没有。</li><li>因为 SPA 项目打包后，服务器上通常只有一个 index.html 和一堆 .js&#x2F;.css 文件，根本不存在物理上的 &#x2F;user&#x2F;123 目录。</li></ul></li><li><strong>结果</strong>：服务器诚实地返回了 <strong>404 Not Found</strong>。</li></ol><br><h3 id="3-7-404-页面配置"><a href="#3-7-404-页面配置" class="headerlink" title="3.7 404 页面配置"></a><strong><font color='red'>3.7 404 页面配置</font></strong></h3><p>在 SPA 应用中，当用户访问了未定义的路径时，如果不做处理，页面可能会显示一片空白。我们需要配置一个 <strong>Catch-All 路由</strong> 来展示友好的 404 页面。</p><p>在 React Router v6 中，使用 <code>path: &quot;*&quot;</code> 即可匹配任意路径。由于 v6 内部有智能的路由排名算法（Ranking Algorithm），你不需要在这个路由上加 <code>exact</code>，也不强求将它放在路由数组的最后（但在 v5 中必须放在最后）。</p><h4 id="1）基础配置"><a href="#1）基础配置" class="headerlink" title="1）基础配置"></a><strong><font color='#10c300'>1）基础配置</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/router/index.jsx</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Home</span> /&gt;</span></span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/about&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">About</span> /&gt;</span></span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 🔥 404 路由配置（通常建议放在最后，便于阅读）</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">NotFound</span> /&gt;</span></span>,</span><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br></pre></td></tr></table></figure><h4 id="2）创建优雅的-404-组件"><a href="#2）创建优雅的-404-组件" class="headerlink" title="2）创建优雅的 404 组件"></a><strong><font color='#10c300'>2）创建优雅的 404 组件</font></strong></h4><p>简单的 404 页面应该包含错误提示和返回首页的按钮。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/pages/NotFound.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Link</span>, useNavigate &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">NotFound</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> navigate = <span class="title function_">useNavigate</span>();</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> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">textAlign:</span> &quot;<span class="attr">center</span>&quot;, <span class="attr">padding:</span> &quot;<span class="attr">50px</span>&quot; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">fontSize:</span> &quot;<span class="attr">72px</span>&quot; &#125;&#125;&gt;</span>404<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">fontSize:</span> &quot;<span class="attr">24px</span>&quot; &#125;&#125;&gt;</span>抱歉，您访问的页面不存在。<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">marginTop:</span> &quot;<span class="attr">20px</span>&quot; &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> navigate(-1)&#125; style=&#123;&#123; marginRight: &quot;10px&quot; &#125;&#125;&gt;</span></span><br><span class="line"><span class="language-xml">          返回上一页</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 class="language-xml"></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/&quot;</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 class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Link</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 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><h4 id="3）重定向到首页"><a href="#3）重定向到首页" class="headerlink" title="3）重定向到首页"></a><strong><font color='#10c300'>3）重定向到首页</font></strong></h4><p>有时候我们不希望显示 404 页面，而是直接将用户重定向回首页（例如在后台管理系统中）。可以使用 <code>&lt;Navigate&gt;</code> 组件。</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">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">  <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Navigate</span> <span class="attr">to</span>=<span class="string">&quot;/&quot;</span> <span class="attr">replace</span> /&gt;</span></span>, <span class="comment">// replace: true 防止用户点击后退时陷入死循环</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><hr><br><h2 id="四、Data-API-进阶"><a href="#四、Data-API-进阶" class="headerlink" title="四、Data API (进阶)"></a><strong>四、Data API (进阶)</strong></h2><p>React Router 6.4+ 引入的 Data API 不仅仅是关于路由跳转，它提供了一套完整的<strong>数据生命周期管理</strong>方案。这使得我们可以在路由层面处理数据获取、提交和错误处理，大大减少组件内部的副作用代码。</p><h3 id="4-1-数据加载-Loader"><a href="#4-1-数据加载-Loader" class="headerlink" title="4.1 数据加载 (Loader)"></a><strong><font color='red'>4.1 数据加载 (Loader)</font></strong></h3><blockquote><p><strong>注意</strong>：Loader 的基础用法已在 <a href="#31-%E6%95%B0%E6%8D%AE%E5%8A%A0%E8%BD%BD-loader">3.1 节</a> 中介绍。</p></blockquote><p>Loader 是 Data API 的读取端。它在路由渲染前运行，为组件提供数据。</p><p><strong>核心优势</strong>：</p><ol><li><strong>并行加载</strong>：路由组件和数据并行请求，消除瀑布流。</li><li><strong>数据&#x2F;UI 分离</strong>：组件只负责接收数据渲染，不负责 Fetch。</li></ol><br><h3 id="4-2-数据提交-Action"><a href="#4-2-数据提交-Action" class="headerlink" title="4.2 数据提交 (Action)"></a><strong><font color='red'>4.2 数据提交 (Action)</font></strong></h3><p>Action 是 Data API 的写入端，用于处理非 GET 请求（如 POST, PUT, DELETE）。它与 <code>&lt;Form&gt;</code> 组件配合，能在不手动编写 <code>onSubmit</code> 和 <code>fetch</code> 的情况下完成数据提交。</p><h4 id="1）定义-Action"><a href="#1）定义-Action" class="headerlink" title="1）定义 Action"></a><strong><font color='#10c300'>1）定义 Action</font></strong></h4><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="comment">// src/pages/Login.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; redirect &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">loginAction</span>(<span class="params">&#123; request &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// 获取表单数据</span></span><br><span class="line">  <span class="keyword">const</span> formData = <span class="keyword">await</span> request.<span class="title function_">formData</span>();</span><br><span class="line">  <span class="keyword">const</span> data = <span class="title class_">Object</span>.<span class="title function_">fromEntries</span>(formData);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 发起请求</span></span><br><span class="line">  <span class="keyword">await</span> <span class="title function_">fetch</span>(<span class="string">&quot;/api/login&quot;</span>, &#123; <span class="attr">method</span>: <span class="string">&quot;POST&quot;</span>, <span class="attr">body</span>: <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(data) &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 成功后重定向</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">redirect</span>(<span class="string">&quot;/dashboard&quot;</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）绑定-Config"><a href="#2）绑定-Config" class="headerlink" title="2）绑定 Config"></a><strong><font color='#10c300'>2）绑定 Config</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// router/index.jsx</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&quot;/login&quot;</span>,</span><br><span class="line">  <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Login</span> /&gt;</span></span>,</span><br><span class="line">  <span class="attr">action</span>: loginAction, <span class="comment">// 👈 绑定 action</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）使用-Form-组件"><a href="#3）使用-Form-组件" class="headerlink" title="3）使用 Form 组件"></a><strong><font color='#10c300'>3）使用 Form 组件</font></strong></h4><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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Form</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Login</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">Form</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</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">name</span>=<span class="string">&quot;username&quot;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</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">name</span>=<span class="string">&quot;password&quot;</span> <span class="attr">type</span>=<span class="string">&quot;password&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span>&gt;</span>登录<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="4-3-无导航交互-useFetcher"><a href="#4-3-无导航交互-useFetcher" class="headerlink" title="4.3 无导航交互 (useFetcher)"></a><strong><font color='red'>4.3 无导航交互 (useFetcher)</font></strong></h3><p>通常点击链接或提交表单会导致页面跳转。但有些操作我们<strong>不希望跳转页面</strong>，比如：<strong>点赞</strong>、<strong>加入购物车</strong>、<strong>Newsletter 订阅</strong>。这时使用 <code>useFetcher</code>。</p><p>提供了 <code>fetcher.Form</code>、<code>fetcher.submit</code>、<code>fetcher.load</code> 等方法，可以在不导航的情况下触发 loader 或 action。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useFetcher &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ProductItem</span>(<span class="params">&#123; product &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> fetcher = <span class="title function_">useFetcher</span>();</span><br><span class="line">  <span class="keyword">const</span> isSubmitting = fetcher.<span class="property">state</span> === <span class="string">&quot;submitting&quot;</span>;</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> <span class="attr">className</span>=<span class="string">&quot;product-card&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>&#123;product.name&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 这里的提交不会导致页面跳转，但会触发 action 并自动重新验证页面数据 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">fetcher.Form</span> <span class="attr">method</span>=<span class="string">&quot;post&quot;</span> <span class="attr">action</span>=<span class="string">&quot;/add-to-cart&quot;</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">type</span>=<span class="string">&quot;hidden&quot;</span> <span class="attr">name</span>=<span class="string">&quot;productId&quot;</span> <span class="attr">value</span>=<span class="string">&#123;product.id&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">disabled</span>=<span class="string">&#123;isSubmitting&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;isSubmitting ? &quot;添加中...&quot; : &quot;加入购物车&quot;&#125;</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 class="language-xml">      <span class="tag">&lt;/<span class="name">fetcher.Form</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><br><h3 id="4-4-错误边界-ErrorElement"><a href="#4-4-错误边界-ErrorElement" class="headerlink" title="4.4 错误边界 (ErrorElement)"></a><strong><font color='red'>4.4 错误边界 (ErrorElement)</font></strong></h3><p>每个路由都可以定义一个 <code>errorElement</code>。当 <code>loader</code>、<code>action</code> 或组件渲染过程中抛出错误时，React Router 会捕获它并渲染 <code>errorElement</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></pre></td><td class="code"><pre><span class="line">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">  <span class="attr">loader</span>: <span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;Oh no!&quot;</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Home</span> /&gt;</span></span>,</span><br><span class="line">  <span class="attr">errorElement</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">ErrorPage</span> /&gt;</span></span>, <span class="comment">// 👈 用户将看到这个组件</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><br><h2 id="五、封装式路由配置-Best-Practice"><a href="#五、封装式路由配置-Best-Practice" class="headerlink" title="五、封装式路由配置 (Best Practice)"></a><strong>五、封装式路由配置 (Best Practice)</strong></h2><p>在真实项目中，我们将路由配置抽离到独立文件中管理，使其结构更清晰。</p><h3 id="5-1-目录结构"><a href="#5-1-目录结构" class="headerlink" title="5.1 目录结构"></a><strong><font color='red'>5.1 目录结构</font></strong></h3><p>推荐在 <code>src/router</code> 目录下管理路由：</p><figure class="highlight plaintext"><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">src/</span><br><span class="line">├── main.jsx         &lt;-- 入口文件</span><br><span class="line">├── pages/</span><br><span class="line">│   ├── Home.jsx</span><br><span class="line">│   ├── Login.jsx</span><br><span class="line">│   └── Dashboard.jsx</span><br><span class="line">├── router/</span><br><span class="line">│   └── index.jsx    &lt;-- 路由配置文件</span><br></pre></td></tr></table></figure><br><h3 id="5-2-路由配置文件"><a href="#5-2-路由配置文件" class="headerlink" title="5.2 路由配置文件"></a><strong><font color='red'>5.2 路由配置文件</font></strong></h3><p>利用 <code>createBrowserRouter</code> 和 <code>lazy</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// src/router/index.jsx</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> &#123; lazy &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; createBrowserRouter, <span class="title class_">Navigate</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 懒加载页面组件</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Home</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../pages/Home&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">About</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../pages/About&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Dashboard</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../pages/Dashboard&quot;</span>));</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Login</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;../pages/Login&quot;</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">// 加载中组件</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">Loading</span> = (<span class="params"></span>) =&gt; <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;p-4&quot;</span>&gt;</span>Loading...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 路由表定义</span></span><br><span class="line"><span class="keyword">const</span> routes = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="title class_">Home</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/login&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="title class_">Login</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/dashboard&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="title class_">Dashboard</span>,</span><br><span class="line">    <span class="comment">// 嵌套路由配置</span></span><br><span class="line">    <span class="attr">children</span>: [</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&quot;stats&quot;</span>, <span class="comment">// 路径为 /dashboard/stats   子路由不要加&#x27;/&#x27;</span></span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Stats View<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&quot;settings&quot;</span>, <span class="comment">// 路径为 /dashboard/settings</span></span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>Settings View<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">  <span class="comment">// 404 路由处理</span></span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;*&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Navigate</span> <span class="attr">to</span>=<span class="string">&quot;/&quot;</span> <span class="attr">replace</span> /&gt;</span></span>,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line"></span><br><span class="line"><span class="comment">// 创建并导出路由实例</span></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>(routes);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br></pre></td></tr></table></figure><br><h3 id="5-3-接入"><a href="#5-3-接入" class="headerlink" title="5.3 接入"></a><strong><font color='red'>5.3 接入</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// App.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">RouterProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Suspense</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&quot;./router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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;&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">div</span>&gt;</span>Loading...<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#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;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><hr><br><h2 id="六、进阶与优化"><a href="#六、进阶与优化" class="headerlink" title="六、进阶与优化"></a><strong>六、进阶与优化</strong></h2><p>在封装式写法中，我们可以创建一个包装组件来保护路由。</p><h3 id="6-1-路由守卫-Protected-Routes"><a href="#6-1-路由守卫-Protected-Routes" class="headerlink" title="6.1 路由守卫 (Protected Routes)"></a><strong><font color='red'>6.1 路由守卫 (Protected Routes)</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// components/AuthGuard.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Navigate</span>, useLocation &#125; <span class="keyword">from</span> <span class="string">&#x27;react-router-dom&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">AuthGuard</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isLogged = <span class="literal">false</span>; <span class="comment">// 替换为真实的鉴权逻辑</span></span><br><span class="line">  <span class="keyword">const</span> location = <span class="title function_">useLocation</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (!isLogged) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Navigate</span> <span class="attr">to</span>=<span class="string">&quot;/login&quot;</span> <span class="attr">state</span>=<span class="string">&#123;&#123;</span> <span class="attr">from:</span> <span class="attr">location</span> &#125;&#125; <span class="attr">replace</span> /&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> children;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// src/router/index.js 使用</span></span><br><span class="line">&#123;</span><br><span class="line">  <span class="attr">path</span>: <span class="string">&#x27;/admin&#x27;</span>,</span><br><span class="line">  <span class="attr">element</span>: (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">AuthGuard</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">AdminPage</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">AuthGuard</span>&gt;</span></span></span><br><span class="line">  ),</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="6-2-全局-Loading-状态"><a href="#6-2-全局-Loading-状态" class="headerlink" title="6.2 全局 Loading 状态"></a><strong><font color='red'>6.2 全局 Loading 状态</font></strong></h3><p>使用 <code>useNavigation</code> 监听全局路由跳转状态。</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useNavigation, <span class="title class_">Outlet</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">RootLayout</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> navigation = <span class="title function_">useNavigation</span>();</span><br><span class="line">  <span class="keyword">const</span> isLoading = navigation.<span class="property">state</span> === <span class="string">&quot;loading&quot;</span>;</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;isLoading &amp;&amp; <span class="tag">&lt;<span class="name">GlobalSpinner</span> /&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Outlet</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><br><h2 id="七、常见误区-Common-Pitfalls"><a href="#七、常见误区-Common-Pitfalls" class="headerlink" title="七、常见误区 (Common Pitfalls)"></a><strong>七、常见误区 (Common Pitfalls)</strong></h2><h3 id="7-1-错误使用-Layout-组件-Context-丢失"><a href="#7-1-错误使用-Layout-组件-Context-丢失" class="headerlink" title="7.1 错误使用 Layout 组件 (Context 丢失)"></a><strong><font color='red'>7.1 错误使用 Layout 组件 (Context 丢失)</font></strong></h3><p>很多初学者容易犯的一个错误是：<strong>将布局组件 (<code>Layout</code> &#x2F; <code>Menu</code>) 防止在 <code>RouterProvider</code> 之外</strong>。</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// App.jsx</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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> <span class="attr">className</span>=<span class="string">&quot;app&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Layout</span> /&gt;</span> &#123;/* ❌ 错误：Layout 在 RouterContext 之外 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#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><p><strong>问题后果：</strong></p><ol><li><strong>无法使用 <code>&lt;Link&gt;</code> &#x2F; <code>&lt;NavLink&gt;</code></strong>：点击菜单会报错 <code>useHref() may be used only in the context of a &lt;Router&gt; component.</code></li><li><strong>无法使用 hooks</strong>：<code>useLocation</code>、<code>useNavigate</code> 等 Hook 均无法工作。</li><li><strong>路由状态无法共享</strong>：布局组件无法感知当前的路由变化。</li><li>如果Layout中是固定的内容，不涉及路由跳转等，布局组件则可以写在 <code>RouterProvider</code> 之外</li></ol><p><strong>最佳实践</strong>：是利用 <strong>嵌套路由 (Nested Routes)</strong> 和 <code>&lt;Outlet /&gt;</code>。</p><p><strong>第一步：创建布局组件 (src&#x2F;components&#x2F;Layout.jsx)</strong></p><p>利用 <code>&lt;Outlet /&gt;</code> 渲染子路由内容：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Outlet</span>, <span class="title class_">Link</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Layout</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> <span class="attr">className</span>=<span class="string">&quot;layout&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/&quot;</span>&gt;</span>首页<span class="tag">&lt;/<span class="name">Link</span>&gt;</span> | <span class="tag">&lt;<span class="name">Link</span> <span class="attr">to</span>=<span class="string">&quot;/about&quot;</span>&gt;</span>关于<span class="tag">&lt;/<span class="name">Link</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">nav</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">hr</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 子路由的内容将渲染在这里 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Outlet</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Layout</span>;</span><br></pre></td></tr></table></figure><p><strong>第二步：配置嵌套路由 (src&#x2F;router&#x2F;index.jsx)</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><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="keyword">import</span> &#123; createBrowserRouter &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Layout</span> <span class="keyword">from</span> <span class="string">&quot;../components/Layout&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Home</span> <span class="keyword">from</span> <span class="string">&quot;../pages/Home&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">About</span> <span class="keyword">from</span> <span class="string">&quot;../pages/About&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> router = <span class="title function_">createBrowserRouter</span>([</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">path</span>: <span class="string">&quot;/&quot;</span>,</span><br><span class="line">    <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Layout</span> /&gt;</span></span>, <span class="comment">// 1. 根路径渲染布局</span></span><br><span class="line">    <span class="attr">children</span>: [</span><br><span class="line">      <span class="comment">// 2. 子路由渲染在 Layout 的 Outlet 中</span></span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">index</span>: <span class="literal">true</span>, <span class="comment">// 默认子路由 (对应 path: &quot;/&quot;)</span></span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">Home</span> /&gt;</span></span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">path</span>: <span class="string">&quot;about&quot;</span>,</span><br><span class="line">        <span class="attr">element</span>: <span class="language-xml"><span class="tag">&lt;<span class="name">About</span> /&gt;</span></span>,</span><br><span class="line">      &#125;,</span><br><span class="line">    ],</span><br><span class="line">  &#125;,</span><br><span class="line">]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> router;</span><br></pre></td></tr></table></figure><p><strong>第三步：入口文件 (src&#x2F;main.jsx)</strong></p><p>入口文件保持简洁，只需注入 <code>router</code>：</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><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> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ReactDOM</span> <span class="keyword">from</span> <span class="string">&quot;react-dom/client&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">RouterProvider</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-router-dom&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> router <span class="keyword">from</span> <span class="string">&quot;./router&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(<span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;root&quot;</span>)).<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">React.StrictMode</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    &#123;/*  默认这里是APP组件，也可以将<span class="tag">&lt;<span class="name">RouterProvider</span> <span class="attr">router</span>=<span class="string">&#123;router&#125;</span> /&gt;</span>写到APP组件中去 */&#125;</span></span><br><span class="line"><span class="language-xml">    &#123;/*  <span class="tag">&lt;<span class="name">App</span> /&gt;</span> */&#125;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">React.StrictMode</span>&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><hr><br><h2 id="八、总结"><a href="#八、总结" class="headerlink" title="八、总结"></a><strong>八、总结</strong></h2><p>React Router v6.4+ 定义了现代 React 应用的路由标准：</p><ol><li><strong>使用 <code>createBrowserRouter</code></strong> 进行集中式路由管理。</li><li><strong>利用 <code>loader</code></strong> 提前加载数据，提升首屏体验。</li><li><strong>结合 <code>Suspense</code> 和 <code>lazy</code></strong> 实现代码分割。</li><li><strong>目录结构规范化</strong>，将路由逻辑与 UI 组件分离。</li></ol><p>掌握这套模式，你将能构建出性能更优、架构更清晰的 React 应用。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;React-Router-全面指南-v6-4-v7&quot;&gt;&lt;a href=&quot;#React-Router-全面指南-v6-4-v7&quot; class=&quot;headerlink&quot; title=&quot;React Router 全面指南 (v6.4+ &amp;amp; v7)&quot;&gt;&lt;/a&gt;Re</summary>
      
    
    
    
    <category term="前端开发" scheme="http://example.com/categories/%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
    
    <category term="React" scheme="http://example.com/tags/React/"/>
    
    <category term="React Router" scheme="http://example.com/tags/React-Router/"/>
    
  </entry>
  
  <entry>
    <title>React 完全指南 (React 18 &amp; 19)</title>
    <link href="http://example.com/posts/4179c0b9.html"/>
    <id>http://example.com/posts/4179c0b9.html</id>
    <published>2025-12-02T10:28:38.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<h1 id="React-初学者完全指南-React-18-19"><a href="#React-初学者完全指南-React-18-19" class="headerlink" title="React 初学者完全指南 (React 18 &amp; 19)"></a>React 初学者完全指南 (React 18 &amp; 19)</h1><blockquote><p>📚 本指南旨在帮助初学者快速掌握 React 的核心概念和最新特性</p></blockquote><hr><h2 id="目录"><a href="#目录" class="headerlink" title="目录"></a>目录</h2><ol><li><a href="#%E4%B8%80-react-%E7%AE%80%E4%BB%8B">React 简介</a></li><li><a href="#%E4%BA%8C-%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA">环境搭建</a></li><li><a href="#%E4%B8%89-%E6%A0%B8%E5%BF%83%E6%A6%82%E5%BF%B5">核心概念</a></li><li><a href="#%E5%9B%9B-react-hooks-%E8%AF%A6%E8%A7%A3">React Hooks 详解</a></li><li><a href="#%E4%BA%94-hoc-%E9%AB%98%E9%98%B6%E7%BB%84%E4%BB%B6">HOC 高阶组件</a></li><li><a href="#5-react-18-%E6%96%B0%E7%89%B9%E6%80%A7">React 18 新特性</a></li><li><a href="#6-react-19-%E6%96%B0%E7%89%B9%E6%80%A7">React 19 新特性</a></li><li><a href="#7-%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5">最佳实践</a></li><li><a href="#8-%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E4%B8%8E%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88">常见问题与解决方案</a></li></ol><br><h2 id="一、React-简介"><a href="#一、React-简介" class="headerlink" title="一、React 简介"></a><strong>一、React 简介</strong></h2><h3 id="1-1-什么是-React？"><a href="#1-1-什么是-React？" class="headerlink" title="1.1 什么是 React？"></a><strong><font color='red'>1.1 什么是 React？</font></strong></h3><p>React 是由 Facebook（现 Meta）开发的一个用于构建用户界面的 JavaScript 库。它的核心思想是：</p><ul><li><strong>组件化</strong>：将 UI 拆分成独立、可复用的组件</li><li><strong>声明式</strong>：描述 UI 应该是什么样子，而不是如何改变它</li><li><strong>单向数据流</strong>：数据从父组件流向子组件，使应用更可预测</li></ul><h3 id="1-2-为什么选择-React？"><a href="#1-2-为什么选择-React？" class="headerlink" title="1.2 为什么选择 React？"></a><strong><font color='red'>1.2 为什么选择 React？</font></strong></h3><table><thead><tr><th>优势</th><th>说明</th></tr></thead><tbody><tr><td>🚀 高性能</td><td>虚拟 DOM 最小化真实 DOM 操作</td></tr><tr><td>🧩 组件复用</td><td>一次编写，多处使用</td></tr><tr><td>🌐 生态丰富</td><td>大量第三方库和工具支持</td></tr><tr><td>📱 跨平台</td><td>React Native 可开发移动应用</td></tr><tr><td>👥 社区活跃</td><td>丰富的学习资源和解决方案</td></tr></tbody></table><hr><br><h2 id="二、-环境搭建"><a href="#二、-环境搭建" class="headerlink" title="二、 环境搭建"></a><strong>二、 环境搭建</strong></h2><h3 id="2-1-使用-Vite-创建项目（推荐）"><a href="#2-1-使用-Vite-创建项目（推荐）" class="headerlink" title="2.1 使用 Vite 创建项目（推荐）"></a><strong><font color='red'>2.1 使用 Vite 创建项目（推荐）</font></strong></h3><figure class="highlight bash"><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></pre></td><td class="code"><pre><span class="line"><span class="comment"># 使用 npm</span></span><br><span class="line">npm create vite@latest vite-react-app -- --template react</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 pnpm</span></span><br><span class="line">pnpm create vite vite-react-app --template react</span><br><span class="line"></span><br><span class="line"><span class="comment"># 使用 yarn</span></span><br><span class="line">yarn create vite vite-react-app --template react</span><br><span class="line"></span><br><span class="line"><span class="comment"># 进入项目目录并启动</span></span><br><span class="line"><span class="built_in">cd</span> vite-react-app</span><br><span class="line">npm/pnpm/yarn install</span><br><span class="line">npm/pnpm/yarn run dev</span><br></pre></td></tr></table></figure><h3 id="2-2-使用-Create-React-App"><a href="#2-2-使用-Create-React-App" class="headerlink" title="2.2 使用 Create React App"></a><strong><font color='red'>2.2 使用 Create React App</font></strong></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">npx create-react-app webpack-react-app</span><br><span class="line"><span class="built_in">cd</span> webpack-react-app</span><br><span class="line">npm start</span><br></pre></td></tr></table></figure><h3 id="2-3-项目结构"><a href="#2-3-项目结构" class="headerlink" title="2.3 项目结构"></a><strong><font color='red'>2.3 项目结构</font></strong></h3><p><strong>vite</strong></p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">vite-react-app/</span><br><span class="line">├── 📁 public/                 # 公共资源目录</span><br><span class="line">│   └── vite.svg               # Vite 项目默认 SVG 图标</span><br><span class="line">├── 📁 src/                    # 源代码目录</span><br><span class="line">│   ├── 📁 assets/             # 静态资源目录</span><br><span class="line">│   ├── App.jsx                # 根组件</span><br><span class="line">│   ├── App.css                # App 组件样式文件</span><br><span class="line">│   ├── main.jsx               # 应用入口文件</span><br><span class="line">│   └── index.css              # 全局样式文件</span><br><span class="line">├── .gitignore                 # Git 忽略配置文件</span><br><span class="line">├── README.md                  # 项目说明文档</span><br><span class="line">├── eslint.config.js           # ESLint 配置文件</span><br><span class="line">├── index.html                 # HTML 入口文件</span><br><span class="line">├── package.json               # 项目配置文件</span><br><span class="line">├── pnpm-lock.yaml             # pnpm 依赖锁定文件</span><br><span class="line">└── vite.config.js             # Vite 配置文件</span><br></pre></td></tr></table></figure><p><strong>webpack</strong></p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">webpack-react-app/</span><br><span class="line">├── 📁 public/                 # 公共资源目录</span><br><span class="line">│   ├── favicon.ico            # 网站图标</span><br><span class="line">│   ├── index.html             # HTML 入口文件</span><br><span class="line">│   ├── manifest.json          # Web App 清单文件</span><br><span class="line">│   └── robots.txt             # 搜索引擎爬虫规则</span><br><span class="line">├── 📁 src/                    # 源代码目录</span><br><span class="line">│   ├── App.css                # App 组件样式文件</span><br><span class="line">│   ├── App.js                 # 根组件</span><br><span class="line">│   ├── App.test.js            # App 组件测试文件</span><br><span class="line">│   ├── index.css              # 全局样式文件</span><br><span class="line">│   ├── index.js               # 应用入口文件</span><br><span class="line">│   ├── reportWebVitals.js     # 性能检测文件</span><br><span class="line">│   └── setupTests.js          # 测试设置文件</span><br><span class="line">├── .gitignore                 # Git 忽略配置文件</span><br><span class="line">├── README.md                  # 项目说明文档</span><br><span class="line">├── package-lock.json          # npm 依赖锁定文件</span><br><span class="line">└── package.json               # 项目配置文件</span><br></pre></td></tr></table></figure><h3 id="2-4-跨域请求代理配置"><a href="#2-4-跨域请求代理配置" class="headerlink" title="2.4 跨域请求代理配置"></a><strong><font color='red'>2.4 跨域请求代理配置</font></strong></h3><p>在前后端分离跨域开发时，通常需要在本地开发服务器中配置代理解决跨域问题。</p><h4 id="1）Vite-配置跨域代理"><a href="#1）Vite-配置跨域代理" class="headerlink" title="1）Vite 配置跨域代理"></a><strong><font color='#10c300'>1）Vite 配置跨域代理</font></strong></h4><p>修改根目录下的 <code>vite.config.js</code> 文件，在 <code>server</code> 选项中添加用户指定的 <code>proxy</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><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&quot;vite&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> react <span class="keyword">from</span> <span class="string">&quot;@vitejs/plugin-react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">react</span>()],</span><br><span class="line">  <span class="attr">server</span>: &#123;</span><br><span class="line">    <span class="attr">proxy</span>: &#123;</span><br><span class="line">      <span class="string">&quot;/api&quot;</span>: &#123;</span><br><span class="line">        <span class="attr">target</span>: <span class="string">&quot;https://jsonplaceholder.typicode.com&quot;</span>, <span class="comment">// 实际后端地址</span></span><br><span class="line">        <span class="attr">changeOrigin</span>: <span class="literal">true</span>,</span><br><span class="line">        <span class="attr">rewrite</span>: <span class="function">(<span class="params">path</span>) =&gt;</span> path.<span class="title function_">replace</span>(<span class="regexp">/^\/api/</span>, <span class="string">&quot;&quot;</span>), <span class="comment">// 如果后端接口本身不带 /api 前缀</span></span><br><span class="line">      &#125;,</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><h4 id="2）Create-React-App-配置跨域代理"><a href="#2）Create-React-App-配置跨域代理" class="headerlink" title="2）Create React App 配置跨域代理"></a><strong><font color='#10c300'>2）Create React App 配置跨域代理</font></strong></h4><p>CRA 官方推荐通过在 src 目录下创建 <code>setupProxy.js</code> 来配置代理底层中间件。</p><ol><li>安装代理插件：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install http-proxy-middleware --save</span><br></pre></td></tr></table></figure><ol start="2"><li>在 <code>src/</code> 目录中新建 <code>setupProxy.js</code>（注意：由于它是 Node 环境直接读取配置，只能使用 CommonJS 规范），写入以下配置：</li></ol><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">const</span> &#123; createProxyMiddleware &#125; = <span class="built_in">require</span>(<span class="string">&quot;http-proxy-middleware&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="keyword">function</span> (<span class="params">app</span>) &#123;</span><br><span class="line">  app.<span class="title function_">use</span>(</span><br><span class="line">    <span class="string">&quot;/api&quot;</span>,</span><br><span class="line">    <span class="title function_">createProxyMiddleware</span>(&#123;</span><br><span class="line">      <span class="attr">target</span>: <span class="string">&quot;https://jsonplaceholder.typicode.com&quot;</span>, <span class="comment">// 实际后端地址</span></span><br><span class="line">      <span class="attr">changeOrigin</span>: <span class="literal">true</span>,</span><br><span class="line">      <span class="attr">pathRewrite</span>: &#123;</span><br><span class="line">        <span class="string">&quot;^/api&quot;</span>: <span class="string">&quot;&quot;</span>, <span class="comment">// 路径重写，去掉 /api 前缀</span></span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">  );</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><hr><br><h2 id="三、-核心概念"><a href="#三、-核心概念" class="headerlink" title="三、 核心概念"></a><strong>三、 核心概念</strong></h2><h3 id="3-1-JSX-语法"><a href="#3-1-JSX-语法" class="headerlink" title="3.1 JSX 语法"></a><strong><font color='red'>3.1 JSX 语法</font></strong></h3><p>JSX 是 JavaScript 的语法扩展，让你可以在 JS 中编写类似 HTML 的代码：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// JSX 基础语法</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Welcome</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> name = <span class="string">&quot;React&quot;</span>;</span><br><span class="line">  <span class="keyword">const</span> isLoggedIn = <span class="literal">true</span>;</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> <span class="attr">className</span>=<span class="string">&quot;welcome&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 使用花括号嵌入表达式 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>Hello, &#123;name&#125;!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* 条件渲染 */&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;isLoggedIn ? <span class="tag">&lt;<span class="name">p</span>&gt;</span>欢迎回来<span class="tag">&lt;/<span class="name">p</span>&gt;</span> : <span class="tag">&lt;<span class="name">p</span>&gt;</span>请登录<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* 注意：class 要写成 className */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">className</span>=<span class="string">&quot;btn&quot;</span>&gt;</span>点击我<span class="tag">&lt;/<span class="name">button</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><h4 id="1）JSX-规则速记"><a href="#1）JSX-规则速记" class="headerlink" title="1）JSX 规则速记"></a><strong><font color='#10c300'>1）JSX 规则速记</font></strong></h4><table><thead><tr><th>HTML</th><th>JSX</th></tr></thead><tbody><tr><td><code>class</code></td><td><code>className</code></td></tr><tr><td><code>for</code></td><td><code>htmlFor</code></td></tr><tr><td><code>onclick</code></td><td><code>onClick</code></td></tr><tr><td><code>tabindex</code></td><td><code>tabIndex</code></td></tr></tbody></table><br><h3 id="3-2-组件"><a href="#3-2-组件" class="headerlink" title="3.2 组件"></a><strong><font color='red'>3.2 组件</font></strong></h3><h4 id="1）函数组件（推荐）"><a href="#1）函数组件（推荐）" class="headerlink" title="1）函数组件（推荐）"></a><strong><font color='#10c300'>1）函数组件（推荐）</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 函数组件是 React 推荐的编写方式</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Greeting</span>(<span class="params">&#123; name &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>你好, &#123;name&#125;!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></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="keyword">const</span> <span class="title function_">Greeting</span> = (<span class="params">&#123; name &#125;</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>你好, &#123;name&#125;!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="2）类组件（了解即可）"><a href="#2）类组件（了解即可）" class="headerlink" title="2）类组件（了解即可）"></a><strong><font color='#10c300'>2）类组件（了解即可）</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Component</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Greeting</span> <span class="keyword">extends</span> <span class="title class_ inherited__">Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>你好, &#123;this.props.name&#125;!<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-3-Props（属性）"><a href="#3-3-Props（属性）" class="headerlink" title="3.3 Props（属性）"></a><strong><font color='red'>3.3 Props（属性）</font></strong></h3><p>Props 是父组件传递给子组件的数据：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 父组件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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">UserCard</span> <span class="attr">name</span>=<span class="string">&quot;张三&quot;</span> <span class="attr">age</span>=<span class="string">&#123;25&#125;</span> <span class="attr">isAdmin</span>=<span class="string">&#123;true&#125;</span> <span class="attr">hobbies</span>=<span class="string">&#123;[</span>&quot;<span class="attr">读书</span>&quot;, &quot;<span class="attr">游戏</span>&quot;]&#125; /&gt;</span></span></span><br><span class="line">  );</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="keyword">function</span> <span class="title function_">UserCard</span>(<span class="params">&#123; name, age, isAdmin, hobbies &#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> <span class="attr">className</span>=<span class="string">&quot;card&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>&#123;name&#125;<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>年龄: &#123;age&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>身份: &#123;isAdmin ? &quot;管理员&quot; : &quot;普通用户&quot;&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;hobbies.map((hobby, index) =&gt; (</span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;index&#125;</span>&gt;</span>&#123;hobby&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        ))&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">ul</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><h4 id="1）Props-默认值"><a href="#1）Props-默认值" class="headerlink" title="1）Props 默认值"></a><strong><font color='#10c300'>1）Props 默认值</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params">&#123; text = <span class="string">&quot;点击&quot;</span>, type = <span class="string">&quot;primary&quot;</span> &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">className</span>=<span class="string">&#123;</span>`<span class="attr">btn-</span>$&#123;<span class="attr">type</span>&#125;`&#125;&gt;</span>&#123;text&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-4-State（状态）"><a href="#3-4-State（状态）" class="headerlink" title="3.4 State（状态）"></a><strong><font color='red'>3.4 State（状态）</font></strong></h3><p>State 是组件内部的可变数据：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 声明状态：[当前值, 更新函数] = useState(初始值)</span></span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</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">p</span>&gt;</span>当前计数: &#123;count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;+1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count - 1)&#125;&gt;-1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(0)&#125;&gt;重置<span class="tag">&lt;/<span class="name">button</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><h4 id="1）State-更新注意事项"><a href="#1）State-更新注意事项" class="headerlink" title="1）State 更新注意事项"></a><strong><font color='#10c300'>1）State 更新注意事项</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Example</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(&#123; <span class="attr">name</span>: <span class="string">&quot;张三&quot;</span>, <span class="attr">age</span>: <span class="number">25</span> &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ❌ 错误：直接修改状态</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">wrongUpdate</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    user.<span class="property">age</span> = <span class="number">26</span>; <span class="comment">// 不会触发重新渲染</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="keyword">const</span> <span class="title function_">correctUpdate</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setUser</span>(&#123; ...user, <span class="attr">age</span>: <span class="number">26</span> &#125;);</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="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">increment</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setCount</span>(<span class="function">(<span class="params">prevCount</span>) =&gt;</span> prevCount + <span class="number">1</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="3-5-事件处理"><a href="#3-5-事件处理" class="headerlink" title="3.5 事件处理"></a><strong><font color='red'>3.5 事件处理</font></strong></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><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">function</span> <span class="title function_">EventExample</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 点击事件</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params">e</span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;按钮被点击&quot;</span>, e);</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="keyword">const</span> <span class="title function_">handleDelete</span> = (<span class="params">id</span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;删除项目:&quot;</span>, id);</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="keyword">const</span> [value, setValue] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</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_">setValue</span>(e.<span class="property">target</span>.<span class="property">value</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">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span>点击我<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> handleDelete(123)&#125;&gt;删除<span class="tag">&lt;/<span class="name">button</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;value&#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">    <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><br><h3 id="3-6-条件渲染"><a href="#3-6-条件渲染" class="headerlink" title="3.6 条件渲染"></a><strong><font color='red'>3.6 条件渲染</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ConditionalExample</span>(<span class="params">&#123; isLoggedIn, messages &#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">      &#123;/* 方式1: 三元运算符 */&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;isLoggedIn ? <span class="tag">&lt;<span class="name">LogoutButton</span> /&gt;</span> : <span class="tag">&lt;<span class="name">LoginButton</span> /&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* 方式2: &amp;&amp; 短路运算 */&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;messages.length &gt; 0 &amp;&amp; <span class="tag">&lt;<span class="name">Badge</span> <span class="attr">count</span>=<span class="string">&#123;messages.length&#125;</span> /&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      &#123;/* 方式3: if-else 提前返回 */&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;(() =&gt; &#123;</span></span><br><span class="line"><span class="language-xml">        if (messages.length === 0) return <span class="tag">&lt;<span class="name">p</span>&gt;</span>暂无消息<span class="tag">&lt;/<span class="name">p</span>&gt;</span>;</span></span><br><span class="line"><span class="language-xml">        if (messages.length &lt; 5) return <span class="tag">&lt;<span class="name">p</span>&gt;</span>少量消息<span class="tag">&lt;/<span class="name">p</span>&gt;</span>;</span></span><br><span class="line"><span class="language-xml">        return <span class="tag">&lt;<span class="name">p</span>&gt;</span>大量消息<span class="tag">&lt;/<span class="name">p</span>&gt;</span>;</span></span><br><span class="line"><span class="language-xml">      &#125;)()&#125;</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><br><h3 id="3-7-列表渲染"><a href="#3-7-列表渲染" class="headerlink" title="3.7 列表渲染"></a><strong><font color='red'>3.7 列表渲染</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">TodoList</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> todos = [</span><br><span class="line">    &#123; <span class="attr">id</span>: <span class="number">1</span>, <span class="attr">text</span>: <span class="string">&quot;学习 React&quot;</span>, <span class="attr">done</span>: <span class="literal">false</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">id</span>: <span class="number">2</span>, <span class="attr">text</span>: <span class="string">&quot;写代码&quot;</span>, <span class="attr">done</span>: <span class="literal">true</span> &#125;,</span><br><span class="line">    &#123; <span class="attr">id</span>: <span class="number">3</span>, <span class="attr">text</span>: <span class="string">&quot;看文档&quot;</span>, <span class="attr">done</span>: <span class="literal">false</span> &#125;,</span><br><span class="line">  ];</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">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;todos.map((todo) =&gt; (</span></span><br><span class="line"><span class="language-xml">        // key 必须是唯一且稳定的标识符</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;todo.id&#125;</span> <span class="attr">className</span>=<span class="string">&#123;todo.done</span> ? &quot;<span class="attr">completed</span>&quot; <span class="attr">:</span> &quot;&quot;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;todo.text&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><blockquote><p>⚠️ <strong>key 的重要性</strong>：key 帮助 React 识别哪些元素改变了，避免使用数组索引作为 key（除非列表是静态的）</p></blockquote><br><h3 id="3-8-CSS-Modules-样式管理"><a href="#3-8-CSS-Modules-样式管理" class="headerlink" title="3.8 CSS Modules - 样式管理"></a><strong><font color='red'>3.8 CSS Modules - 样式管理</font></strong></h3><p>CSS Modules 是一种 CSS 文件的模块化和作用域化方案，它可以让 CSS 类名自动生成唯一的名称，避免全局样式冲突。</p><h4 id="1）什么是-CSS-Modules？"><a href="#1）什么是-CSS-Modules？" class="headerlink" title="1）什么是 CSS Modules？"></a><strong><font color='#10c300'>1）什么是 CSS Modules？</font></strong></h4><p>CSS Modules 的核心思想：</p><ul><li><strong>局部作用域</strong>：默认情况下，CSS 类名只在当前模块内生效</li><li><strong>自动命名</strong>：构建工具会将类名转换为唯一的哈希值（如 <code>.button</code> → <code>.Button_button__2Rx3L</code>）</li><li><strong>显式依赖</strong>：通过 <code>import</code> 引入样式，明确组件与样式的关系</li><li><strong>可组合性</strong>：支持 <code>composes</code> 关键字实现样式继承</li></ul><h4 id="2）基本使用"><a href="#2）基本使用" class="headerlink" title="2）基本使用"></a><strong><font color='#10c300'>2）基本使用</font></strong></h4><p><strong>Step 1️⃣：创建 CSS Module 文件</strong></p><p>文件命名规范：<code>*.module.css</code>（Vite 和 CRA 都默认支持）</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><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">/* Button.module.css */</span></span><br><span class="line"><span class="selector-class">.button</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#007bff</span>;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">  <span class="attribute">border</span>: none;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.button</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#0056b3</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.primary</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#28a745</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.danger</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#dc3545</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Step 2️⃣：在组件中导入并使用</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params">&#123; type = <span class="string">&quot;default&quot;</span>, children &#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">button</span> <span class="attr">className</span>=<span class="string">&#123;styles.button&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Button</span>;</span><br></pre></td></tr></table></figure><p><strong>Step 3️⃣：查看编译后的类名</strong></p><p>在浏览器中查看，类名会被转换为类似这样的唯一标识：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;Button_button__2Rx3L&quot;</span>&gt;</span>点击我<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><h4 id="3）动态类名组合"><a href="#3）动态类名组合" class="headerlink" title="3）动态类名组合"></a><strong><font color='#10c300'>3）动态类名组合</font></strong></h4><p><strong>1️⃣使用模板字符串</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Card.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Card</span>(<span class="params">&#123; isActive, children &#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> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.card</span>&#125; $&#123;<span class="attr">isActive</span> ? <span class="attr">styles.active</span> <span class="attr">:</span> &quot;&quot;&#125;`&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</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><p><strong>2️⃣使用 classnames 库（推荐）</strong></p><p>先安装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install classnames</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Card.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> classNames <span class="keyword">from</span> <span class="string">&quot;classnames&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Card</span>(<span class="params">&#123; isActive, isDisabled, children &#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></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">className</span>=<span class="string">&#123;classNames(styles.card,</span> &#123;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        [<span class="attr">styles.active</span>]<span class="attr">:</span> <span class="attr">isActive</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        [<span class="attr">styles.disabled</span>]<span class="attr">:</span> <span class="attr">isDisabled</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      &#125;)&#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">      &#123;children&#125;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Card</span>;</span><br></pre></td></tr></table></figure><p><strong>3️⃣使用 clsx 库（轻量替代）</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install clsx</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> clsx <span class="keyword">from</span> <span class="string">&quot;clsx&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params">&#123; variant, size, disabled, children &#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">button</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">className</span>=<span class="string">&#123;clsx(</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">styles.button</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">styles</span>[<span class="attr">variant</span>], // <span class="attr">动态访问样式</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">styles</span>[<span class="attr">size</span>],</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">disabled</span> &amp;&amp; <span class="attr">styles.disabled</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      )&#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">      &#123;children&#125;</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><h4 id="4）composes-样式组合"><a href="#4）composes-样式组合" class="headerlink" title="4）composes 样式组合"></a><strong><font color='#10c300'>4）composes 样式组合</font></strong></h4><p>CSS Modules 支持 <code>composes</code> 关键字来实现样式继承和组合：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Button.module.css */</span></span><br><span class="line"><span class="selector-class">.button</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">border</span>: none;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.primary</span> &#123;</span><br><span class="line">  composes: button;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#007bff</span>;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.danger</span> &#123;</span><br><span class="line">  composes: button;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#dc3545</span>;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params">&#123; type &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">className</span>=<span class="string">&#123;styles[type]&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></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">&lt;<span class="title class_">Button</span> type=<span class="string">&quot;primary&quot;</span> /&gt;  <span class="comment">// 自动继承 button 的样式</span></span><br></pre></td></tr></table></figure><h4 id="5）从其他文件组合样式"><a href="#5）从其他文件组合样式" class="headerlink" title="5）从其他文件组合样式"></a><strong><font color='#10c300'>5）从其他文件组合样式</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* base.module.css */</span></span><br><span class="line"><span class="selector-class">.baseButton</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">border</span>: none;</span><br><span class="line">  <span class="attribute">cursor</span>: pointer;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* Button.module.css */</span></span><br><span class="line"><span class="selector-class">.submitButton</span> &#123;</span><br><span class="line">  composes: baseButton from <span class="string">&quot;./base.module.css&quot;</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: green;</span><br><span class="line">  <span class="attribute">color</span>: white;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="6）全局样式与局部样式混合"><a href="#6）全局样式与局部样式混合" class="headerlink" title="6）全局样式与局部样式混合"></a><strong><font color='#10c300'>6）全局样式与局部样式混合</font></strong></h4><p><strong>1️⃣使用 <code>:global</code> 声明全局样式</strong></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><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="comment">/* App.module.css */</span></span><br><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">20px</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="built_in">global</span>(.highlight) &#123;</span><br><span class="line">  <span class="attribute">background-color</span>: yellow;</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="selector-class">.wrapper</span> :<span class="built_in">global</span>(.external-class) &#123;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">10px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2️⃣混合使用全局类名和模块类名</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./App.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.container</span>&#125; <span class="attr">global-theme</span>`&#125;&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;highlight&quot;</span>&gt;</span>全局高亮<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">div</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="7）CSS-变量与主题切换"><a href="#7）CSS-变量与主题切换" class="headerlink" title="7）CSS 变量与主题切换"></a><strong><font color='#10c300'>7）CSS 变量与主题切换</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* theme.module.css */</span></span><br><span class="line"><span class="selector-class">.light</span> &#123;</span><br><span class="line">  <span class="attr">--bg-color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">  <span class="attr">--text-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="selector-class">.dark</span> &#123;</span><br><span class="line">  <span class="attr">--bg-color</span>: <span class="number">#1a1a1a</span>;</span><br><span class="line">  <span class="attr">--text-color</span>: <span class="number">#ffffff</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.container</span> &#123;</span><br><span class="line">  <span class="attribute">background-color</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">  <span class="attribute">padding</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./theme.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [theme, setTheme] = <span class="title function_">useState</span>(<span class="string">&quot;light&quot;</span>);</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> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.container</span>&#125; $&#123;<span class="attr">styles</span>[<span class="attr">theme</span>]&#125;`&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>当前主题：&#123;theme&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setTheme(theme === &quot;light&quot; ? &quot;dark&quot; : &quot;light&quot;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        切换主题</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 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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h4 id="8）配合-Sass-Less-使用"><a href="#8）配合-Sass-Less-使用" class="headerlink" title="8）配合 Sass&#x2F;Less 使用"></a><strong><font color='#10c300'>8）配合 Sass&#x2F;Less 使用</font></strong></h4><p>CSS Modules 完全支持 CSS 预处理器。</p><p><strong>1️⃣安装 Sass</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install sass</span><br></pre></td></tr></table></figure><p><strong>2️⃣创建 SCSS Module 文件</strong></p><figure class="highlight scss"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Button.module.scss */</span></span><br><span class="line"><span class="variable">$primary-color</span>: <span class="number">#007bff</span>;</span><br><span class="line"><span class="variable">$hover-darken</span>: <span class="number">10%</span>;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.button</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">10px</span> <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">border</span>: none;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">4px</span>;</span><br><span class="line"></span><br><span class="line">  &amp;<span class="selector-class">.primary</span> &#123;</span><br><span class="line">    <span class="attribute">background-color</span>: <span class="variable">$primary-color</span>;</span><br><span class="line">    </span><br><span class="line">    &amp;<span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">      <span class="attribute">background-color</span>: <span class="built_in">darken</span>(<span class="variable">$primary-color</span>, <span class="variable">$hover-darken</span>);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  &amp;<span class="selector-class">.large</span> &#123;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">15px</span> <span class="number">30px</span>;</span><br><span class="line">    <span class="attribute">font-size</span>: <span class="number">18px</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3️⃣在组件中使用</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.scss&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params">&#123; variant = <span class="string">&quot;primary&quot;</span>, size = <span class="string">&quot;medium&quot;</span>, children &#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">button</span> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.button</span>&#125; $&#123;<span class="attr">styles</span>[<span class="attr">variant</span>]&#125; $&#123;<span class="attr">styles</span>[<span class="attr">size</span>]&#125;`&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</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><h4 id="9）TypeScript-支持"><a href="#9）TypeScript-支持" class="headerlink" title="9）TypeScript 支持"></a><strong><font color='#10c300'>9）TypeScript 支持</font></strong></h4><p><strong>1️⃣自动生成类型定义</strong></p><p>安装类型生成工具：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -D typescript-plugin-css-modules</span><br></pre></td></tr></table></figure><p>配置 <code>tsconfig.json</code>：</p><figure class="highlight json"><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="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;compilerOptions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;plugins&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="punctuation">&#123;</span> <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;typescript-plugin-css-modules&quot;</span> <span class="punctuation">&#125;</span><span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p><strong>2️⃣手动创建类型定义</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// Button.module.css.d.ts</span></span><br><span class="line"><span class="keyword">declare</span> <span class="keyword">const</span> <span class="attr">styles</span>: &#123;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">button</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">primary</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">danger</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">large</span>: <span class="built_in">string</span>;</span><br><span class="line">  <span class="keyword">readonly</span> <span class="attr">small</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> styles;</span><br></pre></td></tr></table></figure><p><strong>3️⃣在 TypeScript 组件中使用</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">ButtonProps</span> &#123;</span><br><span class="line">  <span class="attr">variant</span>?: <span class="string">&quot;primary&quot;</span> | <span class="string">&quot;danger&quot;</span> | <span class="string">&quot;success&quot;</span>;</span><br><span class="line">  <span class="attr">size</span>?: <span class="string">&quot;small&quot;</span> | <span class="string">&quot;medium&quot;</span> | <span class="string">&quot;large&quot;</span>;</span><br><span class="line">  <span class="attr">children</span>: <span class="title class_">React</span>.<span class="property">ReactNode</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Button</span>: <span class="title class_">React</span>.<span class="property">FC</span>&lt;<span class="title class_">ButtonProps</span>&gt; = <span class="function">(<span class="params">&#123; </span></span></span><br><span class="line"><span class="params"><span class="function">  variant = <span class="string">&quot;primary&quot;</span>, </span></span></span><br><span class="line"><span class="params"><span class="function">  size = <span class="string">&quot;medium&quot;</span>, </span></span></span><br><span class="line"><span class="params"><span class="function">  children </span></span></span><br><span class="line"><span class="params"><span class="function">&#125;</span>) =&gt;</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">button</span> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.button</span>&#125; $&#123;<span class="attr">styles</span>[<span class="attr">variant</span>]&#125; $&#123;<span class="attr">styles</span>[<span class="attr">size</span>]&#125;`&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Button</span>;</span><br></pre></td></tr></table></figure><h4 id="10）实战案例：卡片组件"><a href="#10）实战案例：卡片组件" class="headerlink" title="10）实战案例：卡片组件"></a><strong><font color='#10c300'>10）实战案例：卡片组件</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Card.module.css */</span></span><br><span class="line"><span class="selector-class">.card</span> &#123;</span><br><span class="line">  <span class="attribute">border</span>: <span class="number">1px</span> solid <span class="number">#e0e0e0</span>;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">8px</span>;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: white;</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">2px</span> <span class="number">4px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.1</span>);</span><br><span class="line">  <span class="attribute">transition</span>: all <span class="number">0.3s</span> ease;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.card</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">box-shadow</span>: <span class="number">0</span> <span class="number">4px</span> <span class="number">12px</span> <span class="built_in">rgba</span>(<span class="number">0</span>, <span class="number">0</span>, <span class="number">0</span>, <span class="number">0.15</span>);</span><br><span class="line">  <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">2px</span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.cardHeader</span> &#123;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">justify-content</span>: space-between;</span><br><span class="line">  <span class="attribute">align-items</span>: center;</span><br><span class="line">  <span class="attribute">margin-bottom</span>: <span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">padding-bottom</span>: <span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">border-bottom</span>: <span class="number">1px</span> solid <span class="number">#f0f0f0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.cardTitle</span> &#123;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">18px</span>;</span><br><span class="line">  <span class="attribute">font-weight</span>: bold;</span><br><span class="line">  <span class="attribute">margin</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.cardContent</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="number">#666</span>;</span><br><span class="line">  <span class="attribute">line-height</span>: <span class="number">1.6</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.cardFooter</span> &#123;</span><br><span class="line">  <span class="attribute">margin-top</span>: <span class="number">15px</span>;</span><br><span class="line">  <span class="attribute">display</span>: flex;</span><br><span class="line">  <span class="attribute">gap</span>: <span class="number">10px</span>;</span><br><span class="line">  <span class="attribute">justify-content</span>: flex-end;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.highlighted</span> &#123;</span><br><span class="line">  <span class="attribute">border-color</span>: <span class="number">#007bff</span>;</span><br><span class="line">  <span class="attribute">background-color</span>: <span class="number">#f8f9ff</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Card.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> classNames <span class="keyword">from</span> <span class="string">&quot;classnames&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Card</span>(<span class="params">&#123; title, children, actions, highlighted = <span class="literal">false</span> &#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> <span class="attr">className</span>=<span class="string">&#123;classNames(styles.card,</span> &#123;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      [<span class="attr">styles.highlighted</span>]<span class="attr">:</span> <span class="attr">highlighted</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &#125;)&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;title &amp;&amp; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;styles.cardHeader&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">h3</span> <span class="attr">className</span>=<span class="string">&#123;styles.cardTitle&#125;</span>&gt;</span>&#123;title&#125;<span class="tag">&lt;/<span class="name">h3</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 class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">      </span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;styles.cardContent&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;children&#125;</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></span><br><span class="line"><span class="language-xml">      &#123;actions &amp;&amp; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;styles.cardFooter&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;actions&#125;</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">      )&#125;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Card</span>;</span><br></pre></td></tr></table></figure><h4 id="11）配置自定义哈希命名"><a href="#11）配置自定义哈希命名" class="headerlink" title="11）配置自定义哈希命名"></a><strong><font color='#10c300'>11）配置自定义哈希命名</font></strong></h4><p><strong>Vite 配置</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// vite.config.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; defineConfig &#125; <span class="keyword">from</span> <span class="string">&quot;vite&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> react <span class="keyword">from</span> <span class="string">&quot;@vitejs/plugin-react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">plugins</span>: [<span class="title function_">react</span>()],</span><br><span class="line">  <span class="attr">css</span>: &#123;</span><br><span class="line">    <span class="attr">modules</span>: &#123;</span><br><span class="line">      <span class="comment">// 自定义生成的类名格式</span></span><br><span class="line">      <span class="attr">generateScopedName</span>: <span class="string">&quot;[name]__[local]___[hash:base64:5]&quot;</span>,</span><br><span class="line">      <span class="comment">// [name]: 文件名</span></span><br><span class="line">      <span class="comment">// [local]: 原始类名</span></span><br><span class="line">      <span class="comment">// [hash:base64:5]: 5位哈希值</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><p><strong>Create React App 配置</strong></p><p>CRA 需要 eject 或使用 <code>craco</code> &#x2F; <code>react-app-rewired</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// craco.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">style</span>: &#123;</span><br><span class="line">    <span class="attr">modules</span>: &#123;</span><br><span class="line">      <span class="attr">localIdentName</span>: <span class="string">&quot;[name]__[local]--[hash:base64:5]&quot;</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><h4 id="12）最佳实践"><a href="#12）最佳实践" class="headerlink" title="12）最佳实践"></a><strong><font color='#10c300'>12）最佳实践</font></strong></h4><p><strong>1️⃣文件命名约定</strong></p><figure class="highlight plaintext"><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">components/</span><br><span class="line">├── Button/</span><br><span class="line">│   ├── Button.jsx</span><br><span class="line">│   ├── Button.module.css</span><br><span class="line">│   └── index.js</span><br></pre></td></tr></table></figure><p><strong>2️⃣避免过度嵌套</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ❌ 不推荐：选择器嵌套过深 */</span></span><br><span class="line"><span class="selector-class">.container</span> <span class="selector-class">.wrapper</span> <span class="selector-class">.content</span> <span class="selector-class">.item</span> <span class="selector-class">.title</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: red;</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="selector-class">.container</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.item</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.itemTitle</span> &#123; &#125;</span><br></pre></td></tr></table></figure><p><strong>3️⃣使用语义化命名</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ❌ 不清晰 */</span></span><br><span class="line"><span class="selector-class">.btn1</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.box2</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.red</span> &#123; &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ✅ 语义化 */</span></span><br><span class="line"><span class="selector-class">.submitButton</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.userCard</span> &#123; &#125;</span><br><span class="line"><span class="selector-class">.errorMessage</span> &#123; &#125;</span><br></pre></td></tr></table></figure><p><strong>4️⃣组合全局样式和模块样式</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><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="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./App.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="comment">// 全局重置样式 + 模块样式</span></span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;</span>`<span class="attr">global-reset</span> $&#123;<span class="attr">styles.container</span>&#125;`&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">header</span> <span class="attr">className</span>=<span class="string">&#123;styles.header&#125;</span>&gt;</span>标题<span class="tag">&lt;/<span class="name">header</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><p><strong>5️⃣共享常量（CSS 变量）</strong></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><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/* variables.module.css */</span></span><br><span class="line">:export &#123;</span><br><span class="line">  primaryColor: <span class="number">#007bff</span>;</span><br><span class="line">  successColor: <span class="number">#28a745</span>;</span><br><span class="line">  spacing: <span class="number">16px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><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> variables <span class="keyword">from</span> <span class="string">&quot;./variables.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</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> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">color:</span> <span class="attr">variables.primaryColor</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">padding:</span> <span class="attr">variables.spacing</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">    &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      内容</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><h4 id="13）常见问题与解决方案"><a href="#13）常见问题与解决方案" class="headerlink" title="13）常见问题与解决方案"></a><strong><font color='#10c300'>13）常见问题与解决方案</font></strong></h4><p><strong>Q1: 如何应用多个类名？</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><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">// 方法1：模板字符串</span></span><br><span class="line">&lt;div className=&#123;<span class="string">`<span class="subst">$&#123;styles.card&#125;</span> <span class="subst">$&#123;styles.active&#125;</span>`</span>&#125; /&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法2：数组 join</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;[styles.card,</span> <span class="attr">styles.active</span>]<span class="attr">.join</span>(&#x27; &#x27;)&#125; /&gt;</span></span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法3：classnames 库（推荐）</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;classNames(styles.card,</span> <span class="attr">styles.active</span>)&#125; /&gt;</span></span></span><br></pre></td></tr></table></figure><p><strong>Q2: 如何访问带连字符的类名？</strong></p><figure class="highlight css"><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">/* Button.module.css */</span></span><br><span class="line"><span class="selector-class">.submit-button</span> &#123; &#125;</span><br></pre></td></tr></table></figure><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 方法1：中括号语法</span></span><br><span class="line">&lt;button className=&#123;styles[<span class="string">&#x27;submit-button&#x27;</span>]&#125; /&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方法2：驼峰命名（推荐）</span></span><br><span class="line"><span class="comment">/* 改为 .submitButton */</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">className</span>=<span class="string">&#123;styles.submitButton&#125;</span> /&gt;</span></span></span><br></pre></td></tr></table></figure><p><strong>Q3: 如何在 CSS Modules 中使用伪类和伪元素？</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Button.module.css */</span></span><br><span class="line"><span class="selector-class">.button</span> &#123;</span><br><span class="line">  <span class="attribute">position</span>: relative;</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="selector-class">.button</span><span class="selector-pseudo">:hover</span> &#123;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">0.8</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.button</span><span class="selector-pseudo">:disabled</span> &#123;</span><br><span class="line">  <span class="attribute">cursor</span>: not-allowed;</span><br><span class="line">  <span class="attribute">opacity</span>: <span class="number">0.5</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="selector-class">.button</span><span class="selector-pseudo">::before</span> &#123;</span><br><span class="line">  <span class="attribute">content</span>: <span class="string">&quot;&quot;</span>;</span><br><span class="line">  <span class="attribute">position</span>: absolute;</span><br><span class="line">  <span class="attribute">top</span>: <span class="number">0</span>;</span><br><span class="line">  <span class="attribute">left</span>: <span class="number">0</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>Q4: 如何覆盖第三方组件库样式？</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* App.module.css */</span></span><br><span class="line"><span class="comment">/* 使用 :global 包裹第三方类名 */</span></span><br><span class="line"><span class="selector-class">.customModal</span> :<span class="built_in">global</span>(.ant-modal-content) &#123;</span><br><span class="line">  <span class="attribute">border-radius</span>: <span class="number">16px</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.wrapper</span> :global &#123;</span><br><span class="line">  <span class="selector-class">.mui-button</span> &#123;</span><br><span class="line">    <span class="attribute">text-transform</span>: none;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./App.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">Modal</span> &#125; <span class="keyword">from</span> <span class="string">&quot;antd&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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> <span class="attr">className</span>=<span class="string">&#123;styles.customModal&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Modal</span>&gt;</span>内容<span class="tag">&lt;/<span class="name">Modal</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><h4 id="14）CSS-Modules-vs-其他方案对比"><a href="#14）CSS-Modules-vs-其他方案对比" class="headerlink" title="14）CSS Modules vs 其他方案对比"></a><strong><font color='#10c300'>14）CSS Modules vs 其他方案对比</font></strong></h4><table><thead><tr><th align="left">方案</th><th align="left">优势</th><th align="left">劣势</th><th align="left">适用场景</th></tr></thead><tbody><tr><td align="left"><strong>CSS Modules</strong></td><td align="left">自动作用域、零运行时、学习成本低</td><td align="left">动态样式支持弱、需要构建工具</td><td align="left">中小型项目、组件库</td></tr><tr><td align="left"><strong>CSS-in-JS</strong> (styled-components)</td><td align="left">动态样式强大、主题支持好</td><td align="left">运行时开销、调试困难</td><td align="left">需要大量动态样式的应用</td></tr><tr><td align="left"><strong>Tailwind CSS</strong></td><td align="left">快速开发、设计系统一致性</td><td align="left">HTML 类名冗长、需要学习类名</td><td align="left">追求开发效率的项目</td></tr><tr><td align="left"><strong>普通 CSS</strong></td><td align="left">简单直接</td><td align="left">全局污染、命名冲突</td><td align="left">简单页面、原型开发</td></tr><tr><td align="left"><strong>Sass&#x2F;Less Modules</strong></td><td align="left">变量、嵌套、函数</td><td align="left">需要编译、学习成本</td><td align="left">复杂样式逻辑</td></tr></tbody></table><h4 id="15）高级技巧"><a href="#15）高级技巧" class="headerlink" title="15）高级技巧"></a><strong><font color='#10c300'>15）高级技巧</font></strong></h4><p><strong>1️⃣条件渲染复杂样式</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><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> styles <span class="keyword">from</span> <span class="string">&quot;./Status.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">StatusBadge</span>(<span class="params">&#123; status &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> statusClass = &#123;</span><br><span class="line">    <span class="attr">pending</span>: styles.<span class="property">pending</span>,</span><br><span class="line">    <span class="attr">success</span>: styles.<span class="property">success</span>,</span><br><span class="line">    <span class="attr">error</span>: styles.<span class="property">error</span>,</span><br><span class="line">    <span class="attr">warning</span>: styles.<span class="property">warning</span>,</span><br><span class="line">  &#125;[status];</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">span</span> <span class="attr">className</span>=<span class="string">&#123;</span>`$&#123;<span class="attr">styles.badge</span>&#125; $&#123;<span class="attr">statusClass</span>&#125;`&#125;&gt;</span>&#123;status&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>2️⃣动画与过渡</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Animation.module.css */</span></span><br><span class="line"><span class="keyword">@keyframes</span> fadeIn &#123;</span><br><span class="line">  <span class="selector-tag">from</span> &#123;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">0</span>;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(-<span class="number">10px</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="selector-tag">to</span> &#123;</span><br><span class="line">    <span class="attribute">opacity</span>: <span class="number">1</span>;</span><br><span class="line">    <span class="attribute">transform</span>: <span class="built_in">translateY</span>(<span class="number">0</span>);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.fadeInElement</span> &#123;</span><br><span class="line">  <span class="attribute">animation</span>: fadeIn <span class="number">0.3s</span> ease-in-out;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="selector-class">.transition</span> &#123;</span><br><span class="line">  <span class="attribute">transition</span>: all <span class="number">0.3s</span> <span class="built_in">cubic-bezier</span>(<span class="number">0.4</span>, <span class="number">0</span>, <span class="number">0.2</span>, <span class="number">1</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3️⃣响应式设计</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* Card.module.css */</span></span><br><span class="line"><span class="selector-class">.card</span> &#123;</span><br><span class="line">  <span class="attribute">padding</span>: <span class="number">20px</span>;</span><br><span class="line">  <span class="attribute">width</span>: <span class="number">100%</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">@media</span> (<span class="attribute">min-width</span>: <span class="number">768px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.card</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">50%</span>;</span><br><span class="line">    <span class="attribute">padding</span>: <span class="number">30px</span>;</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">@media</span> (<span class="attribute">min-width</span>: <span class="number">1024px</span>) &#123;</span><br><span class="line">  <span class="selector-class">.card</span> &#123;</span><br><span class="line">    <span class="attribute">width</span>: <span class="number">33.333%</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>4️⃣条件样式的性能优化</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./List.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ListItem</span>(<span class="params">&#123; item, isActive, isSelected &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// 缓存类名计算结果</span></span><br><span class="line">  <span class="keyword">const</span> className = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span></span><br><span class="line">      <span class="title function_">classNames</span>(styles.<span class="property">item</span>, &#123;</span><br><span class="line">        [styles.<span class="property">active</span>]: isActive,</span><br><span class="line">        [styles.<span class="property">selected</span>]: isSelected,</span><br><span class="line">      &#125;),</span><br><span class="line">    [isActive, isSelected]</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">li</span> <span class="attr">className</span>=<span class="string">&#123;className&#125;</span>&gt;</span>&#123;item.name&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="16）常见陷阱-⚠️"><a href="#16）常见陷阱-⚠️" class="headerlink" title="16）常见陷阱 ⚠️"></a><strong><font color='#10c300'>16）常见陷阱 ⚠️</font></strong></h4><p><strong>1️⃣忘记使用 .module.css 后缀</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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：没有 .module 后缀，会被当作普通 CSS</span></span><br><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确</span></span><br><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br></pre></td></tr></table></figure><p><strong>2️⃣直接修改 styles 对象</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><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：styles 是只读的</span></span><br><span class="line">styles.<span class="property">button</span> = <span class="string">&quot;new-class&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：通过字符串拼接</span></span><br><span class="line"><span class="keyword">const</span> className = <span class="string">`<span class="subst">$&#123;styles.button&#125;</span> <span class="subst">$&#123;styles.active&#125;</span>`</span>;</span><br></pre></td></tr></table></figure><p><strong>3️⃣在全局样式中使用模块类名</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ❌ 错误：global.css（全局样式文件） */</span></span><br><span class="line"><span class="selector-class">.Button_button__2Rx3L</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: red; <span class="comment">/* 哈希值会变，无法定位 */</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">/* ✅ 正确：使用语义化的全局类或 data 属性 */</span></span><br><span class="line"><span class="selector-attr">[data-component=<span class="string">&quot;button&quot;</span>]</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: red;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight jsx"><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">// 组件中添加 data 属性</span></span><br><span class="line">&lt;button className=&#123;styles.<span class="property">button</span>&#125; data-component=<span class="string">&quot;button&quot;</span>&gt;</span><br></pre></td></tr></table></figure><p><strong>4️⃣在 CSS Modules 中引用图片路径问题</strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">/* ❌ 可能失效 */</span></span><br><span class="line"><span class="selector-class">.background</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">./images/bg.png</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="selector-class">.background</span> &#123;</span><br><span class="line">  <span class="attribute">background-image</span>: <span class="built_in">url</span>(<span class="string">@/assets/images/bg.png</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Hero.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> bgImage <span class="keyword">from</span> <span class="string">&quot;./images/bg.png&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Hero</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> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">className</span>=<span class="string">&#123;styles.hero&#125;</span> </span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">backgroundImage:</span> `<span class="attr">url</span>($&#123;<span class="attr">bgImage</span>&#125;)` &#125;&#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></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><h4 id="17）调试技巧"><a href="#17）调试技巧" class="headerlink" title="17）调试技巧"></a><strong><font color='#10c300'>17）调试技巧</font></strong></h4><p><strong>1️⃣查看编译后的类名</strong></p><p>在浏览器开发者工具中检查元素，可以看到实际的类名：</p><figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">button</span> <span class="attr">class</span>=<span class="string">&quot;Button_button__2Rx3L Button_primary__3kM9x&quot;</span>&gt;</span>点击<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span><br></pre></td></tr></table></figure><p><strong>2️⃣在开发环境使用可读的类名</strong></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></pre></td><td class="code"><pre><span class="line"><span class="comment">// vite.config.js</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">defineConfig</span>(&#123;</span><br><span class="line">  <span class="attr">css</span>: &#123;</span><br><span class="line">    <span class="attr">modules</span>: &#123;</span><br><span class="line">      <span class="attr">generateScopedName</span>: </span><br><span class="line">        process.<span class="property">env</span>.<span class="property">NODE_ENV</span> === <span class="string">&quot;production&quot;</span></span><br><span class="line">          ? <span class="string">&quot;[hash:base64:8]&quot;</span>  <span class="comment">// 生产：短哈希</span></span><br><span class="line">          : <span class="string">&quot;[name]__[local]___[hash:base64:5]&quot;</span>, <span class="comment">// 开发：可读</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><p><strong>3️⃣使用 console.log 查看 styles 对象</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"><span class="keyword">import</span> styles <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(styles);</span><br><span class="line"><span class="comment">// 输出: &#123; button: &quot;Button_button__2Rx3L&quot;, primary: &quot;Button_primary__3kM9x&quot; &#125;</span></span><br></pre></td></tr></table></figure><h4 id="18）性能优化建议"><a href="#18）性能优化建议" class="headerlink" title="18）性能优化建议"></a><strong><font color='#10c300'>18）性能优化建议</font></strong></h4><ol><li><p><strong>按需导入</strong>：如果只需要少数几个类名，可以使用解构</p><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; button, primary &#125; <span class="keyword">from</span> <span class="string">&quot;./Button.module.css&quot;</span>;</span><br></pre></td></tr></table></figure></li><li><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 每次渲染都计算</span></span><br><span class="line">&#123;items.<span class="title function_">map</span>(<span class="function"><span class="params">item</span> =&gt;</span> (</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;item.active</span> ? <span class="attr">styles.active</span> <span class="attr">:</span> <span class="attr">styles.inactive</span>&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">))&#125;</span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">// ✅ 预先计算或使用 useMemo</span></span><br><span class="line"><span class="language-xml">const getItemClass = useCallback((active) =&gt; </span></span><br><span class="line"><span class="language-xml">  active ? styles.active : styles.inactive, </span></span><br><span class="line"><span class="language-xml">[]);</span></span><br></pre></td></tr></table></figure></li><li><p><strong>生产环境压缩类名</strong>：确保构建工具生成短哈希以减小 HTML 体积</p></li></ol><hr><br><h2 id="四、-React-Hooks-详解"><a href="#四、-React-Hooks-详解" class="headerlink" title="四、 React Hooks 详解"></a><strong>四、 React Hooks 详解</strong></h2><p>Hooks 是 React 16.8 引入的特性，让你在函数组件中使用状态和其他 React 特性。</p><h3 id="4-1-useState-状态管理"><a href="#4-1-useState-状态管理" class="headerlink" title="4.1 useState - 状态管理"></a><strong><font color='red'>4.1 useState - 状态管理</font></strong></h3><p><code>useState</code> 是一个 React Hook，用于在函数组件中声明和管理<strong>状态（state）</strong>（ 一定要在**<code>组件顶层</code>**调用）</p><hr><h4 id="1）基本语法"><a href="#1）基本语法" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(initialValue);</span><br></pre></td></tr></table></figure><ul><li>initialArg：定义的初始值，可以是任意数据，像数字，字符串或者数组和对象。</li><li>useState ()方法的返回值为由两个值组成的数组<ol><li><code>state</code>：当前状态值：在首次渲染时，它将与你传递的 <code>initialArg</code> 相匹配。</li><li><code>setState</code>：更新状态的函数：它可以让你将 state 更新为不同的值并触发重新渲染。</li></ol></li></ul><h4 id="2）基本用法"><a href="#2）基本用法" class="headerlink" title="2）基本用法"></a><strong><font color='#10c300'>2）基本用法</font></strong></h4><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;点击了 &#123;count&#125; 次<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Counter</span>;</span><br></pre></td></tr></table></figure><h4 id="3）核心特性详解"><a href="#3）核心特性详解" class="headerlink" title="3）核心特性详解"></a><strong><font color='#10c300'>3）核心特性详解</font></strong></h4><p><strong>1️⃣ 状态是隔离的</strong></p><p>每个组件实例拥有独立的状态，互不影响。</p><hr><p><strong>2️⃣ 状态更新是替换而非合并</strong></p><p>与 class 组件的 <code>setState</code> 不同，<code>useState</code> 不会自动合并对象：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(&#123; <span class="attr">name</span>: <span class="string">&quot;张三&quot;</span>, <span class="attr">age</span>: <span class="number">20</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ 错误：这会丢失 age 字段</span></span><br><span class="line"><span class="title function_">setUser</span>(&#123; <span class="attr">name</span>: <span class="string">&quot;李四&quot;</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：需要手动展开</span></span><br><span class="line"><span class="title function_">setUser</span>(&#123; ...user, <span class="attr">name</span>: <span class="string">&quot;李四&quot;</span> &#125;);</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣ 函数式更新（解决闭包问题）</strong></p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">incrementTwice</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// ❌ 问题：两次都基于 count=0，结果还是 1</span></span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>);</span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// ✅ 正确：基于最新状态更新</span></span><br><span class="line">    <span class="title function_">setCount</span>(<span class="function">(<span class="params">prev</span>) =&gt;</span> prev + <span class="number">1</span>);</span><br><span class="line">    <span class="title function_">setCount</span>(<span class="function">(<span class="params">prev</span>) =&gt;</span> prev + <span class="number">1</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;incrementTwice&#125;</span>&gt;</span>+2<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>4️⃣惰性初始化</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 每次渲染都会执行 heavyComputation()</span></span><br><span class="line"><span class="keyword">const</span> [data, setData] = <span class="title function_">useState</span>(<span class="title function_">heavyComputation</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 只在初始渲染执行一次，因为初始化的时候函数都会创建新的引用地址</span></span><br><span class="line"><span class="keyword">const</span> [data, setData] = <span class="title function_">useState</span>(<span class="function">() =&gt;</span> <span class="title function_">heavyComputation</span>());</span><br></pre></td></tr></table></figure><hr><p><strong>5️⃣数组&#x2F;对象更新技巧</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [list, setList] = <span class="title function_">useState</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line"><span class="keyword">const</span> [map, setMap] = <span class="title function_">useState</span>(<span class="keyword">new</span> <span class="title class_">Map</span>());</span><br><span class="line"></span><br><span class="line"><span class="comment">// 数组操作</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">addItem</span> = (<span class="params">item</span>) =&gt; <span class="title function_">setList</span>([...list, item]);</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">removeItem</span> = (<span class="params">index</span>) =&gt; <span class="title function_">setList</span>(list.<span class="title function_">filter</span>(<span class="function">(<span class="params">_, i</span>) =&gt;</span> i !== index));</span><br><span class="line"><span class="keyword">const</span> <span class="title function_">updateItem</span> = (<span class="params">index, value</span>) =&gt;</span><br><span class="line">  <span class="title function_">setList</span>(list.<span class="title function_">map</span>(<span class="function">(<span class="params">item, i</span>) =&gt;</span> (i === index ? value : item)));</span><br><span class="line"></span><br><span class="line"><span class="comment">// Map/Set 操作（需要新引用才会触发更新）</span></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">addToMap</span> = (<span class="params">k, v</span>) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> newMap = <span class="keyword">new</span> <span class="title class_">Map</span>(map);</span><br><span class="line">  newMap.<span class="title function_">set</span>(k, v);</span><br><span class="line">  <span class="title function_">setMap</span>(newMap);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="4）TypeScript-支持"><a href="#4）TypeScript-支持" class="headerlink" title="4）TypeScript 支持"></a><strong><font color='#10c300'>4）TypeScript 支持</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基础类型推断</span></span><br><span class="line"><span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>); <span class="comment">// number</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 联合类型</span></span><br><span class="line"><span class="keyword">const</span> [status, setStatus] = useState&lt;<span class="string">&quot;idle&quot;</span> | <span class="string">&quot;loading&quot;</span> | <span class="string">&quot;success&quot;</span>&gt;(<span class="string">&quot;idle&quot;</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 对象类型</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> [user, setUser] = useState&lt;<span class="title class_">User</span> | <span class="literal">null</span>&gt;(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果初始值为 undefined，需要显式指定类型</span></span><br><span class="line"><span class="keyword">const</span> [value, setValue] = useState&lt;<span class="built_in">string</span>&gt;();</span><br></pre></td></tr></table></figure><h4 id="5）常见陷阱与解决方案⚠️"><a href="#5）常见陷阱与解决方案⚠️" class="headerlink" title="5）常见陷阱与解决方案⚠️"></a><strong><font color='#10c300'>5）常见陷阱与解决方案⚠️</font></strong></h4><p><strong>陷阱 1️⃣：闭包过期</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><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="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 永远是旧值</span></span><br><span class="line">    <span class="title function_">setCount</span>(count + <span class="number">1</span>); <span class="comment">// 永远基于初始值</span></span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;, []); <span class="comment">// 空依赖导致闭包陷阱</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 解决：使用函数式更新或正确设置依赖</span></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> timer = <span class="built_in">setInterval</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">  &#125;, <span class="number">1000</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><hr><p><strong>陷阱 2️⃣：异步更新特性</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">setCount</span>(count + <span class="number">1</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 还是旧值！</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 如需基于新值操作，使用 useEffect 或函数式更新</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><hr><p><strong>陷阱 3️⃣：对象引用不变不触发更新</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><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="keyword">const</span> [items, setItems] = <span class="title function_">useState</span>([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">wrongUpdate</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  items.<span class="title function_">push</span>(<span class="number">4</span>); <span class="comment">// 修改原数组</span></span><br><span class="line">  <span class="title function_">setItems</span>(items); <span class="comment">// 引用相同，React 不重新渲染</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">correctUpdate</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">setItems</span>([...items, <span class="number">4</span>]); <span class="comment">// 新数组引用</span></span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure><h4 id="6）最佳实践"><a href="#6）最佳实践" class="headerlink" title="6）最佳实践"></a><strong><font color='#10c300'>6）最佳实践</font></strong></h4><p><strong>1️⃣ 合理拆分状态：</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><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="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(&#123; <span class="attr">user</span>: <span class="literal">null</span>, <span class="attr">posts</span>: [], <span class="attr">loading</span>: <span class="literal">false</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 独立声明</span></span><br><span class="line"><span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> [posts, setPosts] = <span class="title function_">useState</span>([]);</span><br><span class="line"><span class="keyword">const</span> [loading, setLoading] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣ 使用自定义 Hook 封装状态逻辑</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><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="keyword">function</span> <span class="title function_">useCounter</span>(<span class="params">initial = <span class="number">0</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(initial);</span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">inc</span> = (<span class="params"></span>) =&gt; <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">  <span class="keyword">const</span> <span class="title function_">dec</span> = (<span class="params"></span>) =&gt; <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">  <span class="keyword">const</span> <span class="title function_">reset</span> = (<span class="params"></span>) =&gt; <span class="title function_">setCount</span>(initial);</span><br><span class="line">  <span class="keyword">return</span> &#123; count, inc, dec, reset &#125;;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣ 避免深层嵌套状态</strong>：复杂状态考虑使用 <code>useReducer</code></p><p><code>useState</code> 适用于简单状态管理，当状态逻辑复杂或多个状态相互关联时，考虑升级到 <code>useReducer</code>。</p><br><h3 id="4-2-useEffect-副作用处理"><a href="#4-2-useEffect-副作用处理" class="headerlink" title="4.2 useEffect-副作用处理"></a><strong><font color='red'>4.2 useEffect-副作用处理</font></strong></h3><p>它让函数组件能够在<strong>渲染后执行副作用操作（side effects）</strong>，比如：网络请求、DOM 操作、事件监听、定时器、数据订阅等。</p><p>函数组件里没有生命周期方法（像类组件的 <code>componentDidMount</code>、<code>componentWillUnmount</code>）；React 提供 <code>useEffect</code> 来替代它们。</p><hr><h4 id="1）基本语法-1"><a href="#1）基本语法-1" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><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="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 执行副作用逻辑</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 清理副作用逻辑（可选）</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, [dependencies]);</span><br></pre></td></tr></table></figure><ul><li>**参数1 (函数)：**定义的初始值，可以是任意数据，像数字，字符串或者数组和对象。</li><li><strong>参数2 (依赖项)：</strong><ol><li><code>无参数</code>：每次渲染后都执行。</li><li><code>空数组</code>：仅在挂载时执行一次。</li><li><code>依赖参数</code>：依赖参数变化时执行（首次渲染也会执行一次）。</li></ol></li></ul><h4 id="2）三种执行时机"><a href="#2）三种执行时机" class="headerlink" title="2）三种执行时机"></a><strong><font color='#10c300'>2）三种执行时机</font></strong></h4><p><strong>1️⃣ 不带依赖 → 每次渲染都执行</strong></p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</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="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;每次渲染都执行&quot;</span>);</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><p>不写依赖数组时，意味着：</p><ul><li>每次组件<strong>挂载和更新</strong>都会运行。<br>⚠️ 如无必要，不建议省略依赖数组，会影响性能。</li></ul><hr><p><strong>2️⃣ 组件挂载时执行（只执行一次）</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><span class="line">5</span><br><span class="line">6</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="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;组件挂载&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;组件卸载&quot;</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, []); <span class="comment">// 空依赖数组 → 只执行一次</span></span><br></pre></td></tr></table></figure><p>可类比于：</p><ul><li>挂载时执行：<code>componentDidMount</code></li><li>卸载时清理：<code>componentWillUnmount</code></li></ul><hr><p><strong>3️⃣ 特定依赖变化时执行</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></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="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`count 更新了：<span class="subst">$&#123;count&#125;</span>`</span>);</span><br><span class="line">&#125;, [count]); <span class="comment">// 👈 当 count 改变重新运行副作用逻辑</span></span><br></pre></td></tr></table></figure><h4 id="3）清理函数（Cleanup）"><a href="#3）清理函数（Cleanup）" class="headerlink" title="3）清理函数（Cleanup）"></a><strong><font color='#10c300'>3）清理函数（Cleanup）</font></strong></h4><p>当**<code>取消订阅</code><strong>、</strong><code>清除定时器</code>**等，应该在组件卸载时清理，以防止内存泄漏。</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><span class="line">11</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> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;tick&quot;</span>);</span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 清理函数：组件卸载或依赖变化前执行</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;定时器已清理&quot;</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><p><strong>🔍执行时机</strong>：</p><ul><li>组件卸载时</li><li>依赖变化导致重新执行 effect 之前</li></ul><h4 id="4）实际应用场景"><a href="#4）实际应用场景" class="headerlink" title="4）实际应用场景"></a><strong><font color='#10c300'>4）实际应用场景</font></strong></h4><p><strong>1️⃣ 数据获取（需处理竞态）</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><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></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">let</span> cancelled = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">fetchData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> api.<span class="title function_">getUser</span>(userId);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 防止竞态条件：如果组件已卸载或 userId 已变，忽略结果</span></span><br><span class="line">    <span class="keyword">if</span> (!cancelled) &#123;</span><br><span class="line">      <span class="title function_">setUser</span>(data);</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">fetchData</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    cancelled = <span class="literal">true</span>; <span class="comment">// 取消标志</span></span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, [userId]);</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣事件监听</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><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> <span class="title function_">handleScroll</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="title function_">setScrollY</span>(<span class="variable language_">window</span>.<span class="property">scrollY</span>);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;scroll&quot;</span>, handleScroll);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 必须清理，否则重复挂载会添加多个监听器</span></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;scroll&quot;</span>, handleScroll);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣DOM 操作</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><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></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="comment">// 直接操作 DOM（应尽量避免，但在与第三方库集成时有用）</span></span><br><span class="line">  <span class="keyword">const</span> element = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;modal&quot;</span>);</span><br><span class="line">  element?.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;active&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    element?.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&quot;active&quot;</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><h4 id="5）常见陷阱⚠️"><a href="#5）常见陷阱⚠️" class="headerlink" title="5）常见陷阱⚠️"></a><strong><font color='#10c300'>5）常见陷阱⚠️</font></strong></h4><p><strong>1️⃣无限循环</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><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">// ❌ 错误：setState 导致渲染，渲染触发 effect，effect 又 setState</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setCount</span>(count + <span class="number">1</span>);</span><br><span class="line">&#125;, [count]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：使用函数式更新或条件判断</span></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> timer = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> <span class="title function_">setCount</span>(<span class="function">(<span class="params">c</span>) =&gt;</span> c + <span class="number">1</span>), <span class="number">1000</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="built_in">clearTimeout</span>(timer);</span><br><span class="line">&#125;, []); <span class="comment">// 或 [count] 如果需要响应外部 count 变化</span></span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣依赖缺失（ ESLint 会警告）</strong></p><p><code>eslint-plugin-react-hooks</code> 自动检查依赖。</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 遗漏依赖：callback 变化时 effect 不会更新</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fetchData</span>(query, callback);</span><br><span class="line">&#125;, [query]); <span class="comment">// 缺少 callback</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 解决方案 1：添加所有依赖</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fetchData</span>(query, callback);</span><br><span class="line">&#125;, [query, callback]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 解决方案 2：如果 callback 不稳定，使用 ref</span></span><br><span class="line"><span class="keyword">const</span> callbackRef = <span class="title function_">useRef</span>(callback);</span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  callbackRef.<span class="property">current</span> = callback;</span><br><span class="line">&#125;, [callback]);</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="title function_">fetchData</span>(query, <span class="function">(<span class="params">data</span>) =&gt;</span> callbackRef.<span class="title function_">current</span>(data));</span><br><span class="line">&#125;, [query]);</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣闭包陷阱（stale closure）</strong></p><figure class="highlight js"><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="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 永远是旧值！</span></span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;, []); <span class="comment">// 空依赖导致闭包捕获初始 count</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 解决方案 1：添加依赖</span></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> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(count);</span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;, [count]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 解决方案 2：使用 ref 获取最新值</span></span><br><span class="line"><span class="keyword">const</span> countRef = <span class="title function_">useRef</span>(count);</span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  countRef.<span class="property">current</span> = count;</span><br><span class="line">&#125;, [count]);</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> timer = <span class="built_in">setInterval</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(countRef.<span class="property">current</span>); <span class="comment">// 总是最新值</span></span><br><span class="line">  &#125;, <span class="number">1000</span>);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><hr><p><strong>4️⃣async&#x2F;await 直接使用</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：useEffect 不能返回 Promise（async 函数隐式返回 Promise）</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="keyword">await</span> <span class="title function_">fetchData</span>();</span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：内部定义 async 函数</span></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">async</span> <span class="keyword">function</span> <span class="title function_">loadData</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> data = <span class="keyword">await</span> <span class="title function_">fetchData</span>();</span><br><span class="line">    <span class="title function_">setData</span>(data);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">loadData</span>();</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><h4 id="6）useEffect-vs-useLayoutEffect"><a href="#6）useEffect-vs-useLayoutEffect" class="headerlink" title="6）useEffect vs useLayoutEffect"></a><strong><font color='#10c300'>6）useEffect vs useLayoutEffect</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useLayoutEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// useEffect：浏览器绘制完成后执行（不阻塞渲染）</span></span><br><span class="line"><span class="comment">// useLayoutEffect：浏览器绘制之前执行（阻塞渲染，避免闪烁）</span></span><br><span class="line"><span class="title function_">useLayoutEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 用于需要同步执行且影响视觉的 DOM 操作</span></span><br><span class="line">  <span class="keyword">const</span> width = element.<span class="title function_">getBoundingClientRect</span>().<span class="property">width</span>;</span><br><span class="line">  <span class="title function_">setWidth</span>(width);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><p><strong>使用建议</strong>：</p><ul><li>优先使用 <code>useEffect</code></li><li>仅在出现视觉闪烁（如从服务端渲染恢复时需要同步计算布局）时使用 <code>useLayoutEffect</code></li></ul><h4 id="7）TypeScript-支持"><a href="#7）TypeScript-支持" class="headerlink" title="7）TypeScript 支持"></a><strong><font color='#10c300'>7）TypeScript 支持</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 清理函数类型会自动推断</span></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> subscription = api.<span class="title function_">subscribe</span>();</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    subscription.<span class="title function_">unsubscribe</span>(); <span class="comment">// 类型安全</span></span><br><span class="line">  &#125;;</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="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(name);</span><br><span class="line">&#125;, [name]); <span class="comment">// name 必须是依赖项</span></span><br></pre></td></tr></table></figure><h4 id="8）最佳实践"><a href="#8）最佳实践" class="headerlink" title="8）最佳实践"></a><strong><font color='#10c300'>8）最佳实践</font></strong></h4><p>1️⃣<strong>单一职责</strong>：一个 <code>useEffect</code> 只做一件事，便于管理和清理</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 混合多个不相关逻辑</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fetchUser</span>();</span><br><span class="line">  <span class="keyword">const</span> timer = <span class="built_in">setInterval</span>(poll, <span class="number">5000</span>);</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">&quot;New Page&quot;</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 分离成多个 effect</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fetchUser</span>();</span><br><span class="line">&#125;, []);</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> timer = <span class="built_in">setInterval</span>(poll, <span class="number">5000</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">&#125;, []);</span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">document</span>.<span class="property">title</span> = <span class="string">&quot;New Page&quot;</span>;</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><hr><p>2️⃣<strong>自定义 Hook 封装副作用</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useWindowSize</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [size, setSize] = <span class="title function_">useState</span>(&#123; <span class="attr">width</span>: <span class="number">0</span>, <span class="attr">height</span>: <span class="number">0</span> &#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> <span class="title function_">update</span> = (<span class="params"></span>) =&gt;</span><br><span class="line">      <span class="title function_">setSize</span>(&#123;</span><br><span class="line">        <span class="attr">width</span>: <span class="variable language_">window</span>.<span class="property">innerWidth</span>,</span><br><span class="line">        <span class="attr">height</span>: <span class="variable language_">window</span>.<span class="property">innerHeight</span>,</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, update);</span><br><span class="line">    <span class="title function_">update</span>();</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;resize&quot;</span>, update);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> size;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p>3️⃣<strong>对象&#x2F;数组依赖使用 useMemo</strong></p><figure class="highlight plaintext"><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">// ❌ 每次渲染都是新对象，导致 effect 每次都执行</span><br><span class="line">useEffect(() =&gt; &#123;</span><br><span class="line">    fetchData(options);</span><br><span class="line">&#125;, [&#123; page: 1, size: 10 &#125;]);</span><br><span class="line"></span><br><span class="line">// ✅ 使用 useMemo 稳定引用</span><br><span class="line">const options = useMemo(() =&gt; (&#123; page: 1, size: 10 &#125;), []);</span><br><span class="line">useEffect(() =&gt; &#123;</span><br><span class="line">    fetchData(options);</span><br><span class="line">&#125;, [options]);</span><br></pre></td></tr></table></figure><br><h3 id="4-3-useContext-跨组件共享状态"><a href="#4-3-useContext-跨组件共享状态" class="headerlink" title="4.3 useContext - 跨组件共享状态"></a><strong><font color='red'>4.3 useContext - 跨组件共享状态</font></strong></h3><p>它主要用于在组件树中<strong>共享数据</strong>，<strong>避免层层传递 props（“props drilling”）的问题</strong>。</p><p>在 React 应用中，如果很多层组件之间都需要共享某个数据（比如主题、语言、用户信息），直接通过 props 一层层往下传会非常麻烦：</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></pre></td><td class="code"><pre><span class="line">&lt;<span class="title class_">App</span>&gt;</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;<span class="name">Layout</span> <span class="attr">theme</span>=<span class="string">&#123;theme&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Content</span> <span class="attr">theme</span>=<span class="string">&#123;theme&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Button</span> <span class="attr">theme</span>=<span class="string">&#123;theme&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Content</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/<span class="name">Layout</span>&gt;</span></span></span><br><span class="line">&lt;/<span class="title class_">App</span>&gt;</span><br></pre></td></tr></table></figure><p>👆 每层都要传 <code>theme</code>，这就是 <strong>props drilling</strong>。</p><p>React 提供 <strong>Context</strong>，可以让你在组件树间<strong>直接共享数据，不必层层传递</strong>。</p><h4 id="1）基础用法-使用步骤"><a href="#1）基础用法-使用步骤" class="headerlink" title="1）基础用法(使用步骤)"></a><strong><font color='#10c300'>1）基础用法(使用步骤)</font></strong></h4><p><strong>Step 1️⃣：创建 Context</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"><span class="comment">// src\context\index.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; createContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyContext</span> = <span class="title function_">createContext</span>();</span><br><span class="line"><span class="keyword">export</span> &#123; <span class="title class_">MyContext</span> &#125;;</span><br></pre></td></tr></table></figure><p><strong>Step 2️⃣：上层组件提供数据</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><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="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./components/Child&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">GrandChild</span> <span class="keyword">from</span> <span class="string">&quot;./components/GrandChild&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">MyContext</span> &#125; <span class="keyword">from</span> <span class="string">&quot;./context/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [data] = <span class="title function_">useState</span>(<span class="string">&quot;大鱼海棠&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">MyContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;data&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>我是父组件<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Child</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">GrandChild</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">Child</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/<span class="name">MyContext.Provider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p><strong>Step 3️⃣：子组件</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><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="keyword">function</span> <span class="title function_">Child</span>(<span class="params">&#123; children &#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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>我是子组件<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Child</span>;</span><br></pre></td></tr></table></figure><p><strong>Step 4️⃣：孙组件使用 <code>useContext</code> 获取值</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><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;useContext&#125; <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">MyContext</span> &#125; <span class="keyword">from</span> <span class="string">&quot;../context/index&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">GrandChild</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">// 这是app.jsx中传来的数据</span></span><br><span class="line">    <span class="keyword">const</span> data = <span class="title function_">useContext</span>(<span class="title class_">MyContext</span>)</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">p</span>&gt;</span>我是孙组件，红色部分是顶级组件数据：<span class="tag">&lt;<span class="name">span</span> <span class="attr">style</span>=<span class="string">&#123;&#123;color:</span> &#x27;<span class="attr">red</span>&#x27;&#125;&#125;&gt;</span>&#123;data&#125;<span class="tag">&lt;/<span class="name">span</span>&gt;</span><span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> defau lt <span class="title class_">GrandChild</span></span><br></pre></td></tr></table></figure><h4 id="2）自定义-Hook-封装（推荐）"><a href="#2）自定义-Hook-封装（推荐）" class="headerlink" title="2）自定义 Hook 封装（推荐）"></a><strong><font color='#10c300'>2）自定义 Hook 封装（推荐）</font></strong></h4><p>直接 <code>useContext</code> 需要在每个组件处理 <code>null</code>，封装更安全：</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// contexts/ThemeContext.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createContext, useState, useContext, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ThemeContext</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义 Hook，内置错误处理</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useTheme</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="title function_">useContext</span>(<span class="title class_">ThemeContext</span>);</span><br><span class="line">  <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;useTheme 必须在 ThemeProvider 内部使用&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> context;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">ThemeProvider</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [theme, setTheme] = <span class="title function_">useState</span>(<span class="string">&quot;light&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 useMemo 防止不必要的重渲染</span></span><br><span class="line">  <span class="keyword">const</span> value = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      theme,</span><br><span class="line">      setTheme,</span><br><span class="line">      <span class="attr">toggle</span>: <span class="function">() =&gt;</span> <span class="title function_">setTheme</span>(<span class="function">(<span class="params">t</span>) =&gt;</span> (t === <span class="string">&quot;light&quot;</span> ? <span class="string">&quot;dark&quot;</span> : <span class="string">&quot;light&quot;</span>)),</span><br><span class="line">    &#125;),</span><br><span class="line">    [theme],</span><br><span class="line">  );</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">ThemeContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;value&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">ThemeContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.vue 使用：</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ThemeProvider</span>, useTheme &#125; <span class="keyword">from</span> <span class="string">&quot;./contexts/ThemeContext&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Button</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; theme, toggle &#125; = <span class="title function_">useTheme</span>(); <span class="comment">// 直接使用，类型安全</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;toggle&#125;</span>&gt;</span>&#123;theme&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</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">ThemeProvider</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 class="language-xml">    <span class="tag">&lt;/<span class="name">ThemeProvider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）性能优化（关键）"><a href="#3）性能优化（关键）" class="headerlink" title="3）性能优化（关键）"></a><strong><font color='#10c300'>3）性能优化（关键）</font></strong></h4><p><strong>Context 的问题</strong>：一旦 <code>value</code> 变化，<strong>所有</strong>消费组件都会重渲染，即使只使用了部分数据。</p><p><strong>方案1️⃣：拆分 Context（推荐）</strong></p><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 避免单一 Context 包含所有状态</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AppContext</span> = <span class="title function_">createContext</span>(&#123;</span><br><span class="line">  <span class="attr">user</span>: &#123;&#125;,</span><br><span class="line">  <span class="attr">theme</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  <span class="attr">notifications</span>: [],</span><br><span class="line">  <span class="comment">// ... 所有状态</span></span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 拆分为多个 Context</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">UserContext</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">ThemeContext</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">NotificationContext</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 组件只订阅需要的 Context</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">UserAvatar</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> user = <span class="title function_">useContext</span>(<span class="title class_">UserContext</span>); <span class="comment">// 只有 user 变化时重渲染</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#123;user.avatar&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>方案2️⃣：使用 React.memo</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><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="keyword">function</span> <span class="title function_">ExpensiveComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; theme &#125; = <span class="title function_">useContext</span>(<span class="title class_">ThemeContext</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&#123;theme&#125;</span>&gt;</span>...<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 虽然 context 变化，但 props 没变时跳过渲染</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">ExpensiveComponent</span>);</span><br></pre></td></tr></table></figure><hr><p><strong>方案3️⃣：使用第三方状态管理</strong></p><p>如果频繁更新（如滚动位置、动画状态），使用 Zustand、Jotai 或 Redux，它们支持细粒度订阅。</p><p><strong>useContext + useReducer（简易 Redux）</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// contexts/StoreContext.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; createContext, useReducer, useContext, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">StoreContext</span> = <span class="title function_">createContext</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialState = &#123; <span class="attr">count</span>: <span class="number">0</span>, <span class="attr">user</span>: <span class="literal">null</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;increment&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;setUser&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">user</span>: action.<span class="property">payload</span> &#125;;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 自定义 Hooks</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useStore</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useContext</span>(<span class="title class_">StoreContext</span>);</span><br><span class="line">  <span class="keyword">return</span> &#123; state, dispatch &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">StoreProvider</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, initialState);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 将 state 和 dispatch 都放入 context</span></span><br><span class="line">  <span class="keyword">const</span> value = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> [state, dispatch], [state]);</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">StoreContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;value&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">StoreContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">StoreProvider</span>, useStore &#125; <span class="keyword">from</span> <span class="string">&quot;./contexts/ThemeContext&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; state, dispatch &#125; = <span class="title function_">useStore</span>();</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 class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;increment&quot; &#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">      &#123;state.count&#125;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</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">StoreProvider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Counter</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">StoreProvider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="4）TypeScript-支持-1"><a href="#4）TypeScript-支持-1" class="headerlink" title="4）TypeScript 支持"></a><strong><font color='#10c300'>4）TypeScript 支持</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">interface</span> <span class="title class_">User</span> &#123;</span><br><span class="line">  <span class="attr">id</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">name</span>: <span class="built_in">string</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">AuthContextType</span> &#123;</span><br><span class="line">  <span class="attr">user</span>: <span class="title class_">User</span> | <span class="literal">null</span>;</span><br><span class="line">  <span class="attr">login</span>: <span class="function">(<span class="params"><span class="attr">user</span>: <span class="title class_">User</span></span>) =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">  <span class="attr">logout</span>: <span class="function">() =&gt;</span> <span class="built_in">void</span>;</span><br><span class="line">  <span class="attr">isLoading</span>: <span class="built_in">boolean</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">AuthContext</span> = createContext&lt;<span class="title class_">AuthContextType</span> | <span class="literal">null</span>&gt;(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 类型安全的自定义 Hook</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">useAuth</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> context = <span class="title function_">useContext</span>(<span class="title class_">AuthContext</span>);</span><br><span class="line">  <span class="keyword">if</span> (!context) &#123;</span><br><span class="line">    <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">&quot;useAuth must be used within AuthProvider&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="keyword">return</span> context;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// Provider 组件</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">function</span> <span class="title function_">AuthProvider</span>(<span class="params">&#123; children &#125;: &#123; children: React.ReactNode &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = useState&lt;<span class="title class_">User</span> | <span class="literal">null</span>&gt;(<span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">const</span> [isLoading, setIsLoading] = <span class="title function_">useState</span>(<span class="literal">false</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> value = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      user,</span><br><span class="line">      isLoading,</span><br><span class="line">      <span class="attr">login</span>: <span class="function">(<span class="params"><span class="attr">u</span>: <span class="title class_">User</span></span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">setUser</span>(u);</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">logout</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="title function_">setUser</span>(<span class="literal">null</span>);</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">    [user, isLoading],</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">AuthContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;value&#125;</span>&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">AuthContext.Provider</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5）常见陷阱⚠️-1"><a href="#5）常见陷阱⚠️-1" class="headerlink" title="5）常见陷阱⚠️"></a><strong><font color='#10c300'>5）常见陷阱⚠️</font></strong></h4><p><strong>1️⃣Context 默认值陷阱</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><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">// 如果不提供 Provider，会使用默认值</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyContext</span> = <span class="title function_">createContext</span>(&#123; <span class="attr">value</span>: <span class="number">0</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ctx = <span class="title function_">useContext</span>(<span class="title class_">MyContext</span>);</span><br><span class="line">  <span class="comment">// 如果忘记包裹 Provider，ctx 是 &#123; value: 0 &#125;</span></span><br><span class="line">  <span class="comment">// 这可能隐藏错误，建议默认值设为 null 并检查</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣不必要的重渲染</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ❌ 每次渲染都是新对象，导致所有消费者重渲染</span></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">ThemeContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">theme:</span> &quot;<span class="attr">dark</span>&quot;, <span class="attr">count</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ThemeContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ✅ 使用 useMemo 缓存</span></span><br><span class="line">  <span class="keyword">const</span> value = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> (&#123; <span class="attr">theme</span>: <span class="string">&quot;dark&quot;</span>, count &#125;), [count]);</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">ThemeContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;value&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ThemeContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣在条件语句中使用</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><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="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (condition) &#123;</span><br><span class="line">    <span class="keyword">const</span> ctx = <span class="title function_">useContext</span>(<span class="title class_">MyContext</span>); <span class="comment">// ❌ Hook 必须在顶层调用</span></span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ✅ 始终在组件顶层调用</span></span><br><span class="line">  <span class="keyword">const</span> ctx = <span class="title function_">useContext</span>(<span class="title class_">MyContext</span>);</span><br><span class="line">  <span class="keyword">if</span> (!ctx) <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="6）何时使用-vs-不用"><a href="#6）何时使用-vs-不用" class="headerlink" title="6）何时使用 vs 不用"></a><strong><font color='#10c300'>6）何时使用 vs 不用</font></strong></h4><p><strong>1️⃣适合使用：</strong></p><ul><li>主题、语言等全局配置</li><li>认证状态（用户登录信息）</li><li>路由状态</li><li>需要在深层组件访问的共享状态</li></ul><p><strong>2️⃣避免使用：</strong></p><ul><li>仅父子组件通信（直接用 props）</li><li>频繁更新的状态（如滚动、输入、动画，考虑使用订阅模式或外部状态管理）</li><li>简单表单状态（用本地 useState）</li></ul><p><strong>替代方案👇</strong></p><ul><li>简单共享：props drilling、组合组件（composition）</li><li>高频更新：Zustand、Jotai、Recoil、Redux</li><li>服务端状态：React Query、SWR</li></ul><h4 id="7）高级模式：Render-Props-转-Context"><a href="#7）高级模式：Render-Props-转-Context" class="headerlink" title="7）高级模式：Render Props 转 Context"></a><strong><font color='#10c300'>7）高级模式：Render Props 转 Context</font></strong></h4><p>将旧版 Render Props 组件转为 Hook：</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><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="comment">// 旧方式</span></span><br><span class="line">&lt;<span class="title class_">MouseTracker</span>&gt;</span><br><span class="line">  &#123;<span class="function">(<span class="params">&#123; x, y &#125;</span>) =&gt;</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">      &#123;x&#125;, &#123;y&#125;</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">  )&#125;</span><br><span class="line">&lt;/<span class="title class_">MouseTracker</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 新方式：结合 Context 和 Hook</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MouseContext</span> = <span class="title function_">createContext</span>(&#123; <span class="attr">x</span>: <span class="number">0</span>, <span class="attr">y</span>: <span class="number">0</span> &#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useMouse</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useContext</span>(<span class="title class_">MouseContext</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="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; x, y &#125; = <span class="title function_">useMouse</span>(); <span class="comment">// 更简洁的 API</span></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">      &#123;x&#125;, &#123;y&#125;</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><br><h3 id="4-4-useMemo-缓存计算结果"><a href="#4-4-useMemo-缓存计算结果" class="headerlink" title="4.4 useMemo- 缓存计算结果"></a><strong><font color='red'>4.4 useMemo- 缓存计算结果</font></strong></h3><p><code>useMemo</code> 用于缓存昂贵的计算结果，避免每次渲染都重新计算，同时保持<strong>对象&#x2F;数组的引用</strong>稳定。</p><p>换句话说：</p><ul><li>如果依赖没变 → 直接用上次计算的结果；</li><li>如果依赖变了 → 重新计算并返回新结果。</li></ul><p>它可以帮你显著减少不必要的计算或对象重建。</p><p><code>useMemo</code>的理念是同步的，<strong>useMemo不能进行一些额外的副操作，比如网络请求等</strong>。</p><hr><h4 id="1）基本语法-2"><a href="#1）基本语法-2" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> 值;</span><br><span class="line">&#125;, [依赖项]);</span><br></pre></td></tr></table></figure><ul><li>参数1 (工厂函数)：一个返回值的函数（执行计算）</li><li>参数2 (依赖数组)：依赖数组，当其中某项改变时才重新计算</li></ul><p>返回值：<strong>缓存的计算结果</strong>。</p><h4 id="2）使用场景示例"><a href="#2）使用场景示例" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><p><strong>1️⃣ 复杂计算缓存</strong></p><p>未使用<code>useMemo</code>的时候改变颜色，也会执行ComputeTotal价格的计算。</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><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">import</span> &#123; useState, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ComputeTotal</span>(<span class="params">price, count</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;函数运行了&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> price * count;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [price, setPrice] = <span class="title function_">useState</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">const</span> [count] = <span class="title function_">useState</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">const</span> [color, setColor] = <span class="title function_">useState</span>(<span class="string">&quot;red&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 👉 只有 price 变化时，才重新计算</span></span><br><span class="line">  <span class="keyword">const</span> totalPrice = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> <span class="title class_">ComputeTotal</span>(price, count), [price]);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>总价：&#123;totalPrice&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;color&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setColor(&quot;blue&quot;)&#125;&gt;修改颜色<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setPrice(price + 100)&#125;&gt;修改价格<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>✅ 当你改变颜色的时候，不会重新执行ComputeTotal。只有price变化时才会重新计算。</p><hr><p><strong>2️⃣ 保持引用稳定，避免重渲染（关键用途）</strong></p><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><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="keyword">function</span> <span class="title function_">ChartComponent</span>(<span class="params">&#123; data, options &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// ❌ 每次渲染都是新对象，导致 useEffect 无限循环或 Chart 组件重渲染</span></span><br><span class="line">  <span class="keyword">const</span> config = &#123; <span class="attr">type</span>: <span class="string">&quot;line&quot;</span>, data, ...options &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ✅ 依赖不变时保持同一引用</span></span><br><span class="line">  <span class="keyword">const</span> config = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">type</span>: <span class="string">&quot;line&quot;</span>,</span><br><span class="line">      data,</span><br><span class="line">      <span class="attr">options</span>: &#123;</span><br><span class="line">        <span class="attr">responsive</span>: <span class="literal">true</span>,</span><br><span class="line">        ...options,</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">    [data, options],</span><br><span class="line">  );</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="comment">// 现在只在 config 真正变化时执行</span></span><br><span class="line">    chartRef.<span class="property">current</span>.<span class="title function_">update</span>(config);</span><br><span class="line">  &#125;, [config]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">canvas</span> <span class="attr">ref</span>=<span class="string">&#123;canvasRef&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>示例二：</strong></p><p>例如子组件使用 <code>React.memo</code>：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params">&#123; options &#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Child render&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;options.join(&quot;, &quot;)&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MemoChild</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">Child</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="comment">// 如果不缓存，数组每次渲染都新建，导致 Child 重新渲染</span></span><br><span class="line">  <span class="keyword">const</span> options = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> [<span class="string">&quot;A&quot;</span>, <span class="string">&quot;B&quot;</span>], []); <span class="comment">// 👈 缓存数组引用</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">MemoChild</span> <span class="attr">options</span>=<span class="string">&#123;options&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Parent</span>;</span><br></pre></td></tr></table></figure><p><code>useMemo</code> 保证每次渲染中 <code>options</code> 的引用稳定，<code>React.memo</code> 会认为 props 没变，从而跳过重新渲染。</p><hr><p><strong>3️⃣ 缓存组件(极少用)</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><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; useState, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./Child&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [price, setPrice] = <span class="title function_">useState</span>(<span class="number">100</span>);</span><br><span class="line">  <span class="keyword">const</span> [count] = <span class="title function_">useState</span>(<span class="number">1</span>);</span><br><span class="line">  <span class="keyword">const</span> [color, setColor] = <span class="title function_">useState</span>(<span class="string">&quot;red&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> memoizedChild = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Child</span> <span class="attr">count</span>=<span class="string">&#123;count&#125;</span> <span class="attr">price</span>=<span class="string">&#123;price&#125;</span> /&gt;</span></span>,</span><br><span class="line">    [count, price],</span><br><span class="line">  );</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;color&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setColor(&quot;blue&quot;)&#125;&gt;修改颜色<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setPrice(price + 100)&#125;&gt;修改价格<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 使用组件 */&#125;</span></span><br><span class="line"><span class="language-xml">      &#123;memoizedChild&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h4 id="3）与-useCallback-的关系"><a href="#3）与-useCallback-的关系" class="headerlink" title="3）与 useCallback 的关系"></a><strong><font color='#10c300'>3）与 useCallback 的关系</font></strong></h4><p><code>useCallback</code> 本质上是 <code>useMemo</code> 的语法糖：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 这两者是等价的</span></span><br><span class="line"><span class="title function_">useCallback</span>(fn, deps);</span><br><span class="line"><span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> fn, deps);</span><br></pre></td></tr></table></figure><p><strong>选择原则</strong>：</p><ul><li>缓存<strong>函数</strong> → <code>useCallback</code></li><li>缓存<strong>值</strong>（对象、数组、计算结果）→ <code>useMemo</code></li></ul><h4 id="4）实际示例：搜索过滤"><a href="#4）实际示例：搜索过滤" class="headerlink" title="4）实际示例：搜索过滤"></a><strong><font color='#10c300'>4）实际示例：搜索过滤</font></strong></h4><p>**完整案例：**<a href="https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s?singleDoc#wkQxl">https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s?singleDoc#wkQxl</a></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">SearchResults</span>(<span class="params">&#123; query, items &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [highlightIndex, setHighlightIndex] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 搜索结果缓存</span></span><br><span class="line">  <span class="keyword">const</span> results = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (!query) <span class="keyword">return</span> [];</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> startTime = performance.<span class="title function_">now</span>();</span><br><span class="line">    <span class="keyword">const</span> filtered = items.<span class="title function_">filter</span>(<span class="function">(<span class="params">item</span>) =&gt;</span></span><br><span class="line">      item.<span class="property">text</span>.<span class="title function_">toLowerCase</span>().<span class="title function_">includes</span>(query.<span class="title function_">toLowerCase</span>()),</span><br><span class="line">    );</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`搜索耗时: <span class="subst">$&#123;performance.now() - startTime&#125;</span>ms`</span>);</span><br><span class="line">    <span class="keyword">return</span> filtered;</span><br><span class="line">  &#125;, [query, items]);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 高亮项缓存（基于 results，形成计算链）</span></span><br><span class="line">  <span class="keyword">const</span> highlightedItem = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> results[highlightIndex] || <span class="literal">null</span>;</span><br><span class="line">  &#125;, [results, highlightIndex]);</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">      &#123;results.map((item, idx) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">key</span>=<span class="string">&#123;item.id&#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;idx</span> === <span class="string">highlightIndex</span> ? &quot;<span class="attr">highlight</span>&quot; <span class="attr">:</span> &quot;&quot;&#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">          &#123;item.text&#125;</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">      ))&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Preview</span> <span class="attr">data</span>=<span class="string">&#123;highlightedItem&#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><h4 id="4）TypeScript-支持-2"><a href="#4）TypeScript-支持-2" class="headerlink" title="4）TypeScript 支持"></a><strong><font color='#10c300'>4）TypeScript 支持</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line">interface <span class="title class_">User</span> &#123;</span><br><span class="line">    <span class="attr">id</span>: number;</span><br><span class="line">    <span class="attr">name</span>: string;</span><br><span class="line">    <span class="attr">score</span>: number;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 自动推断返回类型为 number</span></span><br><span class="line">  <span class="keyword">const</span> averageScore = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (users.<span class="property">length</span> === <span class="number">0</span>) <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">    <span class="keyword">return</span> users.<span class="title function_">reduce</span>(<span class="function">(<span class="params">sum, u</span>) =&gt;</span> sum + u.<span class="property">score</span>, <span class="number">0</span>) / users.<span class="property">length</span>;</span><br><span class="line">  &#125;, [users]);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 复杂对象类型</span></span><br><span class="line">  <span class="keyword">const</span> processedData = useMemo&lt;&#123; <span class="attr">labels</span>: string[]; <span class="attr">values</span>: number[] &#125;&gt;(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">labels</span>: data.<span class="title function_">map</span>(<span class="function"><span class="params">d</span> =&gt;</span> d.<span class="property">date</span>),</span><br><span class="line">      <span class="attr">values</span>: data.<span class="title function_">map</span>(<span class="function"><span class="params">d</span> =&gt;</span> d.<span class="property">value</span>)</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, [data]);</span><br></pre></td></tr></table></figure><h4 id="5）常见陷阱⚠️-2"><a href="#5）常见陷阱⚠️-2" class="headerlink" title="5）常见陷阱⚠️"></a><strong><font color='#10c300'>5）常见陷阱⚠️</font></strong></h4><p><strong>1️⃣过度使用（负优化）</strong></p><p><code>useMemo</code> 本身也有开销（依赖比较、缓存存储），简单计算无需缓存：</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 没必要：加法计算比 useMemo 开销更小</span></span><br><span class="line"><span class="keyword">const</span> sum = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> a + b, [a, b]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 直接使用</span></span><br><span class="line"><span class="keyword">const</span> sum = a + b;</span><br></pre></td></tr></table></figure><p><strong>使用时机</strong>：</p><ul><li>计算复杂度 &gt; O(n) 且 n 较大</li><li>需要保持对象引用稳定（用于 props 或依赖数组）</li><li>明确测量到性能瓶颈（React DevTools Profiler）</li></ul><hr><p><strong>2️⃣依赖数组遗漏</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 遗漏 user，导致使用旧的 user 数据</span></span><br><span class="line"><span class="keyword">const</span> fullName = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;user.firstName&#125;</span> <span class="subst">$&#123;lastName&#125;</span>`</span>; <span class="comment">// user 来自外层作用域</span></span><br><span class="line">&#125;, [lastName]); <span class="comment">// 缺少 user</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 完整依赖</span></span><br><span class="line"><span class="keyword">const</span> fullName = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="string">`<span class="subst">$&#123;user.firstName&#125;</span> <span class="subst">$&#123;lastName&#125;</span>`</span>;</span><br><span class="line">&#125;, [user, lastName]);</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣在 useMemo 里执行副作用</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><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="comment">// ❌ 错误：useMemo 应该纯函数，不执行副作用</span></span><br><span class="line"><span class="keyword">const</span> data = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&quot;key&quot;</span>, value); <span class="comment">// 副作用！</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">process</span>(value);</span><br><span class="line">&#125;, [value]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 副作用放在 useEffect</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">localStorage</span>.<span class="title function_">setItem</span>(<span class="string">&quot;key&quot;</span>, value);</span><br><span class="line">&#125;, [value]);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> data = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> <span class="title function_">process</span>(value), [value]);</span><br></pre></td></tr></table></figure><hr><p><strong>4️⃣ 返回函数时的混淆</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 这样缓存的是函数的返回值，不是函数本身</span></span><br><span class="line"><span class="keyword">const</span> handler = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;clicked&quot;</span>); <span class="comment">// 返回一个函数</span></span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 如果真要缓存函数，直接用 useCallback</span></span><br><span class="line"><span class="keyword">const</span> handler = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;clicked&quot;</span>);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure><h4 id="6）高级模式"><a href="#6）高级模式" class="headerlink" title="6）高级模式"></a><strong><font color='#10c300'>6）高级模式</font></strong></h4><p><strong>1️⃣计算链</strong></p><p><code>useMemo</code> 可以依赖其他 <code>useMemo</code> 的结果，形成计算链：</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> rawData = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> <span class="title function_">fetchData</span>(), []);</span><br><span class="line"><span class="keyword">const</span> processedData = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> <span class="title function_">cleanData</span>(rawData), [rawData]);</span><br><span class="line"><span class="keyword">const</span> statistics = <span class="title function_">useMemo</span>(</span><br><span class="line">  <span class="function">() =&gt;</span> <span class="title function_">calculateStats</span>(processedData),</span><br><span class="line">  [processedData],</span><br><span class="line">);</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣条件性缓存</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"><span class="keyword">const</span> value = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">if</span> (!enabled) <span class="keyword">return</span> <span class="literal">null</span>; <span class="comment">// 提前返回</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">heavyComputation</span>(data);</span><br><span class="line">&#125;, [enabled, data]);</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣与 useEffect 配合防止无限循环</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><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> userIds = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> users.<span class="title function_">map</span>(<span class="function">(<span class="params">u</span>) =&gt;</span> u.<span class="property">id</span>), [users]);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在 userIds 引用稳定，不会导致 effect 每次都执行</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">fetchDetails</span>(userIds);</span><br><span class="line">&#125;, [userIds]);</span><br></pre></td></tr></table></figure><br><p>🌟 <strong>一句话总结：</strong></p><p><code>useMemo</code> 用来解决”因为对象引用变化导致的无效重渲染”比解决”重复计算”更常见</p><br><h3 id="4-5-useCallback-缓存函数引用"><a href="#4-5-useCallback-缓存函数引用" class="headerlink" title="4.5 useCallback- 缓存函数引用"></a><strong><font color='red'>4.5 useCallback- 缓存函数引用</font></strong></h3><p>在 React 中，每次组件渲染都会重新执行组件函数体，<strong>里面定义的函数也会被“重新创建”</strong>。<br>如果这些函数作为 props 传给子组件，即使函数逻辑没有变，根据 <strong>引用地址比较</strong>，子组件会认为 props 变了而重新渲染。</p><p><code>useCallback</code> 解决了这个问题：</p><p>✅ **让函数引用在依赖不变时保持稳定（不变）**语义上，<code>useCallback</code> 明确表示”缓存函数”，而 <code>useMemo</code> 用于缓存任意计算值。</p><hr><h4 id="1）基本语法-3"><a href="#1）基本语法-3" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> memoizedCallback = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 函数逻辑</span></span><br><span class="line">&#125;, [dependencies]);</span><br></pre></td></tr></table></figure><ul><li><strong>第一个参数</strong>：要缓存的回调函数</li><li><strong>第二个参数</strong>：依赖数组，当依赖中有值变化时，返回的新函数引用会更新</li></ul><h4 id="2）核心使用场景"><a href="#2）核心使用场景" class="headerlink" title="2）核心使用场景"></a><strong><font color='#10c300'>2）核心使用场景</font></strong></h4><p><strong>1️⃣传递给优化后的子组件（最常见）</strong></p><p>配合 <code>React.memo</code> 防止子组件因父组件渲染而重渲染：</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span>, &#123; useState, useCallback &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params">&#123; onClick &#125;</span>) &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;子组件渲染了&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;onClick&#125;</span>&gt;</span>子组件按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MemoChild</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">Child</span>); <span class="comment">// 👈 只有 props 变了才渲染</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> [text, setText] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 只有 count 变化时才会更新函数引用(子组件渲染)</span></span><br><span class="line">  <span class="keyword">const</span> handleClick = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Clicked:&quot;</span>, count);</span><br><span class="line">  &#125;, [count]); <span class="comment">// 👈 缓存函数引用</span></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">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;count +1<span class="tag">&lt;/<span class="name">button</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;text&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;(e)</span> =&gt;</span> setText(e.target.value)&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">MemoChild</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p><strong>🔍 分析：</strong></p><ul><li>如果不用 <code>useCallback</code>，每次 App 渲染都会“新建”一个 <code>handleClick</code> 函数引用；</li><li><code>MemoChild</code> 会认为 props (<code>onClick</code>) 改了 → 重新渲染；</li><li>用了 <code>useCallback</code> 后，在依赖不变化时，函数引用保持稳定 → 组件不重渲染。</li></ul><hr><p><strong>2️⃣作为 useEffect 的依赖</strong></p><p>当 <code>useEffect</code> 依赖某个函数时，必须用 <code>useCallback</code> 保持引用稳定</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><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="keyword">function</span> <span class="title function_">Search</span>(<span class="params">&#123; query &#125;</span>) &#123;</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="comment">// ❌ 每次渲染都是新函数，导致 effect 每次都要执行</span></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">fetchData</span> = <span class="keyword">async</span> (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> api.<span class="title function_">search</span>(query);</span><br><span class="line">    <span class="title function_">setResults</span>(res);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ✅ fetchData 引用稳定，只有 query 变化时才重新触发 effect</span></span><br><span class="line">  <span class="keyword">const</span> fetchData = <span class="title function_">useCallback</span>(<span class="title function_">async</span> () =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> res = <span class="keyword">await</span> api.<span class="title function_">search</span>(query);</span><br><span class="line">    <span class="title function_">setResults</span>(res);</span><br><span class="line">  &#125;, [query]);</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="title function_">fetchData</span>();</span><br><span class="line">  &#125;, [fetchData]); <span class="comment">// 现在可以安全地将函数放入依赖数组</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Results</span> <span class="attr">data</span>=<span class="string">&#123;results&#125;</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣自定义 Hook 中返回的回调</strong></p><p>确保 Hook 使用者可以获得稳定的函数引用：</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useDebounce</span>(<span class="params">callback, delay</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [debouncedCallback] = <span class="title function_">useState</span>(<span class="function">() =&gt;</span> <span class="title function_">debounce</span>(callback, delay));</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回稳定的函数引用</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useCallback</span>(</span><br><span class="line">    <span class="function">(<span class="params">...args</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="title function_">debouncedCallback</span>(...args);</span><br><span class="line">    &#125;,</span><br><span class="line">    [debouncedCallback],</span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）什么时候用-useCallback"><a href="#3）什么时候用-useCallback" class="headerlink" title="3）什么时候用 useCallback"></a><strong><font color='#10c300'>3）什么时候用 useCallback</font></strong></h4><ol><li>函数作为 props 传递给 <code>React.memo</code> 包裹的子组件</li><li>函数作为其他 Hook（<code>useEffect</code>、<code>useMemo</code>）的依赖</li><li>函数是自定义 Hook 的返回值，供外部使用</li></ol><h4 id="4）TypeScript-支持-3"><a href="#4）TypeScript-支持-3" class="headerlink" title="4）TypeScript 支持"></a><strong><font color='#10c300'>4）TypeScript 支持</font></strong></h4><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><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 基础类型推断</span></span><br><span class="line"><span class="keyword">const</span> handleChange = <span class="title function_">useCallback</span>(<span class="function">(<span class="params"><span class="attr">e</span>: <span class="title class_">React</span>.<span class="title class_">ChangeEvent</span>&lt;<span class="title class_">HTMLInputElement</span>&gt;</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setValue</span>(e.<span class="property">target</span>.<span class="property">value</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="keyword">type</span> <span class="title class_">FetchFn</span> = <span class="function">(<span class="params"><span class="attr">id</span>: <span class="built_in">string</span></span>) =&gt;</span> <span class="title class_">Promise</span>&lt;<span class="title class_">User</span>&gt;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> fetchUser = useCallback&lt;<span class="title class_">FetchFn</span>&gt;(<span class="title function_">async</span> (id) =&gt; &#123;</span><br><span class="line">  <span class="keyword">const</span> res = <span class="keyword">await</span> api.<span class="title function_">getUser</span>(id);</span><br><span class="line">  <span class="keyword">return</span> res.<span class="property">data</span>;</span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 返回元组（常见于自定义 Hook）</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useToggle</span>(<span class="params">initial = <span class="literal">false</span></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(initial);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> toggle = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setState</span>(<span class="function">(<span class="params">s</span>) =&gt;</span> !s);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> setTrue = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> <span class="title function_">setState</span>(<span class="literal">true</span>), []);</span><br><span class="line">  <span class="keyword">const</span> setFalse = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> <span class="title function_">setState</span>(<span class="literal">false</span>), []);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 类型自动推断为 [boolean, () =&gt; void, () =&gt; void, () =&gt; void]</span></span><br><span class="line">  <span class="keyword">return</span> [state, toggle, setTrue, setFalse] <span class="keyword">as</span> <span class="keyword">const</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="5）高级模式"><a href="#5）高级模式" class="headerlink" title="5）高级模式"></a><strong><font color='#10c300'>5）高级模式</font></strong></h4><p><strong>1️⃣结合 useRef 解决过期闭包（稳定引用 + 最新值）</strong></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useStableCallback</span>(<span class="params">fn</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> ref = <span class="title function_">useRef</span>(fn);</span><br><span class="line">  ref.<span class="property">current</span> = fn; <span class="comment">// 每次渲染更新 ref</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回稳定引用，但始终调用最新函数</span></span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useCallback</span>(<span class="function">(<span class="params">...args</span>) =&gt;</span> ref.<span class="title function_">current</span>(...args), []);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用：可以放入 useEffect 依赖而不触发重新执行，且能访问最新 props/state</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params">&#123; onUpdate &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> stableCallback = <span class="title function_">useStableCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;最新状态&quot;</span>); <span class="comment">// 永远能访问最新值，无需依赖数组</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> timer = <span class="built_in">setInterval</span>(stableCallback, <span class="number">1000</span>);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="built_in">clearInterval</span>(timer);</span><br><span class="line">  &#125;, [stableCallback]); <span class="comment">// 永远不会变，effect 只执行一次</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣ 记忆化事件处理器工厂</strong></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">List</span>(<span class="params">&#123; items &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// ❌ 每次渲染都创建 N 个新函数</span></span><br><span class="line">  <span class="keyword">return</span> items.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> handleItemClick(item.id)&#125;&gt;&#123;item.name&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line">  ));</span><br><span class="line"></span><br><span class="line">  <span class="comment">// ✅ 使用 useCallback 缓存每个处理器（配合 memo）</span></span><br><span class="line">  <span class="keyword">return</span> items.<span class="title function_">map</span>(<span class="function">(<span class="params">item</span>) =&gt;</span> (</span><br><span class="line">    <span class="language-xml"><span class="tag">&lt;<span class="name">MemoItem</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">item</span>=<span class="string">&#123;item&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">      <span class="attr">onClick</span>=<span class="string">&#123;useCallback(</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        () =&gt;</span> handleItemClick(item.id),</span></span><br><span class="line"><span class="language-xml">        [item.id], // 只有当 item.id 变化时才更新</span></span><br><span class="line"><span class="language-xml">      )&#125;</span></span><br><span class="line"><span class="language-xml">    /&gt;</span></span><br><span class="line">  ));</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣依赖注入模式</strong></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useApi</span>(<span class="params">api</span>) &#123;</span><br><span class="line">  <span class="comment">// 即使 api 对象变化，只要 endpoint 不变，fetchData 引用稳定</span></span><br><span class="line">  <span class="keyword">const</span> fetchData = <span class="title function_">useCallback</span>(</span><br><span class="line">    <span class="function">(<span class="params">endpoint</span>) =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> api.<span class="title function_">request</span>(endpoint);</span><br><span class="line">    &#125;,</span><br><span class="line">    [api],</span><br><span class="line">  ); <span class="comment">// api 通常是稳定的单例</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> fetchData;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="6）常见误区⚠️"><a href="#6）常见误区⚠️" class="headerlink" title="6）常见误区⚠️"></a><strong><font color='#10c300'>6）常见误区⚠️</font></strong></h4><ol><li><p><strong>不要滥用</strong><br>如果子组件没有 <code>React.memo</code> 或不是性能瓶颈，没必要加 <code>useCallback</code>（增加复杂度）</p></li><li><p><strong>缓存不是减少渲染次数的万能钥匙</strong><br><code>useCallback</code> 只防止不必要的重渲染，但状态更新触发的渲染仍会发生</p></li><li><p><strong>依赖不正确会导致逻辑错误</strong><br>缺少依赖可能让函数内部拿到旧的状态</p></li><li><p>在写代码时，<strong>默认不要加 <code>useCallback</code></strong>。只有当你遇到下面这两个信号时，再补上：</p><ul><li>👋 “我要把这个函数传给一个很重的、加了 memo 的列表子组件”。</li><li>“ESLint 警告我说，这个函数被用这了 useEffect 的依赖里”。</li></ul><p>除此之外，放心大胆地写普通函数，代码更干净，性能反而更好。</p></li></ol><br><h3 id="4-6-useReducer-复杂状态管理"><a href="#4-6-useReducer-复杂状态管理" class="headerlink" title="4.6 useReducer-复杂状态管理"></a><strong><font color='red'>4.6 useReducer-复杂状态管理</font></strong></h3><blockquote><p>✅ <code>useReducer</code> 是 <code>useState</code> 的高级替代方案。<br>当状态变化逻辑比较复杂，或者新状态依赖旧状态时，用 <code>useReducer</code> 会更晰。</p></blockquote><p>它和 Redux 的核心思想一样：<br><strong>通过 “动作（action）” 和 “状态更新函数（reducer）” 来控制状态变化。</strong></p><hr><h4 id="1）基本语法-4"><a href="#1）基本语法-4" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, initialArg, init?)</span><br></pre></td></tr></table></figure><ul><li><strong><code>state</code></strong>：当前状态</li><li><strong><code>dispatch</code></strong>：触发状态更新的函数</li><li><strong><code>reducer</code></strong>：一个函数，负责“接收旧状态 + 动作（action）”，返回新状态</li><li><strong><code>initialArg</code></strong>：初始状态</li><li><strong><code>init（可选）</code></strong>：<em>惰性初始化函数</em>，用于在初始渲染时对 <code>initialArg</code> 进行加工或计算，返回真正的初始状态，只会在初始化执行一次</li></ul><h4 id="2）使用场景示例-1"><a href="#2）使用场景示例-1" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><p><strong><font color='#00A6ED'>1️⃣ 计数器</font></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useReducer &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 定义 reducer</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;increment&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;decrement&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">count</span>: state.<span class="property">count</span> - <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state; <span class="comment">// 返回原状态（防止报错）</span></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">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, &#123; <span class="attr">count</span>: <span class="number">0</span> &#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">p</span>&gt;</span>计数：&#123;state.count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;increment&quot; &#125;)&#125;&gt;+1<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;decrement&quot; &#125;)&#125;&gt;-1<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Counter</span>;</span><br></pre></td></tr></table></figure><p>✅ <code>dispatch</code> 类似于调用 <code>setState</code>，但语义更清晰、逻辑集中。</p><hr><p><strong><font color='#00A6ED'>2️⃣ 复杂状态示例：表单管理</font></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useReducer &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> initialForm = &#123;</span><br><span class="line">  <span class="attr">username</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  <span class="attr">age</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">formReducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;CHANGE_FIELD&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, [action.<span class="property">field</span>]: action.<span class="property">value</span> &#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;RESET&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> initialForm;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state;</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">function</span> <span class="title function_">Form</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [form, dispatch] = <span class="title function_">useReducer</span>(formReducer, initialForm);</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;form.username&#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;(e)</span> =&gt;</span></span></span><br><span class="line"><span class="language-xml">          dispatch(&#123;</span></span><br><span class="line"><span class="language-xml">            type: &quot;CHANGE_FIELD&quot;,</span></span><br><span class="line"><span class="language-xml">            field: &quot;username&quot;,</span></span><br><span class="line"><span class="language-xml">            value: e.target.value,</span></span><br><span class="line"><span class="language-xml">          &#125;)</span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        placeholder=&quot;用户名&quot;</span></span><br><span class="line"><span class="language-xml">      /&gt;</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;form.age&#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;(e)</span> =&gt;</span></span></span><br><span class="line"><span class="language-xml">          dispatch(&#123;</span></span><br><span class="line"><span class="language-xml">            type: &quot;CHANGE_FIELD&quot;,</span></span><br><span class="line"><span class="language-xml">            field: &quot;age&quot;,</span></span><br><span class="line"><span class="language-xml">            value: e.target.value,</span></span><br><span class="line"><span class="language-xml">          &#125;)</span></span><br><span class="line"><span class="language-xml">        &#125;</span></span><br><span class="line"><span class="language-xml">        placeholder=&quot;年龄&quot;</span></span><br><span class="line"><span class="language-xml">      /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;RESET&quot; &#125;)&#125;&gt;重置<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;JSON.stringify(form)&#125;<span class="tag">&lt;/<span class="name">p</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Form</span>;</span><br></pre></td></tr></table></figure><p>✅ 优势：</p><ul><li>所有状态更新逻辑集中在 <code>reducer</code>；</li><li>更容易维护、测试、调试。</li></ul><h4 id="3）useReducer-的执行流程图"><a href="#3）useReducer-的执行流程图" class="headerlink" title="3）useReducer 的执行流程图"></a><strong><font color='#10c300'>3）useReducer 的执行流程图</font></strong></h4><p>1️⃣ 组件渲染时：</p><ul><li>React 按 <code>initialArg</code> 初始化状态。</li></ul><p>2️⃣ 调用 <code>dispatch(action)</code>：</p><ul><li>React 会执行 <code>reducer(state, action)</code>；</li><li>得到新的 state；</li><li>重新渲染组件。</li></ul><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"><span class="title function_">dispatch</span>(action)</span><br><span class="line">   ↓</span><br><span class="line"><span class="title function_">reducer</span>(state, action)</span><br><span class="line">   ↓</span><br><span class="line">newState → 触发重新渲染</span><br></pre></td></tr></table></figure><h4 id="4）useState-vs-useReducer-选择指南"><a href="#4）useState-vs-useReducer-选择指南" class="headerlink" title="4）useState vs useReducer 选择指南"></a><strong><font color='#10c300'>4）useState vs useReducer 选择指南</font></strong></h4><table><thead><tr><th align="left">场景</th><th align="left">useState</th><th align="left">useReducer</th></tr></thead><tbody><tr><td align="left">状态类型</td><td align="left">简单值（string, number, boolean）</td><td align="left">复杂对象（含多个字段）</td></tr><tr><td align="left">更新逻辑</td><td align="left">直接设置新值</td><td align="left">基于动作（action）计算新状态</td></tr><tr><td align="left">状态关联</td><td align="left">独立状态</td><td align="left">多状态相互依赖（如表单校验影响提交按钮）</td></tr><tr><td align="left">状态转换</td><td align="left">少（&lt; 3 种变化）</td><td align="left">多（增删改查、加载、错误处理）</td></tr><tr><td align="left">可测试性</td><td align="left">一般</td><td align="left">高（reducer 是纯函数）</td></tr><tr><td align="left">团队协作</td><td align="left">快速开发</td><td align="left">大型项目易维护</td></tr></tbody></table><p><strong>转换信号</strong>：当 <code>useState</code> 出现多个 <code>setXxx</code> 连续调用，或状态逻辑超过 5 行时，考虑改用 <code>useReducer</code>。</p><h4 id="5）使用惰性初始化（第三个参数）"><a href="#5）使用惰性初始化（第三个参数）" class="headerlink" title="5）使用惰性初始化（第三个参数）"></a><strong><font color='#10c300'>5）使用惰性初始化（第三个参数）</font></strong></h4><p><code>useReducer</code> 还支持传入一个函数，延迟计算初始状态（避免初始化开销）👇</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useReducer &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">init</span>(<span class="params">initialCount</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> &#123; <span class="attr">count</span>: initialCount &#125;;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;reset&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> <span class="title function_">init</span>(action.<span class="property">payload</span>);</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;increment&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; <span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state;</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">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, <span class="number">0</span>, init);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>计数: &#123;state.count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;increment&quot; &#125;)&#125;&gt;+<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;reset&quot;, payload: 5 &#125;)&#125;&gt;</span></span><br><span class="line"><span class="language-xml">        重置为 5</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 class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Counter</span>;</span><br></pre></td></tr></table></figure><p>这种方式适用于初始状态计算非常复杂的情况。</p><h4 id="6）useReducer-useContext"><a href="#6）useReducer-useContext" class="headerlink" title="6）useReducer + useContext"></a><strong><font color='#10c300'>6）useReducer + useContext</font></strong></h4><p><strong>1️⃣全局状态（Redux 思想）</strong></p><p><code>useReducer</code> 常配合 <code>useContext</code> 使用，构建轻量“全局状态管理”：</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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; createContext, useReducer, useContext &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CounterContext</span> = <span class="title function_">createContext</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params">state, action</span>) &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;add&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> state + <span class="number">1</span>;</span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state;</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">function</span> <span class="title function_">CounterProvider</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, dispatch] = <span class="title function_">useReducer</span>(reducer, <span class="number">0</span>);</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">CounterContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;&#123;</span> <span class="attr">count</span>, <span class="attr">dispatch</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">CounterContext.Provider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; count, dispatch &#125; = <span class="title function_">useContext</span>(<span class="title class_">CounterContext</span>);</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">p</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> dispatch(&#123; type: &quot;add&quot; &#125;)&#125;&gt;+1<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="keyword">function</span> <span class="title function_">App</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">CounterProvider</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">CounterProvider</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>✅ 相当于一个小型 Redux。</p><hr><p><strong>2️⃣主题切换</strong></p><p>用 <strong><code>useReducer</code> + <code>useContext</code></strong> 实现一个<strong>全局状态管理系统</strong>，就像一个轻量版 Redux。</p><p>我们以“主题切换（深色 &#x2F; 浅色）”为例 👇</p><p><strong><font color='cornflowerblue'>🎯 功能目标</font></strong></p><ul><li>页面上有多个组件；</li><li>这些组件都能感知当前主题；</li><li>点击按钮可以在浅色&#x2F;深色模式之间切换；</li><li>所有组件自动更新，<strong>不用手动传 props</strong>。</li></ul><p><strong><font color='cornflowerblue'>🧱项目结构</font></strong></p><figure class="highlight plaintext"><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">├── 📁 components/                   # 组件</span><br><span class="line">│   ├── 📄 Header.jsx               # 子组件，读取主题并展示</span><br><span class="line">│   ├── 📄 Content.jsx              # 子组件，读取主题并展示</span><br><span class="line">├── 📁 context/                      # Context</span><br><span class="line">│   ├── 📄 index.js                 # 创建 Context + Reducer</span><br><span class="line">└── 📄 App.jsx</span><br></pre></td></tr></table></figure><p>完整案例：<a href="https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#S3pEZ">https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#S3pEZ</a></p><ul><li>页面加载时默认“浅色”；</li><li>点击“切换主题”按钮；</li><li><code>dispatch</code> 触发 <code>TOGGLE_THEME</code>；</li><li><code>reducer</code> 更新 theme → 所有使用该状态的组件自动重新渲染；</li><li>所有组件同步变成“深色模式”。</li></ul><h4 id="7）TypeScript-最佳实践"><a href="#7）TypeScript-最佳实践" class="headerlink" title="7）TypeScript 最佳实践"></a><strong><font color='#10c300'>7）TypeScript 最佳实践</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1. 定义 State 和 Action 类型</span></span><br><span class="line"><span class="keyword">interface</span> <span class="title class_">State</span> &#123;</span><br><span class="line">  <span class="attr">count</span>: <span class="built_in">number</span>;</span><br><span class="line">  <span class="attr">error</span>: <span class="built_in">string</span> | <span class="literal">null</span>;</span><br><span class="line">  <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span> | <span class="string">&quot;loading&quot;</span> | <span class="string">&quot;success&quot;</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">type</span> <span class="title class_">Action</span> =</span><br><span class="line">  | &#123; <span class="attr">type</span>: <span class="string">&quot;increment&quot;</span> &#125;</span><br><span class="line">  | &#123; <span class="attr">type</span>: <span class="string">&quot;decrement&quot;</span> &#125;</span><br><span class="line">  | &#123; <span class="attr">type</span>: <span class="string">&quot;reset&quot;</span>; <span class="attr">payload</span>: <span class="built_in">number</span> &#125;</span><br><span class="line">  | &#123; <span class="attr">type</span>: <span class="string">&quot;setError&quot;</span>; <span class="attr">error</span>: <span class="built_in">string</span> &#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 2. Reducer 类型推断</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">reducer</span>(<span class="params"><span class="attr">state</span>: <span class="title class_">State</span>, <span class="attr">action</span>: <span class="title class_">Action</span></span>): <span class="title class_">State</span> &#123;</span><br><span class="line">  <span class="keyword">switch</span> (action.<span class="property">type</span>) &#123;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;increment&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">count</span>: state.<span class="property">count</span> + <span class="number">1</span> &#125;;</span><br><span class="line">    <span class="keyword">case</span> <span class="string">&quot;reset&quot;</span>:</span><br><span class="line">      <span class="keyword">return</span> &#123; ...state, <span class="attr">count</span>: action.<span class="property">payload</span>, <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span> &#125;;</span><br><span class="line">    <span class="comment">// TypeScript 会检查是否处理了所有 action type</span></span><br><span class="line">    <span class="attr">default</span>:</span><br><span class="line">      <span class="keyword">return</span> state;</span><br><span class="line">  &#125;</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">function</span> <span class="title function_">Counter</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, dispatch] = <span class="title function_">useReducer</span>(reducer, &#123;</span><br><span class="line">    <span class="attr">count</span>: <span class="number">0</span>,</span><br><span class="line">    <span class="attr">error</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">status</span>: <span class="string">&quot;idle&quot;</span>,</span><br><span class="line">  &#125;);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// dispatch 类型安全，错误的 action 会报错</span></span><br><span class="line">  <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&quot;increment&quot;</span> &#125;); <span class="comment">// ✅</span></span><br><span class="line">  <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&quot;reset&quot;</span>, <span class="attr">payload</span>: <span class="number">10</span> &#125;); <span class="comment">// ✅</span></span><br><span class="line">  <span class="title function_">dispatch</span>(&#123; <span class="attr">type</span>: <span class="string">&quot;unknown&quot;</span> &#125;); <span class="comment">// ❌ TypeScript 错误</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><p>🌟 <strong>一句话总结：</strong></p><p><code>useReducer</code> &#x3D; “复杂版 useState”<br>当状态逻辑复杂或多步骤更新时，用 reducer 管理更干净、更可靠。</p><br><h3 id="4-7-useRef-引用-DOM-和保存可变值"><a href="#4-7-useRef-引用-DOM-和保存可变值" class="headerlink" title="4.7 useRef - 引用 DOM 和保存可变值"></a><strong><font color='red'>4.7 useRef - 引用 DOM 和保存可变值</font></strong></h3><p>✅ <code>useRef</code> 返回一个 <strong>可变的 ref 对象</strong> <code>&#123; current: ... &#125;</code><br>这个对象的 <code>.current</code> 属性可以存储任何值（DOM 元素、定时器 ID、普通变量等），<strong>改变 <code>.current</code> 不会触发组件重渲染</strong>。</p><p>它的两个主要用途：</p><ol><li><strong>访问 DOM 元素</strong>（函数组件中替代 <code>document.querySelector</code>）</li><li><strong>保存任意变量值</strong>（这个值在组件的整个生命周期内保持不变，不会因重新渲染而丢失）</li></ol><hr><h4 id="1）基本语法-5"><a href="#1）基本语法-5" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> refContainer = <span class="title function_">useRef</span>(initialValue);</span><br></pre></td></tr></table></figure><ul><li><code>initialValue</code> 是初始值</li><li><code>refContainer.current</code> 是存放值的地方</li><li>改变 <code>refContainer.current</code> <strong>不会触发组件重新渲染</strong></li></ul><h4 id="2）使用场景示例-2"><a href="#2）使用场景示例-2" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><p><strong><font color='#00A6ED'>1️⃣ 操作 DOM 元素</font></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">InputFocus</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>); <span class="comment">// 初始 current = null</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">focusInput</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    inputRef.<span class="property">current</span>.<span class="title function_">focus</span>(); <span class="comment">// 获取 DOM 节点并聚焦</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">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">ref</span>=<span class="string">&#123;inputRef&#125;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;点击按钮聚焦我&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;focusInput&#125;</span>&gt;</span>聚焦输入框<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">InputFocus</span>;</span><br></pre></td></tr></table></figure><ul><li><code>ref=&#123;inputRef&#125;</code> 将 DOM 节点绑定到 <code>inputRef.current</code></li><li>点击按钮时执行 <code>inputRef.current.focus()</code> 来让输入框获得焦点</li></ul><hr><p><strong><font color='#00A6ED'>2️⃣ 用来存储可变值</font></strong></p><p><code>useRef</code> 可以用来存储任意可变值，而且即使组件重新渲染，这个值仍然保留。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useRef, useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">RenderCount</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="keyword">const</span> renderTimes = <span class="title function_">useRef</span>(<span class="number">0</span>);</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">    renderTimes.<span class="property">current</span> += <span class="number">1</span>; <span class="comment">// 每次渲染 +1</span></span><br><span class="line">  &#125;);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;组件渲染&quot;</span>);</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">p</span>&gt;</span>按钮点击次数：&#123;count&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>组件渲染次数：&#123;renderTimes.current&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;/* 这里点击调用了useState，导致页面重新渲染，但是useRef一直处于叠加状态，没有初始到0 说明具有持久性 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setCount(count + 1)&#125;&gt;点击<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">RenderCount</span>;</span><br></pre></td></tr></table></figure><ul><li><code>renderTimes.current</code> 是一个持久化引用，组件每次渲染都会累加，但不会引起额外的渲染</li><li>对 <code>useRef</code> 变量的修改不会触发 UI 更新</li></ul><h4 id="3）注意事项⚠️"><a href="#3）注意事项⚠️" class="headerlink" title="3）注意事项⚠️"></a><strong><font color='#10c300'>3）注意事项⚠️</font></strong></h4><ol><li><p><strong>不要用它来代替 state</strong>，除非你不需要触发渲染。</p></li><li><p><code>.current</code> 修改不会更新 UI，只有 <code>state</code> 更新会触发渲染。</p></li><li><p>访问 DOM 节点要确保该节点已经渲染到页面上（通常在 <code>useEffect</code> 中使用）。</p></li><li><p>函数组件不能使用ref，因为函数组件没有实例（和 forwardRef 搭配可使用ref）</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./child&quot;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> domRef = <span class="title function_">useRef</span>();</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">      &#123;/* 错误写法，函数组件不能使用ref，因为函数组件没有实例 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;domRef&#125;</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 class="tag">&lt;/<span class="name">button</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><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure></li></ol><h4 id="4）和-forwardRef-搭配"><a href="#4）和-forwardRef-搭配" class="headerlink" title="4）和 forwardRef 搭配"></a><strong><font color='#10c300'>4）和 forwardRef 搭配</font></strong></h4><p><a name="forwardRef"><strong><code>useRef</code></strong> 配合 <strong><code>forwardRef</code></strong> 可以将 ref 传给子组件中的 DOM 元素：</a></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef, forwardRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">CustomInput</span> = <span class="title function_">forwardRef</span>(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>;</span><br><span class="line">&#125;);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> myInputRef = <span class="title function_">useRef</span>();</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">CustomInput</span> <span class="attr">ref</span>=<span class="string">&#123;myInputRef&#125;</span> <span class="attr">placeholder</span>=<span class="string">&quot;自定义组件中的输入框&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> myInputRef.current.focus()&#125;&gt;聚焦<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>✅ 这样可以让父组件直接操作子组件内部的 DOM 元素。</p><p><strong><font color='red'>🚫在react19中已废弃forwardRef </font></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ React 19 写法：直接从 props 中获取 ref</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">CustomInput</span>(<span class="params">&#123; placeholder, ref, ...props &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span> <span class="attr">placeholder</span>=<span class="string">&#123;placeholder&#125;</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> myInputRef = <span class="title function_">useRef</span>();</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">CustomInput</span> <span class="attr">ref</span>=<span class="string">&#123;myInputRef&#125;</span> <span class="attr">placeholder</span>=<span class="string">&quot;自定义组件中的输入框&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> myInputRef.current.focus()&#125;&gt;聚焦<span class="tag">&lt;/<span class="name">button</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><h4 id="5）TypeScript-支持"><a href="#5）TypeScript-支持" class="headerlink" title="5）TypeScript 支持"></a><strong><font color='#10c300'>5）TypeScript 支持</font></strong></h4><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// DOM 元素引用（自动推断类型）</span></span><br><span class="line"><span class="keyword">const</span> inputRef = useRef&lt;<span class="title class_">HTMLInputElement</span>&gt;(<span class="literal">null</span>);</span><br><span class="line">inputRef.<span class="property">current</span>?.<span class="title function_">focus</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 非 DOM 引用需要显式类型</span></span><br><span class="line"><span class="keyword">const</span> timerRef = useRef&lt;<span class="built_in">number</span> | <span class="literal">null</span>&gt;(<span class="literal">null</span>);</span><br><span class="line">timerRef.<span class="property">current</span> = <span class="variable language_">window</span>.<span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;&#125;, <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 确保有初始值（非 null）</span></span><br><span class="line"><span class="keyword">const</span> countRef = useRef&lt;<span class="built_in">number</span>&gt;(<span class="number">0</span>);</span><br><span class="line">countRef.<span class="property">current</span> += <span class="number">1</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// MutableRefObject vs RefObjectD</span></span><br><span class="line"><span class="comment">// useRef&lt;T&gt;(null) -&gt; RefObject&lt;T&gt;（current 只读，用于 DOM）</span></span><br><span class="line"><span class="comment">// useRef&lt;T&gt;(undefined) 或 useRef&lt;T&gt;(initial) -&gt; MutableRefObject&lt;T&gt;（current 可写）</span></span><br></pre></td></tr></table></figure><br><h3 id="4-8-useId-生成唯一-ID"><a href="#4-8-useId-生成唯一-ID" class="headerlink" title="4.8 useId - 生成唯一 ID"></a><strong><font color='red'>4.8 useId - 生成唯一 ID</font></strong></h3><p><code>useId</code> 是一个 React 内置 Hook，用来生成一个稳定且唯一的 ID 字符串，通常用于：</p><ul><li><strong>无障碍（a11y）场景</strong>：让 <code>label</code> 和表单控件配对使用；</li><li><strong>服务端渲染（SSR）</strong>：防止客户端与服务器渲染的 ID 不一致；</li><li><strong>生成稳定唯一 key&#x2F;id</strong>（每次渲染保持不变）。</li></ul><hr><h4 id="1）基本语法-6"><a href="#1）基本语法-6" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> id = <span class="title function_">useId</span>();</span><br></pre></td></tr></table></figure><ul><li>返回一个在当前组件作用域内 <strong>唯一且稳定</strong> 的字符串，例如：<code>&quot;r1:0&quot;</code>。</li><li>每次渲染都保证相同组件中的 id 一致。</li><li>不会在不同组件之间重复。</li></ul><h4 id="2）使用场景示例-3"><a href="#2）使用场景示例-3" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><p><strong><font color='#00A6ED'>1️⃣ 关联 <code>&lt;label&gt;</code> 与 <code>&lt;input&gt;</code></font></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><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">jsximport <span class="title class_">React</span>, &#123; useId &#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_">NameField</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> id = <span class="title function_">useId</span>();</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">label</span> <span class="attr">htmlFor</span>=<span class="string">&#123;id&#125;</span>&gt;</span>姓名：<span class="tag">&lt;/<span class="name">label</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">id</span>=<span class="string">&#123;id&#125;</span> <span class="attr">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">placeholder</span>=<span class="string">&quot;请输入姓名&quot;</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">NameField</span>;</span><br></pre></td></tr></table></figure><p>✅ 在这里：</p><ul><li><code>useId</code> 生成一个唯一 id；</li><li><code>label</code> 的 <code>htmlFor</code> 与 <code>input</code> 的 <code>id</code> 一致；</li><li>当多个 <code>NameField</code> 组件同时存在时，每个组件生成的 id 不会冲突。</li></ul><hr><p><strong><font color='#00A6ED'>2️⃣ 组合前缀使用（推荐）</font></strong></p><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></pre></td><td class="code"><pre><span class="line">jsxconst id = <span class="title function_">useId</span>();</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">id</span>=<span class="string">&#123;</span>`<span class="attr">email-</span>$&#123;<span class="attr">id</span>&#125;`&#125; /&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">label</span> <span class="attr">htmlFor</span>=<span class="string">&#123;</span>`<span class="attr">email-</span>$&#123;<span class="attr">id</span>&#125;`&#125;&gt;</span>邮箱<span class="tag">&lt;/<span class="name">label</span>&gt;</span></span></span><br></pre></td></tr></table></figure><p>结果类比：<code>id=&quot;email-r1:0&quot;</code>, <code>email-r1:1</code> 等。</p><h4 id="3）与-SSR-配合"><a href="#3）与-SSR-配合" class="headerlink" title="3）与 SSR 配合"></a><strong><font color='#10c300'>3）与 SSR 配合</font></strong></h4><p>React 18 引入了 <code>useId</code>，是为了解决 <strong>服务端渲染（Server Side Rendering）</strong> 时生成的 ID 不一致问题：</p><ul><li>SSR 阶段会生成稳定的 ID。</li><li>Hydration（客户端激活）时，React 会保证客户端生成的 id 与服务端的一致。</li></ul><p>✅ 所以 <code>useId</code> 是 <strong>SSR 安全的唯一 ID</strong>。</p><h4 id="4）注意事项⚠️"><a href="#4）注意事项⚠️" class="headerlink" title="4）注意事项⚠️"></a><strong><font color='#10c300'>4）注意事项⚠️</font></strong></h4><ol><li><code>useId</code> <strong>不适合当作列表 key 的唯一标识</strong>，因为它只在组件作用域独立唯一，不是数据层唯一。</li><li>每次调用 <code>useId</code> 都会生成一个不同的子 ID，React 内部有机制确保组合时不冲突。</li><li>只可在 <strong>组件初始化阶段调用一次</strong>，即让 <code>id</code> 在整个组件生命周期内稳定。</li></ol><br><h3 id="4-9-useDeferredValue-延迟更新值"><a href="#4-9-useDeferredValue-延迟更新值" class="headerlink" title="4.9 useDeferredValue - 延迟更新值"></a><strong><font color='red'>4.9 useDeferredValue - 延迟更新值</font></strong></h3><p>当你更新某个状态时，React 不一定立刻同步渲染使用这个值的部分，而是会<strong>优先渲染更紧急的更新（交互、输入）</strong>，稍后再更新那些耗时的渲染部分。</p><p>✅ 简单理解：让页面中不太重要的部分“晚一点更新”，以保证用户操作更流畅。</p><hr><h4 id="1）基本语法-7"><a href="#1）基本语法-7" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> deferredValue = <span class="title function_">useDeferredValue</span>(value);</span><br></pre></td></tr></table></figure><ul><li><code>value</code>：原始的值（可能变化频繁）</li><li><code>deferredValue</code>：React 返回的延迟更新的值</li></ul><p>当 <code>value</code> 改变时，React <strong>不会立即</strong>让 <code>deferredValue</code> 同步，而是“稍后”更新它（基于调度优先级），如果更新非常快，比如频繁输入，<code>deferredValue</code> 会滞后一点跟上。</p><h4 id="2）使用场景示例-4"><a href="#2）使用场景示例-4" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><p><strong><font color='#00A6ED'>1️⃣ 搜索过滤（大列表）</font></strong></p><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><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="keyword">import</span> &#123; useState, useDeferredValue, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SlowList</span>(<span class="params">&#123; input &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// 模拟大数据过滤（耗时） 要配合useMemo使用，要不然子组件每次渲染，也会导致输入卡顿</span></span><br><span class="line">  <span class="keyword">const</span> list = <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> items = [];</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">10000</span>; i++) &#123;</span><br><span class="line">      items.<span class="title function_">push</span>(</span><br><span class="line">        <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">key</span>=<span class="string">&#123;i&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;input&#125; - 项 &#123;i&#125;</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><span class="line">    <span class="keyword">return</span> items;</span><br><span class="line">  &#125;, [input]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;list&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SearchPage</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [query, setQuery] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> deferredQuery = <span class="title function_">useDeferredValue</span>(query); <span class="comment">// 👈 延迟使用 query</span></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;query&#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;(e)</span> =&gt;</span> setQuery(e.target.value)&#125;</span></span><br><span class="line"><span class="language-xml">        placeholder=&quot;输入搜索关键词...&quot;</span></span><br><span class="line"><span class="language-xml">      /&gt;</span></span><br><span class="line"><span class="language-xml">      &#123;/* 使用延迟的状态值渲染大列表 */&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">SlowList</span> <span class="attr">input</span>=<span class="string">&#123;deferredQuery&#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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">SearchPage</span>;</span><br></pre></td></tr></table></figure><p><strong>🔍 效果：</strong></p><ul><li>输入框响应非常顺畅；</li><li>列表渲染稍微滞后一点更新（不会阻塞输入）；</li><li>React 自动安排较低优先级的渲染任务。</li></ul><h4 id="3）useDeferredValue的本质"><a href="#3）useDeferredValue的本质" class="headerlink" title="3）useDeferredValue的本质"></a><strong><font color='#10c300'>3）useDeferredValue的本质</font></strong></h4><p>它利用了 <strong>React 的并发特性（Concurrent Rendering）</strong><br>让某些更新以较低优先级进行：</p><ul><li>用户输入 → 高优先级；</li><li>列表更新 → 低优先级；</li><li>React 内部可在合适时机处理低优先级更新。</li></ul><p>所以它非常适合：</p><ul><li>输入搜索时展示结果；</li><li>大量数据过滤；</li><li>复杂渲染场景中保持 UI 互动流畅。</li></ul><h4 id="4）注意事项⚠️-1"><a href="#4）注意事项⚠️-1" class="headerlink" title="4）注意事项⚠️"></a><strong><font color='#10c300'>4）注意事项⚠️</font></strong></h4><table><thead><tr><th>注意点</th><th>说明</th></tr></thead><tbody><tr><td>不会跳过更新</td><td>只是延迟执行，最终仍会同步到最新值</td></tr><tr><td>不建议用于关键 UI 状态</td><td>因为它可能暂时落后于真实值</td></tr><tr><td>需要 React 18+</td><td>属于并发系统的功能</td></tr><tr><td>可搭配 <code>useTransition</code> 一起使用</td><td>共同控制更新优先级更加灵活</td></tr></tbody></table><br><h3 id="4-10-useTransition-非阻塞状态更新"><a href="#4-10-useTransition-非阻塞状态更新" class="headerlink" title="4.10 useTransition - 非阻塞状态更新"></a><strong><font color='red'>4.10 useTransition - 非阻塞状态更新</font></strong></h3><p>用来将某些状态更新标记为“过渡更新（transition update）”。</p><p>简单来说，它告诉 React：</p><blockquote><p>“这类更新不用立刻执行，可以稍后完成，让用户交互不要被卡顿。”</p></blockquote><p>在 React 18 的 <strong>并发渲染模式</strong> 下，更新有优先级区分：</p><ul><li><strong>紧急更新（Urgent）</strong>：立刻执行，比如输入框、点击。</li><li><strong>过渡更新（Transition）</strong>：可以延后，比如筛选、分页、排序、大列表渲染。</li></ul><h4 id="1）基本语法搜索过滤"><a href="#1）基本语法搜索过滤" class="headerlink" title="1）基本语法搜索过滤"></a><strong><font color='#10c300'>1）基本语法搜索过滤</font></strong></h4><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> [isPending, startTransition] = <span class="title function_">useTransition</span>();</span><br></pre></td></tr></table></figure><ul><li><p><code>isPending</code> ：布尔值：表示过渡更新是否正在进行中，可用于显示“加载中…”</p></li><li><p><code>startTransition()</code>：函数：用于包裹属于过渡更新的代码（例如 setState）</p></li></ul><h4 id="2）使用场景示例-5"><a href="#2）使用场景示例-5" class="headerlink" title="2）使用场景示例"></a><strong><font color='#10c300'>2）使用场景示例</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useTransition &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> allItems = <span class="title class_">Array</span>.<span class="title function_">from</span>(&#123; <span class="attr">length</span>: <span class="number">20000</span> &#125;, <span class="function">(<span class="params">_, i</span>) =&gt;</span> <span class="string">`Item <span class="subst">$&#123;i + <span class="number">1</span>&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">List</span>(<span class="params">&#123; list &#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">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;list.map((item) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;item&#125;</span>&gt;</span>&#123;item&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [query, setQuery] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> [list, setList] = <span class="title function_">useState</span>(allItems);</span><br><span class="line">  <span class="keyword">const</span> [isPending, startTransition] = <span class="title function_">useTransition</span>(); <span class="comment">// 👈 使用 useTransition</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> newQuery = e.<span class="property">target</span>.<span class="property">value</span>;</span><br><span class="line">    <span class="title function_">setQuery</span>(newQuery); <span class="comment">// 紧急更新（立即响应输入）</span></span><br><span class="line"></span><br><span class="line">    <span class="comment">// 过滤操作标记为“过渡更新”，不会阻塞输入</span></span><br><span class="line">    <span class="title function_">startTransition</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="keyword">const</span> filtered = allItems.<span class="title function_">filter</span>(<span class="function">(<span class="params">item</span>) =&gt;</span></span><br><span class="line">        item.<span class="title function_">toLowerCase</span>().<span class="title function_">includes</span>(newQuery.<span class="title function_">toLowerCase</span>()),</span><br><span class="line">      );</span><br><span class="line">      <span class="title function_">setList</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> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">padding:</span> <span class="attr">20</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span>&gt;</span>useTransition 优化的搜索示例<span class="tag">&lt;/<span class="name">h2</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;query&#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">placeholder</span>=<span class="string">&quot;输入关键字过滤列表…&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">      &#123;isPending &amp;&amp; <span class="tag">&lt;<span class="name">span</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">color:</span> &quot;<span class="attr">orange</span>&quot; &#125;&#125;&gt;</span>正在过滤数据…<span class="tag">&lt;/<span class="name">span</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>匹配到 &#123;list.length&#125; 条结果<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">List</span> <span class="attr">list</span>=<span class="string">&#123;list&#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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p>✅ 体验：<br>输入框输入即时响应；<br>列表过滤是延后执行的，界面不会卡顿。</p><h4 id="3）工作原理"><a href="#3）工作原理" class="headerlink" title="3）工作原理"></a><strong><font color='#10c300'>3）工作原理</font></strong></h4><figure class="highlight plaintext"><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><br><span class="line">        ↓</span><br><span class="line">React 空闲时 → 执行过滤逻辑（过渡更新）</span><br><span class="line">        ↓</span><br><span class="line">过渡完成 → isPending = false，列表更新</span><br></pre></td></tr></table></figure><p>当 <code>startTransition</code> 内的更新在进行时：</p><ul><li>React 会优先处理交互事件（比如输入框），</li><li>暂缓其他较慢的渲染任务，</li><li>确保界面流畅。</li></ul><h4 id="4）与useDeferredValue的对比"><a href="#4）与useDeferredValue的对比" class="headerlink" title="4）与useDeferredValue的对比"></a><strong><font color='#10c300'>4）与useDeferredValue的对比</font></strong></h4><ul><li><strong><code>useDeferredValue</code></strong> 主要用于延迟单个值的更新，适用于值的变化直接影响到 UI 渲染但又不是立即必要的更新。</li><li><strong><code>useTransition</code></strong> 用于告诉 React 哪些更新是低优先级的，并可使用 <code>isPending</code> 状态反馈更新是否处于等待状态，适用于控制大块区域或复杂状态的更新行为，允许你在触发更新时提供更自然的用户体验。</li></ul><table><thead><tr><th>对比项</th><th><code>useTransition</code></th><th><code>useDeferredValue</code></th></tr></thead><tbody><tr><td>控制对象</td><td>一整段状态更新（<code>setState</code>）</td><td>一个状态值</td></tr><tr><td>延迟方式</td><td>手动包裹更新</td><td>自动使值延迟生效</td></tr><tr><td>返回值</td><td><code>[isPending, startTransition]</code></td><td><code>deferredValue</code></td></tr><tr><td>是否有加载状态</td><td>✅ 有 <code>isPending</code></td><td>❌ 需手动比较</td></tr><tr><td>使用场景</td><td>你要延迟执行某个更新逻辑</td><td>你要延迟某个值传递下去</td></tr></tbody></table><h4 id="5）注意事项⚠️"><a href="#5）注意事项⚠️" class="headerlink" title="5）注意事项⚠️"></a><strong><font color='#10c300'>5）注意事项⚠️</font></strong></h4><ul><li><code>useTransition</code> 只在 React 18+ 有效果；</li><li>它不会跳过渲染，只是调度顺序不同；</li><li>不适用于动画或时间延迟，只改变更新优先级；</li><li>不要滥用——仅在性能瓶颈或卡顿时使用。</li></ul><br><h3 id="4-11-useImperativeHandle-暴露自定义ref"><a href="#4-11-useImperativeHandle-暴露自定义ref" class="headerlink" title="4.11 useImperativeHandle - 暴露自定义ref"></a><strong><font color='red'>4.11 useImperativeHandle - 暴露自定义ref</font></strong></h3><p><code>useImperativeHandle</code> 用于自定义通过 ref 暴露给父组件的实例值。它通常与 <code>forwardRef</code> 配合使用（React 19 后 ref 可作为 prop 直接传递）。<a href="#forwardRef">forwardRef用法</a></p><hr><h4 id="1）基本语法-8"><a href="#1）基本语法-8" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">useImperativeHandle</span>(ref, createHandle, deps?)</span><br></pre></td></tr></table></figure><ul><li><strong>ref</strong>：从 <code>forwardRef</code> 或 props 传入的 ref</li><li><strong>createHandle</strong>：返回暴露给父组件的对象</li><li><strong>deps</strong>：依赖数组，类似 <code>useEffect</code></li></ul><h4 id="2）React-19-之前的写法（forwardRef）"><a href="#2）React-19-之前的写法（forwardRef）" class="headerlink" title="2）React 19 之前的写法（forwardRef）"></a><strong><font color='#10c300'>2）React 19 之前的写法（forwardRef）</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef, useImperativeHandle, forwardRef &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 子组件</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Child</span> = <span class="title function_">forwardRef</span>(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useImperativeHandle</span>(</span><br><span class="line">    ref,</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">focus</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        inputRef.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="attr">getValue</span>: <span class="function">() =&gt;</span> inputRef.<span class="property">current</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;),</span><br><span class="line">    [],</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputRef&#125;</span> <span class="attr">placeholder</span>=<span class="string">&quot;输入内容&quot;</span> /&gt;</span></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="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> childRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 调用子组件暴露的方法</span></span><br><span class="line">    childRef.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(childRef.<span class="property">current</span>.<span class="title function_">getValue</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;childRef&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span>操作子组件<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Parent</span>;</span><br></pre></td></tr></table></figure><h4 id="3）React-19-的简化写法"><a href="#3）React-19-的简化写法" class="headerlink" title="3）React 19 的简化写法"></a><strong><font color='#10c300'>3）React 19 的简化写法</font></strong></h4><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useRef, useImperativeHandle &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 子组件</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Child</span>(<span class="params">&#123; ref &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useImperativeHandle</span>(</span><br><span class="line">    ref,</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      <span class="comment">// 返回一个对象</span></span><br><span class="line">      <span class="attr">focus</span>: <span class="function">() =&gt;</span> inputRef.<span class="property">current</span>?.<span class="title function_">focus</span>(),</span><br><span class="line">      <span class="attr">clear</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (inputRef.<span class="property">current</span>) inputRef.<span class="property">current</span>.<span class="property">value</span> = <span class="string">&quot;&quot;</span>;</span><br><span class="line">      &#125;,</span><br><span class="line">    &#125;),</span><br><span class="line">    [],</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputRef&#125;</span> /&gt;</span></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="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> childRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="comment">// 调用子组件暴露的方法</span></span><br><span class="line">    childRef.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">    childRef.<span class="property">current</span>.<span class="title function_">clear</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;childRef&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span>操作子组件<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Parent</span>;</span><br></pre></td></tr></table></figure><h4 id="4）使用场景"><a href="#4）使用场景" class="headerlink" title="4）使用场景"></a><strong><font color='#10c300'>4）使用场景</font></strong></h4><p><strong>1️⃣ 封装第三方组件</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><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">const</span> <span class="title class_">FancyInput</span> = <span class="title function_">forwardRef</span>(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> inputRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useImperativeHandle</span>(ref, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="comment">// 只暴露特定的 API，隐藏内部实现</span></span><br><span class="line">    <span class="attr">focus</span>: <span class="function">() =&gt;</span> inputRef.<span class="property">current</span>.<span class="title function_">focus</span>(),</span><br><span class="line">    <span class="attr">scrollIntoView</span>: <span class="function">() =&gt;</span></span><br><span class="line">      inputRef.<span class="property">current</span>.<span class="title function_">scrollIntoView</span>(&#123; <span class="attr">behavior</span>: <span class="string">&quot;smooth&quot;</span> &#125;),</span><br><span class="line">  &#125;));</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">ref</span>=<span class="string">&#123;inputRef&#125;</span> &#123;<span class="attr">...props</span>&#125; /&gt;</span></span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣动画控制</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">AnimatedBox</span> = <span class="title function_">forwardRef</span>(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> elementRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useImperativeHandle</span>(ref, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">    <span class="attr">shake</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      elementRef.<span class="property">current</span>?.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;shake-animation&quot;</span>);</span><br><span class="line">      <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        elementRef.<span class="property">current</span>?.<span class="property">classList</span>.<span class="title function_">remove</span>(<span class="string">&quot;shake-animation&quot;</span>);</span><br><span class="line">      &#125;, <span class="number">500</span>);</span><br><span class="line">    &#125;,</span><br><span class="line">    <span class="attr">highlight</span>: <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      elementRef.<span class="property">current</span>?.<span class="property">classList</span>.<span class="title function_">add</span>(<span class="string">&quot;highlight&quot;</span>);</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 class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">ref</span>=<span class="string">&#123;elementRef&#125;</span>&gt;</span>&#123;props.children&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="5）注意事项⚠️-1"><a href="#5）注意事项⚠️-1" class="headerlink" title="5）注意事项⚠️"></a><strong><font color='#10c300'>5）注意事项⚠️</font></strong></h4><ol><li><p><strong>避免过度使用</strong>：优先通过 props 和 state 进行数据流通信，refs 是”逃生舱”</p></li><li><p><strong>返回值限制</strong>：<code>createHandle</code> 返回的对象中不能包含原始类型的 ref 值（如 <code>&#123; current: ... &#125;</code>），只能是普通函数或值</p></li><li><p><strong>TypeScript 支持</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><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></pre></td><td class="code"><pre><span class="line">interface <span class="title class_">ChildRef</span> &#123;</span><br><span class="line">    <span class="attr">focus</span>: <span class="function">() =&gt;</span> <span class="keyword">void</span>;</span><br><span class="line">    <span class="attr">clear</span>: <span class="function">() =&gt;</span> <span class="keyword">void</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Child</span> = forwardRef&lt;<span class="title class_">ChildRef</span>, <span class="title class_">Props</span>&gt;(<span class="function">(<span class="params">props, ref</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">useImperativeHandle</span>(ref, <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">        <span class="attr">focus</span>: <span class="function">() =&gt;</span> &#123;&#125;,</span><br><span class="line">        <span class="attr">clear</span>: <span class="function">() =&gt;</span> &#123;&#125;</span><br><span class="line">    &#125;));</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure></li><li><p><strong>与 <code>useRef</code> 配合</strong>：通常在子组件内部维护实际 DOM ref，再通过 <code>useImperativeHandle</code> 选择性暴露方法</p></li><li><p><strong>清理逻辑</strong>：如果暴露的方法涉及副作用（如定时器），记得在组件卸载时清理</p></li></ol><p>这个模式适用于需要<strong>命令式操作</strong>（如聚焦、播放、滚动、触发动画）但不想暴露整个 DOM 节点的场景。</p><br><h3 id="4-12-useLayoutEffect-同步执行的副作用"><a href="#4-12-useLayoutEffect-同步执行的副作用" class="headerlink" title="4.12 useLayoutEffect - 同步执行的副作用"></a><strong><font color='red'>4.12 useLayoutEffect - 同步执行的副作用</font></strong></h3><blockquote><p><code>useLayoutEffect</code> 可能会影响性能。尽可能使用 <a href="https://zh-hans.react.dev/reference/react/useEffect"><code>useEffect</code></a></p></blockquote><p><code>useLayoutEffect</code> 与 <code>useEffect</code> 签名完全相同，但执行时机不同：<strong>在浏览器绘制（paint）之前同步执行</strong>，用于避免视觉闪烁。</p><hr><h4 id="1）执行时机对比"><a href="#1）执行时机对比" class="headerlink" title="1）执行时机对比"></a><strong><font color='#10c300'>1）执行时机对比</font></strong></h4><figure class="highlight plaintext"><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><br><span class="line">    ↓</span><br><span class="line">useLayoutEffect（同步执行，阻塞绘制）← 在这里修改 DOM，用户看不到中间态</span><br><span class="line">    ↓</span><br><span class="line">浏览器绘制（Paint）到屏幕</span><br><span class="line">    ↓</span><br><span class="line">useEffect（异步执行，不阻塞绘制）</span><br></pre></td></tr></table></figure><h4 id="2）核心使用场景-1"><a href="#2）核心使用场景-1" class="headerlink" title="2）核心使用场景"></a><strong><font color='#10c300'>2）核心使用场景</font></strong></h4><p><strong>1️⃣测量 DOM 并同步修改（防止闪烁）</strong></p><p>**完整案例：**<a href="https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#fdd2408c">https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#fdd2408c</a></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useLayoutEffect, useRef, useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Tooltip</span>(<span class="params">&#123; children, content &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [position, setPosition] = <span class="title function_">useState</span>(&#123; <span class="attr">top</span>: <span class="number">0</span>, <span class="attr">left</span>: <span class="number">0</span> &#125;);</span><br><span class="line">  <span class="keyword">const</span> triggerRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line">  <span class="keyword">const</span> tooltipRef = <span class="title function_">useRef</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="title function_">useLayoutEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> triggerRect = triggerRef.<span class="property">current</span>.<span class="title function_">getBoundingClientRect</span>();</span><br><span class="line">    <span class="keyword">const</span> tooltipRect = tooltipRef.<span class="property">current</span>.<span class="title function_">getBoundingClientRect</span>();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 计算 tooltip 位置（例如显示在 trigger 上方）</span></span><br><span class="line">    <span class="keyword">const</span> top = triggerRect.<span class="property">top</span> - tooltipRect.<span class="property">height</span> - <span class="number">8</span>;</span><br><span class="line">    <span class="keyword">const</span> left = triggerRect.<span class="property">left</span> + (triggerRect.<span class="property">width</span> - tooltipRect.<span class="property">width</span>) / <span class="number">2</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 同步设置位置，用户不会看到 tooltip 先出现在错误位置</span></span><br><span class="line">    <span class="title function_">setPosition</span>(&#123; top, left &#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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">span</span> <span class="attr">ref</span>=<span class="string">&#123;triggerRef&#125;</span>&gt;</span>&#123;children&#125;<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">div</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">ref</span>=<span class="string">&#123;tooltipRef&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        <span class="attr">style</span>=<span class="string">&#123;&#123;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">position:</span> &quot;<span class="attr">fixed</span>&quot;,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">top:</span> <span class="attr">position.top</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">left:</span> <span class="attr">position.left</span>,</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">        &#125;&#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">        &#123;content&#125;</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;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>如果用 <code>useEffect</code> 会怎样？</strong></p><ul><li>浏览器先绘制 tooltip 在默认位置（例如 0,0）</li><li>用户看到一闪而过的错位</li><li>然后 <code>useEffect</code> 执行，跳转到正确位置</li></ul><hr><p><strong>2️⃣从服务端渲染恢复（SSR Hydration）</strong></p><p>当服务端渲染的 HTML 与客户端首次渲染不一致时，用 <code>useLayoutEffect</code> 在绘制前修正，避免 hydration 不匹配警告：</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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">ClientOnlyComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [width, setWidth] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 服务端没有 window，默认渲染为 0</span></span><br><span class="line">  <span class="comment">// 客户端在绘制前同步计算实际宽度，防止闪烁</span></span><br><span class="line">  <span class="title function_">useLayoutEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="title function_">setWidth</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>窗口宽度: &#123;width&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）与-useEffect-的选择指南"><a href="#3）与-useEffect-的选择指南" class="headerlink" title="3）与 useEffect 的选择指南"></a><strong><font color='#10c300'>3）与 useEffect 的选择指南</font></strong></h4><table><thead><tr><th align="left">场景</th><th align="left">推荐</th><th align="left">原因</th></tr></thead><tbody><tr><td align="left">数据获取</td><td align="left"><code>useEffect</code></td><td align="left">不阻塞绘制，用户更快看到内容</td></tr><tr><td align="left">事件监听</td><td align="left"><code>useEffect</code></td><td align="left">不阻塞绘制</td></tr><tr><td align="left">基于 DOM 测量调整布局</td><td align="left"><code>useLayoutEffect</code></td><td align="left">防止闪烁</td></tr><tr><td align="left">动画初始状态</td><td align="left"><code>useLayoutEffect</code></td><td align="left">防止闪烁</td></tr><tr><td align="left">DOM 修改（如聚焦）</td><td align="left"><code>useEffect</code></td><td align="left">通常不需要同步，除非有视觉问题</td></tr></tbody></table><p><strong>黄金法则</strong>：先使用 <code>useEffect</code>，如果出现<strong>视觉闪烁</strong>（flicker）再改为 <code>useLayoutEffect</code></p><h4 id="4）性能警告-⚠️"><a href="#4）性能警告-⚠️" class="headerlink" title="4）性能警告 ⚠️"></a><strong><font color='#10c300'>4）性能警告 ⚠️</font></strong></h4><p><code>useLayoutEffect</code> <strong>会阻塞浏览器绘制</strong>，执行时间过长会导致：</p><ol><li><p><strong>掉帧&#x2F;卡顿</strong>：用户感觉到界面卡住</p></li><li><p><strong>延迟交互</strong>：无法响应用户输入</p></li><li><p><strong>影响用户体验</strong>：比轻微的视觉闪烁更糟糕</p><figure class="highlight js"><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="comment">// ❌ 错误：在 useLayoutEffect 中执行耗时操作</span></span><br><span class="line"><span class="title function_">useLayoutEffect</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> processed = <span class="title function_">heavyDataProcessing</span>(data);</span><br><span class="line">  <span class="title function_">setData</span>(processed);</span><br><span class="line">&#125;, []);</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：耗时操作应在 useEffect 中</span></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> processed = <span class="title function_">heavyDataProcessing</span>(data);</span><br><span class="line">  <span class="title function_">setData</span>(processed);</span><br><span class="line">&#125;, []);</span><br></pre></td></tr></table></figure></li></ol><br><h3 id="4-13-自定义-Hooks"><a href="#4-13-自定义-Hooks" class="headerlink" title="4.13 自定义 Hooks"></a><strong><font color='red'>4.13 自定义 Hooks</font></strong></h3><p>自定义 Hook 是<strong>提取组件逻辑到可复用函数</strong>的机制，解决 React 中状态逻辑复用问题（替代高阶组件 HOC 和 Render Props）。</p><h4 id="1）核心规则"><a href="#1）核心规则" class="headerlink" title="1）核心规则"></a><strong><font color='#10c300'>1）核心规则</font></strong></h4><p><strong>1️⃣ 命名必须以 <code>use</code> 开头</strong></p><figure class="highlight js"><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">// ✅ 正确：React 识别为 Hook，会检查规则</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useWindowSize</span>(<span class="params"></span>) &#123; ... &#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ❌ 错误：普通函数，React 不会应用 Hook 规则</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">getWindowSize</span>(<span class="params"></span>) &#123; ... &#125;</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣遵循 Hooks 规则</strong></p><ul><li>只在顶层调用（不在循环、条件、嵌套函数中）</li><li>只在 React 函数或自定义 Hook 中调用</li></ul><h4 id="2）基础模板"><a href="#2）基础模板" class="headerlink" title="2）基础模板"></a><strong><font color='#10c300'>2）基础模板</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useCustomHook</span>(<span class="params">arg</span>) &#123;</span><br><span class="line">  <span class="comment">// 可以使用其他 Hooks</span></span><br><span class="line">  <span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(initialValue);</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="comment">// 副作用逻辑</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      <span class="comment">// 清理逻辑</span></span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, [arg]);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 返回状态、方法或计算值</span></span><br><span class="line">  <span class="keyword">return</span> [state, setState]; <span class="comment">// 或返回对象 &#123; state, action &#125;</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="3）实战示例"><a href="#3）实战示例" class="headerlink" title="3）实战示例"></a><strong><font color='#10c300'>3）实战示例</font></strong></h4><p><strong>1️⃣useLocalStorage（持久化状态）</strong></p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> useLocalStorage <span class="keyword">from</span> <span class="string">&quot;./Hooks/useLocalStorage&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [name, setName] = <span class="title function_">useLocalStorage</span>(<span class="string">&#x27;name&#x27;</span>, <span class="string">&#x27;Guest&#x27;</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">value</span>=<span class="string">&#123;name&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;e</span> =&gt;</span> setName(e.target.value)&#125; /&gt;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// src\Hooks\useLocalStorage.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useLocalStorage</span>(<span class="params">key, initialValue</span>) &#123;</span><br><span class="line">    <span class="comment">// 惰性初始化：从 localStorage 读取</span></span><br><span class="line">    <span class="keyword">const</span> [storedValue, setStoredValue] = <span class="title function_">useState</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> item = <span class="variable language_">window</span>.<span class="property">localStorage</span>.<span class="title function_">getItem</span>(key);</span><br><span class="line">            <span class="keyword">return</span> item ? <span class="title class_">JSON</span>.<span class="title function_">parse</span>(item) : initialValue;</span><br><span class="line">        &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">error</span>(error);</span><br><span class="line">            <span class="keyword">return</span> initialValue;</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 更新 localStorage 当状态变化</span></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">setValue</span> = (<span class="params">value</span>) =&gt; &#123;</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">const</span> valueToStore = value <span class="keyword">instanceof</span> <span class="title class_">Function</span> ? <span class="title function_">value</span>(storedValue) : value;</span><br><span class="line">            <span class="title function_">setStoredValue</span>(valueToStore);</span><br><span class="line">            <span class="variable language_">window</span>.<span class="property">localStorage</span>.<span class="title function_">setItem</span>(key, <span class="title class_">JSON</span>.<span class="title function_">stringify</span>(valueToStore));</span><br><span class="line">        &#125; <span class="keyword">catch</span> (error) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">error</span>(error);</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> [storedValue, setValue];</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> useLocalStorage</span><br></pre></td></tr></table></figure><hr><p><strong>2️⃣useDebounce（防抖）</strong></p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> useDebounce <span class="keyword">from</span> <span class="string">&quot;./Hooks/useDebounce&quot;</span></span><br><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SearchInput</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [text, setText] = <span class="title function_">useState</span>(<span class="string">&#x27;&#x27;</span>);</span><br><span class="line">    <span class="keyword">const</span> debouncedText = <span class="title function_">useDebounce</span>(text, <span class="number">500</span>); <span class="comment">// 500ms 防抖</span></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">if</span> (debouncedText) &#123;</span><br><span class="line">            <span class="variable language_">console</span>.<span class="title function_">log</span>(debouncedText); <span class="comment">// 只在停止输入 500ms 后搜索</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;, [debouncedText]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">value</span>=<span class="string">&#123;text&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;e</span> =&gt;</span> setText(e.target.value)&#125; /&gt;</span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">SearchInput</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// src\Hooks\useDebounce.js</span></span><br><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useDebounce</span>(<span class="params">value, delay</span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [debouncedValue, setDebouncedValue] = <span class="title function_">useState</span>(value);</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> handler = <span class="built_in">setTimeout</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="title function_">setDebouncedValue</span>(value);</span><br><span class="line">        &#125;, delay);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">            <span class="built_in">clearTimeout</span>(handler); <span class="comment">// 清除上一次的定时器</span></span><br><span class="line">        &#125;;</span><br><span class="line">    &#125;, [value, delay]);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> debouncedValue;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> useDebounce</span><br></pre></td></tr></table></figure><hr><p><strong>3️⃣useFetch（数据获取）</strong></p><figure class="highlight js"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useFetch</span>(<span class="params">url</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(&#123;</span><br><span class="line">    <span class="attr">data</span>: <span class="literal">null</span>,</span><br><span class="line">    <span class="attr">isLoading</span>: <span class="literal">true</span>,</span><br><span class="line">    <span class="attr">error</span>: <span class="literal">null</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">let</span> cancelled = <span class="literal">false</span>;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">setState</span>(<span class="function">(<span class="params">prev</span>) =&gt;</span> (&#123; ...prev, <span class="attr">isLoading</span>: <span class="literal">true</span> &#125;));</span><br><span class="line"></span><br><span class="line">    <span class="title function_">fetch</span>(url)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> res.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!cancelled) &#123;</span><br><span class="line">          <span class="title function_">setState</span>(&#123; data, <span class="attr">isLoading</span>: <span class="literal">false</span>, <span class="attr">error</span>: <span class="literal">null</span> &#125;);</span><br><span class="line">        &#125;</span><br><span class="line">      &#125;)</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="function">(<span class="params">error</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> (!cancelled) &#123;</span><br><span class="line">          <span class="title function_">setState</span>(&#123; <span class="attr">data</span>: <span class="literal">null</span>, <span class="attr">isLoading</span>: <span class="literal">false</span>, error &#125;);</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 class="function">() =&gt;</span> &#123;</span><br><span class="line">      cancelled = <span class="literal">true</span>;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, [url]);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> state;</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="keyword">function</span> <span class="title function_">UserProfile</span>(<span class="params">&#123; userId &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; <span class="attr">data</span>: user, isLoading, error &#125; = <span class="title function_">useFetch</span>(<span class="string">`/api/users/<span class="subst">$&#123;userId&#125;</span>`</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">if</span> (isLoading) <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Spinner</span> /&gt;</span></span>;</span><br><span class="line">  <span class="keyword">if</span> (error) <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Error</span> <span class="attr">message</span>=<span class="string">&#123;error.message&#125;</span> /&gt;</span></span>;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;user.name&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><p><strong>4️⃣useMediaQuery（响应式）</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useEffect &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useMediaQuery</span>(<span class="params">query</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [matches, setMatches] = <span class="title function_">useState</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(query).<span class="property">matches</span>,</span><br><span class="line">  );</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> media = <span class="variable language_">window</span>.<span class="title function_">matchMedia</span>(query);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">const</span> <span class="title function_">listener</span> = (<span class="params"></span>) =&gt; <span class="title function_">setMatches</span>(media.<span class="property">matches</span>);</span><br><span class="line">    media.<span class="title function_">addEventListener</span>(<span class="string">&quot;change&quot;</span>, listener);</span><br><span class="line">    <span class="title function_">setMatches</span>(media.<span class="property">matches</span>);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> media.<span class="title function_">removeEventListener</span>(<span class="string">&quot;change&quot;</span>, listener);</span><br><span class="line">  &#125;, [query]); <span class="comment">// 不必依赖 matches</span></span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> matches;</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="keyword">function</span> <span class="title function_">ResponsiveComponent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> isMobile = <span class="title function_">useMediaQuery</span>(<span class="string">&quot;(max-width: 768px)&quot;</span>);</span><br><span class="line">  <span class="keyword">const</span> isTablet = <span class="title function_">useMediaQuery</span>(<span class="string">&quot;(min-width: 769px) and (max-width: 1024px)&quot;</span>);</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>&#123;isMobile ? &quot;手机视图&quot; : isTablet ? &quot;平板视图&quot; : &quot;桌面视图&quot;&#125;<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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">ResponsiveComponent</span>;</span><br></pre></td></tr></table></figure><h4 id="4）高级模式"><a href="#4）高级模式" class="headerlink" title="4）高级模式"></a><strong><font color='#10c300'>4）高级模式</font></strong></h4><p><strong>1️⃣组合多个 Hooks（自定义 Hook 使用其他自定义 Hook）</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState, useEffect, useMemo &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useWindowSize</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [size, setSize] = <span class="title function_">useState</span>(&#123; <span class="attr">width</span>: <span class="number">0</span>, <span class="attr">height</span>: <span class="number">0</span> &#125;);</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> <span class="title function_">update</span> = (<span class="params"></span>) =&gt;</span><br><span class="line">      <span class="title function_">setSize</span>(&#123;</span><br><span class="line">        <span class="attr">width</span>: <span class="variable language_">window</span>.<span class="property">innerWidth</span>,</span><br><span class="line">        <span class="attr">height</span>: <span class="variable language_">window</span>.<span class="property">innerHeight</span>,</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, update);</span><br><span class="line">    <span class="title function_">update</span>();</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;resize&quot;</span>, update);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> size;</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="keyword">function</span> <span class="title function_">useBreakpoint</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; width &#125; = <span class="title function_">useWindowSize</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> &#123;</span><br><span class="line">      <span class="attr">isMobile</span>: width &lt; <span class="number">768</span>,</span><br><span class="line">      <span class="attr">isTablet</span>: width &gt;= <span class="number">768</span> &amp;&amp; width &lt; <span class="number">1024</span>,</span><br><span class="line">      <span class="attr">isDesktop</span>: width &gt;= <span class="number">1024</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, [width]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> breakpoints = <span class="title function_">useBreakpoint</span>();</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">h1</span>&gt;</span>Current Breakpoints<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">pre</span>&gt;</span>&#123;JSON.stringify(breakpoints, null, 4)&#125;<span class="tag">&lt;/<span class="name">pre</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p><strong>2️⃣返回稳定引用（防止无限重渲染）</strong></p><p>**完整案例：**<a href="https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#Xexzy">https://www.yuque.com/zhbiao/qr34us/qk5da4gmpqzhat4s/edit#Xexzy</a></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">useAuth</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(<span class="literal">null</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 useCallback 保持方法引用稳定</span></span><br><span class="line">  <span class="keyword">const</span> login = <span class="title function_">useCallback</span>(<span class="title function_">async</span> (credentials) =&gt; &#123;</span><br><span class="line">    <span class="keyword">const</span> user = <span class="keyword">await</span> api.<span class="title function_">login</span>(credentials);</span><br><span class="line">    <span class="title function_">setUser</span>(user);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> logout = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    api.<span class="title function_">logout</span>();</span><br><span class="line">    <span class="title function_">setUser</span>(<span class="literal">null</span>);</span><br><span class="line">  &#125;, []);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// 使用 useMemo 保持对象引用稳定</span></span><br><span class="line">  <span class="keyword">const</span> value = <span class="title function_">useMemo</span>(</span><br><span class="line">    <span class="function">() =&gt;</span> (&#123;</span><br><span class="line">      user,</span><br><span class="line">      <span class="attr">isAuthenticated</span>: !!user,</span><br><span class="line">      login,</span><br><span class="line">      logout,</span><br><span class="line">    &#125;),</span><br><span class="line">    [user, login, logout],</span><br><span class="line">  );</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> value;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 现在可以安全地用于 useEffect 依赖</span></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">if</span> (auth.<span class="property">isAuthenticated</span>) &#123;</span><br><span class="line">    <span class="comment">// 不会导致无限循环，因为 auth 引用稳定</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;, [auth]);</span><br></pre></td></tr></table></figure><br><h4 id="5）常见陷阱"><a href="#5）常见陷阱" class="headerlink" title="5）常见陷阱"></a><strong><font color='#10c300'>5）常见陷阱</font></strong></h4><p><strong>1️⃣依赖数组遗漏</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：handler 变化时不会更新</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useEventListener</span>(<span class="params">eventName, handler</span>) &#123;</span><br><span class="line">  <span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(eventName, handler);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(eventName, handler);</span><br><span class="line">  &#125;, []); <span class="comment">// 缺少 handler</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：但要求使用者使用 useCallback 包裹 handler</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(eventName, handler);</span><br><span class="line">  <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(eventName, handler);</span><br><span class="line">&#125;, [eventName, handler]);</span><br></pre></td></tr></table></figure><p><strong>2️⃣返回不稳定引用导致重渲染</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：每次返回新数组</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useData</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="title function_">fetchData</span>();</span><br><span class="line">  <span class="keyword">return</span> [data, data.<span class="property">length</span>]; <span class="comment">// 新数组引用</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="keyword">function</span> <span class="title function_">useData</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> data = <span class="title function_">fetchData</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="title function_">useMemo</span>(<span class="function">() =&gt;</span> [data, data.<span class="property">length</span>], [data]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3️⃣在条件语句中使用 Hook</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">useConditionalHook</span>(<span class="params">condition</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (condition) &#123;</span><br><span class="line">    <span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(<span class="number">0</span>); <span class="comment">// Hook 在条件中！</span></span><br><span class="line">  &#125;</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="keyword">function</span> <span class="title function_">useConditionalHook</span>(<span class="params">condition</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [state, setState] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="comment">// 根据 condition 决定是否使用 state</span></span><br><span class="line">  <span class="keyword">return</span> condition ? state : <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h2 id="五、-HOC-高阶组件"><a href="#五、-HOC-高阶组件" class="headerlink" title="五、 HOC 高阶组件"></a><strong>五、 HOC 高阶组件</strong></h2><p>高阶组件是一个<strong>函数</strong>，它接收一个组件并返回一个新的组件，例如：</p><h3 id="5-1-React-memo"><a href="#5-1-React-memo" class="headerlink" title="5.1 React.memo"></a><strong><font color='red'>5.1 React.memo</font></strong></h3><p><code>memo</code> 是一个 <strong>高阶组件</strong>，用于<strong>优化函数组件的重新渲染</strong>。只有当它的 <strong>props 发生变化</strong> 时，React 才会重新渲染这个组件。否则，它会直接复用上一次的渲染结果，提高性能。</p><hr><h4 id="1）基本语法-9"><a href="#1）基本语法-9" class="headerlink" title="1）基本语法"></a><strong><font color='#10c300'>1）基本语法</font></strong></h4><figure class="highlight js"><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">// 直接在父组件将引入的子组件MyComponent使用memo缓存</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MemoizedComponent</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">MyComponent</span>);</span><br></pre></td></tr></table></figure><p>或者定义时直接使用：</p><figure class="highlight js"><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">// 直接在子组件导出的时候就对当前组件MyComponent使用memo缓存</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">MyComponent</span>);</span><br></pre></td></tr></table></figure><h4 id="2）基本示例"><a href="#2）基本示例" class="headerlink" title="2）基本示例"></a><strong><font color='#10c300'>2）基本示例</font></strong></h4><figure class="highlight js"><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">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./components/Child&quot;</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> [data, setData] = <span class="title function_">useState</span>(<span class="string">&quot;父组件数据&quot;</span>);</span><br><span class="line">    <span class="keyword">const</span> [b] = <span class="title function_">useState</span>(<span class="number">100</span>)</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;父组件渲染了&#x27;</span>);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> setData(`新数据$&#123;new Date().getTime()&#125;`)&#125;&gt;我是App：&#123;data&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">Child</span> <span class="attr">b</span>=<span class="string">&#123;b&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br><span class="line"></span><br><span class="line">============================================分割线==============================================</span><br><span class="line"></span><br><span class="line"><span class="comment">// Child.jsx</span></span><br><span class="line"><span class="keyword">import</span> &#123; memo &#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_">Child</span>(<span class="params">props</span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;子组件渲染了&#x27;</span>);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;<span class="name">p</span>&gt;</span>我是子组件: &#123;props.b&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    )</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 使用 React.memo 包装组件，只有当 props 发生变化时才重新渲染</span></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title function_">memo</span>(<span class="title class_">Child</span>);</span><br></pre></td></tr></table></figure><p><strong>🔍 输出结果：</strong></p><ul><li>初始化的时候父组件和子组件都渲染</li><li>触发父组件的点击事件，data状态发生变化，b状态未变，子组件未触发渲染</li></ul><p>如果用的是普通组件，哪怕 props 一样，也会重新渲染。</p><h4 id="3）工作原理-1"><a href="#3）工作原理-1" class="headerlink" title="3）工作原理"></a><strong><font color='#10c300'>3）工作原理</font></strong></h4><p><code>React.memo</code> 在渲染后，会 <strong>保存上一次的 props</strong>。<br>下一次组件渲染时，它会做一个 <strong>浅比较（shallow compare）</strong>：</p><ul><li>如果旧的 props 与新的 props 内容一致 → 跳过渲染；</li><li>如果不同 → 重新渲染。</li></ul><blockquote><p>默认比较是浅层（只比较基础类型或引用是否相同），<br>对象&#x2F;数组类型的 props 如果新建了对象，就会认为不同。</p></blockquote><h4 id="4）自定义比较函数"><a href="#4）自定义比较函数" class="headerlink" title="4）自定义比较函数"></a><strong><font color='#10c300'>4）自定义比较函数</font></strong></h4><p>有时候你希望控制“什么算变”，可以在第二个参数传入自定义比较函数：</p><figure class="highlight js"><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="keyword">const</span> <span class="title class_">Memoized</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="title class_">MyComponent</span>, <span class="function">(<span class="params">prevProps, nextProps</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// 返回 true 表示不需要重新渲染（props 相等）</span></span><br><span class="line">  <span class="comment">// 返回 false 表示需要重渲染</span></span><br><span class="line">  <span class="keyword">return</span> prevProps.<span class="property">id</span> === nextProps.<span class="property">id</span>;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure><h4 id="5）常见优化场景"><a href="#5）常见优化场景" class="headerlink" title="5）常见优化场景"></a><strong><font color='#10c300'>5）常见优化场景</font></strong></h4><table><thead><tr><th>场景</th><th>说明</th></tr></thead><tbody><tr><td>列表项组件</td><td>列表中有大量子组件，父组件频繁更新</td></tr><tr><td>表单组件</td><td>输入变化导致整个页面重渲染</td></tr><tr><td>复杂组件</td><td>内部渲染代价高但 props 很稳定</td></tr><tr><td>与 useCallback 搭配</td><td>保持传入函数引用一致，避免误触更新</td></tr></tbody></table><h4 id="6）与-useCallback-和-useMemo-的配合"><a href="#6）与-useCallback-和-useMemo-的配合" class="headerlink" title="6）与 useCallback 和 useMemo 的配合"></a><strong><font color='#10c300'>6）与 useCallback 和 useMemo 的配合</font></strong></h4><p><code>React.memo</code> 只关心 props 是否变化。但是如果传入的是一个函数，每次渲染函数都是新引用，就会导致重新渲染。函数作为 <code>props</code> 的“引用不稳定”问题</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="comment">// 子组件</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Child</span> = <span class="title class_">React</span>.<span class="title function_">memo</span>(<span class="function">(<span class="params">props</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;Child render&quot;</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;props.count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></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="keyword">function</span> <span class="title function_">Parent</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;clicked&quot;</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">div</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> <span class="attr">count</span>=<span class="string">&#123;count&#125;</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#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><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Parent</span>;</span><br></pre></td></tr></table></figure><p>你期望的是：只要 <code>count</code> 没变，<code>Child</code> 不应该重新渲染。但实际上，<strong>每次父组件重渲染，<code>Child</code> 都会重新渲染</strong>。</p><p><strong><font color='#00A6ED'>🌈原因：函数的引用每次都变</font></strong></p><p>在 React 中，函数也是一种对象，而对象&#x2F;函数的「引用地址」每次都会变。当父组件重新运行时 都会创建一个 <strong>新函数对象</strong>，和之前的不是同一个引用。</p><p>React.memo 的默认比较方式是浅比较（shallow compare）</p><figure class="highlight js"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">oldProps.<span class="property">onClick</span> === newProps.<span class="property">onClick</span>;</span><br></pre></td></tr></table></figure><p>而因为每次重新渲染时，这个函数都是新建的，所以结果是 <code>false</code>，导致 <code>Child</code> 无论 props 内容是否逻辑相同，都会被认为变动了。</p><p>🤏**解决方案：**通过 <code>useCallback</code> 来让函数在依赖不变的情况下，保持同一个引用</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123;useCallback, useState&#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line">unction <span class="title class_">Parent</span>() &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">const</span> handleClick = <span class="title function_">useCallback</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&#x27;clicked&#x27;</span>);</span><br><span class="line">  &#125;, []) <span class="comment">// 👈 引用稳定</span></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> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</span> <span class="attr">count</span>=<span class="string">&#123;count&#125;</span> <span class="attr">onClick</span>=<span class="string">&#123;handleClick&#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><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">Parent</span>;</span><br></pre></td></tr></table></figure><h4 id="7）注意事项"><a href="#7）注意事项" class="headerlink" title="7）注意事项"></a><strong><font color='#10c300'>7）注意事项</font></strong></h4><table><thead><tr><th>项目</th><th>说明</th></tr></thead><tbody><tr><td>比较方式</td><td>浅比较（对象或函数需注意引用变化）</td></tr><tr><td>不适合滥用</td><td>若组件很轻量，比较成本可能高于渲染收益</td></tr><tr><td>useContext 更新仍会触发</td><td>Context 更新时会重新渲染子节点</td></tr><tr><td>不缓存 state</td><td>仅缓存渲染结果，与内部状态无关</td></tr></tbody></table><h4 id="8）总结"><a href="#8）总结" class="headerlink" title="8）总结"></a><strong><font color='#10c300'>8）总结</font></strong></h4><table><thead><tr><th>项目</th><th>内容</th></tr></thead><tbody><tr><td>作用</td><td>缓存组件渲染结果，避免不必要渲染</td></tr><tr><td>类型</td><td>高阶组件（HOC）</td></tr><tr><td>比较机制</td><td>浅层比较 props（可自定义）</td></tr><tr><td>推荐搭配</td><td><code>useCallback</code>, <code>useMemo</code> 保持 props 引用稳定</td></tr><tr><td>适用场景</td><td>子组件稳定、父组件频繁渲染、性能优化</td></tr></tbody></table><hr><p>🌟 <strong>一句话总结：</strong></p><blockquote><p><code>React.memo</code> 就像是函数组件的“PureComponent”。<br>当 props 没变时，跳过渲染，提高性能。</p></blockquote><hr><br><h2 id="七、-React-19-新特性"><a href="#七、-React-19-新特性" class="headerlink" title="七、 React 19 新特性"></a><strong>七、 React 19 新特性</strong></h2><p>React 19 带来了许多令人兴奋的新特性，特别是针对 <strong>性能优化</strong> 和 <strong>服务端组件（RSC）</strong> 的深度集成。最引人注目的是 React Compiler（React 编译器）的引入，它旨在自动处理 memoization，减少手动优化的负担。</p><h3 id="7-1-React-Compiler-React-编译器"><a href="#7-1-React-Compiler-React-编译器" class="headerlink" title="7.1 React Compiler (React 编译器)"></a><strong><font color='red'>7.1 React Compiler (React 编译器)</font></strong></h3><p>这是 React 19 最具革命性的变化。</p><ul><li><strong>过去</strong>：为了避免不必要的重新渲染，我们需要手动使用 <code>useMemo</code>、<code>useCallback</code> 和 <code>React.memo</code>。</li><li><strong>现在</strong>：React Compiler 可以在构建时自动分析代码，并自动应用优化。这意味着你可能不再需要手动写 <code>useMemo</code> 和 <code>useCallback</code> 了！</li></ul><blockquote><p>⚠️ 注意：这是一个构建工具层面的优化，而不是运行时 API。</p></blockquote><h3 id="7-2-Actions-Server-Actions"><a href="#7-2-Actions-Server-Actions" class="headerlink" title="7.2 Actions (Server Actions)"></a><strong><font color='red'>7.2 Actions (Server Actions)</font></strong></h3><p>React 19 更加拥抱服务端能力，引入了 <strong>Actions</strong> 概念，简化数据提交和变更。</p><h4 id="1）useActionState-原-useFormState"><a href="#1）useActionState-原-useFormState" class="headerlink" title="1）useActionState (原 useFormState)"></a><strong><font color='#10c300'>1）useActionState (原 useFormState)</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useActionState &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="keyword">function</span> <span class="title function_">updateName</span>(<span class="params">error, formData</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> name = formData.<span class="title function_">get</span>(<span class="string">&quot;name&quot;</span>);</span><br><span class="line">  <span class="keyword">if</span> (!name) <span class="keyword">return</span> <span class="string">&quot;Name required&quot;</span>;</span><br><span class="line">  <span class="keyword">await</span> api.<span class="title function_">updateUser</span>(name);</span><br><span class="line">  <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">ChangeName</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [error, submitAction, isPending] = <span class="title function_">useActionState</span>(updateName, <span class="literal">null</span>);</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">form</span> <span class="attr">action</span>=<span class="string">&#123;submitAction&#125;</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">type</span>=<span class="string">&quot;text&quot;</span> <span class="attr">name</span>=<span class="string">&quot;name&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">button</span> <span class="attr">type</span>=<span class="string">&quot;submit&quot;</span> <span class="attr">disabled</span>=<span class="string">&#123;isPending&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;isPending ? &quot;Updating...&quot; : &quot;Update&quot;&#125;</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 class="language-xml">      &#123;error &amp;&amp; <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;error&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span>&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）useFormStatus"><a href="#2）useFormStatus" class="headerlink" title="2）useFormStatus"></a><strong><font color='#10c300'>2）useFormStatus</font></strong></h4><p>让我们在子组件中直接获取父级 <code>&lt;form&gt;</code> 的状态，而不需要通过 props 传递 <code>loading</code> 状态。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; useFormStatus &#125; <span class="keyword">from</span> <span class="string">&quot;react-dom&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">SubmitButton</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> &#123; pending &#125; = <span class="title function_">useFormStatus</span>();</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">disabled</span>=<span class="string">&#123;pending&#125;</span>&gt;</span>&#123;pending ? &quot;提交中...&quot; : &quot;提交&quot;&#125;<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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">form</span> <span class="attr">action</span>=<span class="string">&#123;submitAction&#125;</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">name</span>=<span class="string">&quot;name&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">SubmitButton</span> /&gt;</span> &#123;/* 自动感知 form 状态 */&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">form</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-3-新的-use-API"><a href="#7-3-新的-use-API" class="headerlink" title="7.3 新的 use API"></a><strong><font color='red'>7.3 新的 <code>use</code> API</font></strong></h3><p><code>use</code> 是一个新的 API，用于在 render 中读取资源（Promise 或 Context）。</p><h4 id="1）读取-Context"><a href="#1）读取-Context" class="headerlink" title="1）读取 Context"></a><strong><font color='#10c300'>1）读取 Context</font></strong></h4><p>不再受限于只能在顶层使用 <code>useContext</code>，<code>use</code> 可以在条件语句和循环中使用（尽管仍建议在顶层）。</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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; use &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">ThemeContext</span> <span class="keyword">from</span> <span class="string">&quot;./ThemeContext&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Heading</span>(<span class="params">&#123; children &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (children == <span class="literal">null</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="comment">// ✅ 可以在条件判断中使用</span></span><br><span class="line">  <span class="keyword">const</span> theme = <span class="title function_">use</span>(<span class="title class_">ThemeContext</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">color:</span> <span class="attr">theme.color</span> &#125;&#125;&gt;</span>&#123;children&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h4 id="2）读取-Promise"><a href="#2）读取-Promise" class="headerlink" title="2）读取 Promise"></a><strong><font color='#10c300'>2）读取 Promise</font></strong></h4><p>可以直接在组件中等待 Promise 解析（配合 Suspense）。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; use, <span class="title class_">Suspense</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Comments</span>(<span class="params">&#123; commentsPromise &#125;</span>) &#123;</span><br><span class="line">  <span class="comment">// ❌ 以前：需要 useEffect + useState</span></span><br><span class="line">  <span class="comment">// ✅ 现在：直接 use(promise)</span></span><br><span class="line">  <span class="keyword">const</span> comments = <span class="title function_">use</span>(commentsPromise);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">return</span> comments.<span class="title function_">map</span>(<span class="function">(<span class="params">c</span>) =&gt;</span> <span class="language-xml"><span class="tag">&lt;<span class="name">p</span> <span class="attr">key</span>=<span class="string">&#123;c.id&#125;</span>&gt;</span>&#123;c.text&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span>);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Page</span>(<span class="params">&#123; post &#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">Suspense</span> <span class="attr">fallback</span>=<span class="string">&quot;加载评论中...&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Comments</span> <span class="attr">commentsPromise</span>=<span class="string">&#123;fetchComments(post.id)&#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><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h3 id="7-4-Ref-作为-Prop"><a href="#7-4-Ref-作为-Prop" class="headerlink" title="7.4 Ref 作为 Prop"></a><strong><font color='red'>7.4 Ref 作为 Prop</font></strong></h3><p>终于！在 React 19 中，你不再需要 <code>forwardRef</code> 了。<code>ref</code> 现在只是一个普通的 prop。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// React 19 写法</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">MyInput</span>(<span class="params">&#123; placeholder, ref &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">input</span> <span class="attr">placeholder</span>=<span class="string">&#123;placeholder&#125;</span> <span class="attr">ref</span>=<span class="string">&#123;ref&#125;</span> /&gt;</span></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">&lt;<span class="title class_">MyInput</span> ref=&#123;inputRef&#125; /&gt;;</span><br></pre></td></tr></table></figure><h3 id="7-5-文档元数据支持"><a href="#7-5-文档元数据支持" class="headerlink" title="7.5 文档元数据支持"></a><strong><font color='red'>7.5 文档元数据支持</font></strong></h3><p>直接在组件中渲染 <code>&lt;title&gt;</code>、<code>&lt;meta&gt;</code> 和 <code>&lt;link&gt;</code> 标签，React 会自动将它们提升到 <code>&lt;head&gt;</code> 中。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span> <span class="title function_">HomePage</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">title</span>&gt;</span>首页 - 我的应用<span class="tag">&lt;/<span class="name">title</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">meta</span> <span class="attr">name</span>=<span class="string">&quot;description&quot;</span> <span class="attr">content</span>=<span class="string">&quot;这是首页&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h1</span>&gt;</span>欢迎<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><hr><br><h2 id="八、-最佳实践"><a href="#八、-最佳实践" class="headerlink" title="八、 最佳实践"></a><strong>八、 最佳实践</strong></h2><h3 id="8-1-组件拆分与结构"><a href="#8-1-组件拆分与结构" class="headerlink" title="8.1 组件拆分与结构"></a><strong><font color='red'>8.1 组件拆分与结构</font></strong></h3><ol><li><strong>单一职责原则</strong>：每个组件只做一件事。如果一个组件超过 200 行，或者包含多个 <code>useEffect</code> 处理不同逻辑，考虑拆分。</li><li><strong>容器与展示组件分离</strong>：<ul><li><strong>展示组件 (Presentational)</strong>：只负责渲染 UI，通过 props 接收数据和回调（如 <code>Button</code>, <code>Card</code>）。</li><li><strong>容器组件 (Container)</strong>：负责获取数据、处理逻辑，将数据传给展示组件。</li></ul></li></ol><h3 id="8-2-性能优化指南"><a href="#8-2-性能优化指南" class="headerlink" title="8.2 性能优化指南"></a><strong><font color='red'>8.2 性能优化指南</font></strong></h3><ol><li><p><strong>变动分离</strong>：将变动频繁的部分（如输入框文字）抽离成独立组件，避免带动父组件重渲染。</p></li><li><p><strong>列表虚拟化</strong>：对于长列表（超过 50 条），使用 <code>react-window</code> 或 <code>react-virtuoso</code> 仅渲染可视区域的元素。</p></li><li><p><strong>懒加载 (Lazy Loading)</strong>：<br>对于非首屏路由或大型组件，使用 <code>lazy</code> 和 <code>Suspense</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title class_">AdminPanel</span> = <span class="title function_">lazy</span>(<span class="function">() =&gt;</span> <span class="keyword">import</span>(<span class="string">&quot;./AdminPanel&quot;</span>));</span><br><span class="line"></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">AdminPanel</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></pre></td></tr></table></figure></li><li><p><strong>避免滥用 Context</strong>：Context 适合全局配置（主题、用户、语言），不适合高频变动的数据。高频数据应考虑 <code>Zustand</code> 或 <code>Redux</code> 等专门的状态管理库。</p></li></ol><h3 id="8-3-自定义-Hooks-封装逻辑"><a href="#8-3-自定义-Hooks-封装逻辑" class="headerlink" title="8.3 自定义 Hooks 封装逻辑"></a><strong><font color='red'>8.3 自定义 Hooks 封装逻辑</font></strong></h3><p>不要在组件里堆砌 <code>useEffect</code>。将相关的逻辑（如 data fetching, event listeners）封装到自定义 Hook 中。</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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 不推荐</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [width, setWidth] = <span class="title function_">useState</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>);</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> <span class="title function_">handle</span> = (<span class="params"></span>) =&gt; <span class="title function_">setWidth</span>(<span class="variable language_">window</span>.<span class="property">innerWidth</span>);</span><br><span class="line">    <span class="variable language_">window</span>.<span class="title function_">addEventListener</span>(<span class="string">&quot;resize&quot;</span>, handle);</span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> <span class="variable language_">window</span>.<span class="title function_">removeEventListener</span>(<span class="string">&quot;resize&quot;</span>, handle);</span><br><span class="line">  &#125;, []);</span><br><span class="line">  <span class="comment">// ...</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="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> width = <span class="title function_">useWindowSize</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="8-4-错误边界-Error-Boundaries"><a href="#8-4-错误边界-Error-Boundaries" class="headerlink" title="8.4 错误边界 (Error Boundaries)"></a><strong><font color='red'>8.4 错误边界 (Error Boundaries)</font></strong></h3><p>使用错误边界捕获组件树中的 JS 错误，防止整个应用崩溃（白屏）。推荐使用 <code>react-error-boundary</code> 库。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> &#123; <span class="title class_">ErrorBoundary</span> &#125; <span class="keyword">from</span> <span class="string">&quot;react-error-boundary&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">ErrorBoundary</span> <span class="attr">fallback</span>=<span class="string">&#123;</span>&lt;<span class="attr">div</span>&gt;</span>出错了！<span class="tag">&lt;/<span class="name">div</span>&gt;</span>&#125;&gt;</span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;<span class="name">MyWidget</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;/<span class="name">ErrorBoundary</span>&gt;</span></span>;</span><br></pre></td></tr></table></figure><hr><br><h2 id="九、-常见问题与解决方案"><a href="#九、-常见问题与解决方案" class="headerlink" title="九、 常见问题与解决方案"></a><strong>九、 常见问题与解决方案</strong></h2><h3 id="Q1-为什么我的组件渲染了两次？"><a href="#Q1-为什么我的组件渲染了两次？" class="headerlink" title="Q1: 为什么我的组件渲染了两次？"></a><strong><font color='red'>Q1: 为什么我的组件渲染了两次？</font></strong></h3><p><strong>A:</strong> 这是 React 18+ 在 <strong>开发模式 (Strict Mode)</strong> 下的预期行为。<br>React 会故意卸载并重新挂载组件（mount -&gt; unmount -&gt; mount），主要为了检测：</p><ul><li><code>useEffect</code> 清理函数是否正确。</li><li>是否有不纯的副作用。</li></ul><p><strong>生产环境只会渲染一次</strong>，无需担心性能。</p><h3 id="Q2-“Too-many-re-renders”-报错"><a href="#Q2-“Too-many-re-renders”-报错" class="headerlink" title="Q2: “Too many re-renders” 报错"></a><strong><font color='red'>Q2: “Too many re-renders” 报错</font></strong></h3><p><strong>A:</strong> 通常是因为你在<strong>渲染阶段</strong>直接调用了 <code>setState</code>。</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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [count, setCount] = <span class="title function_">useState</span>(<span class="number">0</span>);</span><br><span class="line">  <span class="title function_">setCount</span>(<span class="number">1</span>); <span class="comment">// 🔴 每次渲染又触发更新 -&gt; 死循环</span></span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>&#123;count&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></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="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="title function_">setCount</span>(<span class="number">1</span>);</span><br><span class="line">&#125;, []); <span class="comment">// 仅在挂载时执行</span></span><br></pre></td></tr></table></figure><h3 id="Q3-useEffect-依赖警告-exhaustive-deps"><a href="#Q3-useEffect-依赖警告-exhaustive-deps" class="headerlink" title="Q3: useEffect 依赖警告 (exhaustive-deps)"></a><strong><font color='red'>Q3: useEffect 依赖警告 (exhaustive-deps)</font></strong></h3><p><strong>A:</strong> ESLint 警告你遗漏了依赖项。</p><ul><li><strong>不要忽略它</strong>：忽略通常会导致闭包陷阱（读到旧值）。</li><li><strong>解决方案</strong>：<ol><li>将函数移到 <code>useEffect</code> 内部。</li><li>使用 <code>useCallback</code> 包裹函数。</li><li>如果真的只需要执行一次，检查逻辑是否正确，或者用 <code>useRef</code> 保存不需要触发更新的值。</li></ol></li></ul><h3 id="Q4-为什么-console-log-打印-State-总是旧值？"><a href="#Q4-为什么-console-log-打印-State-总是旧值？" class="headerlink" title="Q4: 为什么 console.log 打印 State 总是旧值？"></a><strong><font color='red'>Q4: 为什么 console.log 打印 State 总是旧值？</font></strong></h3><p><strong>A:</strong> <code>setState</code> 是<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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> <span class="title function_">handleClick</span> = (<span class="params"></span>) =&gt; &#123;</span><br><span class="line">  <span class="title function_">setCount</span>(<span class="number">10</span>);</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 仍然是 0 (旧值)</span></span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 解决方法：使用 useEffect 监听变化</span></span><br><span class="line"><span class="title function_">useEffect</span>(<span class="function">() =&gt;</span> &#123;</span><br><span class="line">  <span class="variable language_">console</span>.<span class="title function_">log</span>(count); <span class="comment">// 10 (新值)</span></span><br><span class="line">&#125;, [count]);</span><br></pre></td></tr></table></figure><h3 id="Q5-A-component-is-changing-an-uncontrolled-input-to-be-controlled"><a href="#Q5-A-component-is-changing-an-uncontrolled-input-to-be-controlled" class="headerlink" title="Q5: A component is changing an uncontrolled input to be controlled"></a><strong><font color='red'>Q5: A component is changing an uncontrolled input to be controlled</font></strong></h3><p><strong>A:</strong> 这通常是因为你给 input 的 <code>value</code> 传了 <code>undefined</code> 或 <code>null</code>。</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 错误：初始是 undefined (非受控)，后来变成 &#x27;hello&#x27; (受控)</span></span><br><span class="line"><span class="keyword">const</span> [text, setText] = <span class="title function_">useState</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;text&#125;</span> /&gt;</span></span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 正确：给初始值</span></span><br><span class="line"><span class="keyword">const</span> [text, setText] = <span class="title function_">useState</span>(<span class="string">&quot;&quot;</span>);</span><br></pre></td></tr></table></figure><h3 id="Q6-如何处理组件卸载后的异步状态更新？"><a href="#Q6-如何处理组件卸载后的异步状态更新？" class="headerlink" title="Q6: 如何处理组件卸载后的异步状态更新？"></a><strong><font color='red'>Q6: 如何处理组件卸载后的异步状态更新？</font></strong></h3><p><strong>A:</strong> 在 <code>useEffect</code> 中发起异步请求时，如果组件在请求完成前被卸载，尝试更新状态可能会导致警告（React 18 前）或潜在的竞态条件。</p><p><strong>🤔 为什么说“组件可能已卸载”？</strong></p><p>由于 <code>fetch</code> 是异步的（网络请求需要时间），在请求发出后到响应返回前的这段时间内，用户可能已经点击了路由跳转、关闭了弹窗或条件渲染导致该组件被销毁。<br>此时代码执行到 <code>.then(setUser)</code> 时，实际上是在试图更新一个“已经不存在的组件”的状态。</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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// ❌ 问题：组件卸载后更新状态</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">UserData</span>(<span class="params">&#123; userId &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(<span class="literal">null</span>);</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="title function_">fetch</span>(<span class="string">`/api/users/<span class="subst">$&#123;userId&#125;</span>`</span>)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> res.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">setUser</span>(data)); <span class="comment">// 如果此时用户已经离开当前页面，组件已卸载，这里就会报错</span></span><br><span class="line">  &#125;, [userId]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ✅ 解决：使用 AbortController (推荐)</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">UserData</span>(<span class="params">&#123; userId &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(<span class="literal">null</span>);</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> controller = <span class="keyword">new</span> <span class="title class_">AbortController</span>(); <span class="comment">// 1. 创建控制器</span></span><br><span class="line"></span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">`/api/users/<span class="subst">$&#123;userId&#125;</span>`</span>, &#123; <span class="attr">signal</span>: controller.<span class="property">signal</span> &#125;) <span class="comment">// 2. 绑定 signal</span></span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> res.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> <span class="title function_">setUser</span>(data))</span><br><span class="line">      .<span class="title function_">catch</span>(<span class="function">(<span class="params">err</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 3. 忽略因取消导致的错误</span></span><br><span class="line">        <span class="keyword">if</span> (err.<span class="property">name</span> !== <span class="string">&quot;AbortError&quot;</span>) <span class="keyword">throw</span> err;</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 4. 组件卸载或 userId 变化时取消请求</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> controller.<span class="title function_">abort</span>();</span><br><span class="line">  &#125;, [userId]);</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// ⚠️ 传统解法：使用 cleanup function + 标记变量 (isMounted)</span></span><br><span class="line"><span class="comment">// 这种方法不终止网络请求，只是不执行 setState，浪费流量但能避免报错</span></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">UserDataFallback</span>(<span class="params">&#123; userId &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">const</span> [user, setUser] = <span class="title function_">useState</span>(<span class="literal">null</span>);</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">let</span> isMounted = <span class="literal">true</span>; <span class="comment">// 1. 设置标记</span></span><br><span class="line"></span><br><span class="line">    <span class="title function_">fetch</span>(<span class="string">`/api/users/<span class="subst">$&#123;userId&#125;</span>`</span>)</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> res.<span class="title function_">json</span>())</span><br><span class="line">      .<span class="title function_">then</span>(<span class="function">(<span class="params">data</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 2. 只有组件还在挂载中时才更新状态</span></span><br><span class="line">        <span class="keyword">if</span> (isMounted) <span class="title function_">setUser</span>(data);</span><br><span class="line">      &#125;);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 3. 清理函数：组件卸载时将标记设为 false</span></span><br><span class="line">    <span class="keyword">return</span> <span class="function">() =&gt;</span> &#123;</span><br><span class="line">      isMounted = <span class="literal">false</span>;</span><br><span class="line">    &#125;;</span><br><span class="line">  &#125;, [userId]);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;h1 id=&quot;React-初学者完全指南-React-18-19&quot;&gt;&lt;a href=&quot;#React-初学者完全指南-React-18-19&quot; class=&quot;headerlink&quot; title=&quot;React 初学者完全指南 (React 18 &amp;amp; 19)&quot;&gt;&lt;/a&gt;Rea</summary>
      
    
    
    
    <category term="框架与生态" scheme="http://example.com/categories/%E6%A1%86%E6%9E%B6%E4%B8%8E%E7%94%9F%E6%80%81/"/>
    
    
    <category term="react18" scheme="http://example.com/tags/react18/"/>
    
    <category term="react19" scheme="http://example.com/tags/react19/"/>
    
  </entry>
  
  <entry>
    <title>React 类组件开发全攻略</title>
    <link href="http://example.com/posts/4179c0b9543543.html"/>
    <id>http://example.com/posts/4179c0b9543543.html</id>
    <published>2025-12-02T09:28:38.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<h2 id="一、组件与类组件"><a href="#一、组件与类组件" class="headerlink" title="一、组件与类组件"></a><strong><font color='red'>一、组件与类组件</font></strong></h2><p>组件其实就是一段封装了 HTML、CSS、JS 的代码，最终表现为一个自定义标签。</p><p>组件都有自己独立的作用域：</p><blockquote><p>React 中的组件分为两大类：</p><ol><li>类组件（本手册重点）</li><li>函数组件</li></ol></blockquote><h3 id="2-1、类组件"><a href="#2-1、类组件" class="headerlink" title="2.1、类组件"></a><strong><font color='#10c300'>2.1、类组件</font></strong></h3><p>要将 React 组件定义为类，请继承内置的 Component 类并定义 render 方法：</p><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>自定义类组件<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// return后面不写html标签直接换行 则需要用括号包裹</span></span><br><span class="line">    <span class="comment">// return (</span></span><br><span class="line">    <span class="comment">//     &lt;div&gt;自定义类组件&lt;/div&gt;</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"></span><br><span class="line"><span class="comment">// 使用组件</span></span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="1）关于Fragment"><a href="#1）关于Fragment" class="headerlink" title="1）关于Fragment "></a><strong><font color='cornflowerblue'>1）关于Fragment </font></strong></h4><p>这个空标签被称作 Fragment。React Fragment 允许你将子元素分组，而不会在 HTML 结构中添加额外节点。</p><p>类似vue中的<code>&lt;templete&gt;&lt;/templete&gt;</code></p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">let</span> arr = [<span class="language-xml"><span class="tag">&lt;<span class="name">h1</span> <span class="attr">key</span>=<span class="string">&quot;1&quot;</span>&gt;</span>111<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>, <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span> <span class="attr">key</span>=<span class="string">&quot;2&quot;</span>&gt;</span>222<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>];</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>1<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>2<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h4 id="2）props"><a href="#2）props" class="headerlink" title="2）props"></a><strong><font color='cornflowerblue'>2）props</font></strong></h4><p>组件的属性类似于函数的参数，可以让组件接收外面的数据，展现出不同的结果。</p><blockquote><p>props是类组件⾃带的属性，代表所有属性的集合</p></blockquote><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><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">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> &#123; name &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>hello，&#123;name&#125; <span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">name</span>=<span class="string">&quot;张三&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">name</span>=<span class="string">&quot;李四&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">name</span>=<span class="string">&quot;王五&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">name</span>=<span class="string">&quot;赵六&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h5 id="a、props-children"><a href="#a、props-children" class="headerlink" title="a、props.children "></a><strong><font color='orange'>a、props.children </font></strong></h5><p>类似vue中的插槽</p><blockquote><p>如果在组件标签内写内容，通过props.children读取<br>如果传⼊单个内容，返回的就是⼀个对象，如果传⼊多个内容的话，返回的就是数组</p></blockquote><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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> &#123; children &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>一级标题<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;children&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>二级主题<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>这是内容<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Welcome</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line"><span class="comment">// 如果想把组件标签内的多个内容渲染到不同位置,通过数组索引访问即可</span></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">props</span>);</span><br><span class="line">    <span class="keyword">let</span> &#123; children &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;children[1]&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>一级标题<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;children[0]&#125;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h3</span>&gt;</span>二级主题<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">p</span>&gt;</span>这是内容<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">Welcome</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h4 id="3）state"><a href="#3）state" class="headerlink" title="3）state "></a><strong><font color='cornflowerblue'>3）state </font></strong></h4><ul><li>state是属于组件内部私有的，外部无法访问</li><li>state 用于存储组件的动态数据，当组件的 state 更新时，就会执行render函数，组件会重新渲染以更新页面</li></ul><p>使用state必须先定义初始值：</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><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="comment">// 定义初始状态的两种⽅式</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 1.在构造器⾥定义</span></span><br><span class="line"><span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span> = &#123;</span><br><span class="line">        <span class="attr">num</span>: <span class="number">1</span></span><br><span class="line">    &#125;</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">state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h5 id="a、何时使用状态？"><a href="#a、何时使用状态？" class="headerlink" title="a、何时使用状态？"></a><strong><font color='orange'>a、何时使用状态？</font></strong></h5><ul><li>组件中什么东西将来会变化，就把什么东西定义成状态</li><li>只要修改了状态，组件就会重新渲染</li></ul><h5 id="b、state-的使用"><a href="#b、state-的使用" class="headerlink" title="b、state 的使用"></a><strong><font color='orange'>b、state 的使用</font></strong></h5><p><strong>1、不能直接修改state，必须采取setState方法去更改</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  add = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 方式一：</span></span><br><span class="line">    <span class="comment">// this.setState(&#123;</span></span><br><span class="line">    <span class="comment">// 方式二：推荐 传入函数</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prevState</span>) =&gt;</span> (&#123;</span><br><span class="line">      <span class="attr">num</span>: prevState.<span class="property">num</span> + <span class="number">1</span>,</span><br><span class="line">    &#125;));</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.add&#125;</span>&gt;</span>点击增加<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><p><strong>2、数组和对象的更改</strong></p><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></pre></td><td class="code"><pre><span class="line"><span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">数组名: 新数组</span><br><span class="line">对象: 新对象</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">    <span class="attr">score</span>: [<span class="string">&quot;语文90&quot;</span>, <span class="string">&quot;数学99&quot;</span>],</span><br><span class="line">    <span class="attr">information</span>: &#123;</span><br><span class="line">      <span class="attr">name</span>: <span class="string">&quot;JACK&quot;</span>,</span><br><span class="line">      <span class="attr">age</span>: <span class="number">18</span>,</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">num</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span> + <span class="number">1</span>,</span><br><span class="line">      <span class="attr">score</span>: [...<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">score</span>, <span class="string">&quot;英语59&quot;</span>],</span><br><span class="line">      <span class="attr">information</span>: &#123; ...<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">information</span>, <span class="attr">links</span>: <span class="string">&quot;唱歌&quot;</span> &#125;,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num, score, information &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>成绩：&#123;score&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>信息：&#123;JSON.stringify(information)&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>修改<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><p><strong>3、state的更新可能是异步的（出于性能考虑）</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><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="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">num</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span> + <span class="number">1</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span>); <span class="comment">// 第一次点击修改时为：1 因此证明更新是异步的</span></span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>修改<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><p>如果想同步获取数据，那么可以使用setState的第⼆个参数</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><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">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(</span><br><span class="line">      &#123;</span><br><span class="line">        <span class="attr">num</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span> + <span class="number">1</span>,</span><br><span class="line">      &#125;,</span><br><span class="line">      <span class="function">() =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span>);</span><br><span class="line">      &#125;,</span><br><span class="line">    );</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>修改<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><p><strong>4、state的更新可能会被合并</strong></p><p>连续多次修改state，出于性能考虑，会只执行⼀次。</p><p>因为state中的值发生变化render函数就会执行一次，即页面渲染一次。如果这里遍历十次页面渲染十次就会影响性能。</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> index = <span class="number">0</span>; index &lt; <span class="number">10</span>; index++) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span>); <span class="comment">// 打印10个1</span></span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">        <span class="attr">num</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">num</span> + <span class="number">1</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="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span> // 页面展示的确是2</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>修改<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><p>如果不想合并多次更改state的操作，可以通过给setState传入函数的形式，返回新的状态</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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">num</span>: <span class="number">1</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">let</span> i = <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">setState</span>(<span class="function">(<span class="params">prev</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="comment">// 代表的是上⼀次更新的状态</span></span><br><span class="line">        <span class="variable language_">console</span>.<span class="title function_">log</span>(prev.<span class="property">num</span>); <span class="comment">// 1,2,3,4,5,6,7,8,9,10</span></span><br><span class="line">        <span class="keyword">return</span> &#123;</span><br><span class="line">          <span class="comment">// 返回值就是你想把什么状态改成什么</span></span><br><span class="line">          <span class="attr">num</span>: prev.<span class="property">num</span> + <span class="number">1</span>,</span><br><span class="line">        &#125;;</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="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; num &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;num&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>修改<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="4）类组件完整结构"><a href="#4）类组件完整结构" class="headerlink" title="4）类组件完整结构"></a><strong><font color='cornflowerblue'>4）类组件完整结构</font></strong></h4><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><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="keyword">class</span> <span class="title class_">Qq</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="comment">// 定义数据</span></span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">nickname</span>: <span class="string">&quot;爷傲奈我何&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// js逻辑</span></span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">nickname</span>: <span class="string">&quot;更换数据了&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="comment">// html</span></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; nickname &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;nickname&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>点击<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h2 id="三、事件处理"><a href="#三、事件处理" class="headerlink" title="三、事件处理"></a><strong><font color='red'>三、事件处理</font></strong></h2><h3 id="3-1、基本语法"><a href="#3-1、基本语法" class="headerlink" title="3.1、基本语法"></a><strong><font color='#10c300'>3.1、基本语法</font></strong></h3><ul><li>React 事件绑定属性的命名采用驼峰式写法，而不是小写</li><li>等号后面跟的不是字符串，而是函数名</li></ul><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="title function_">alert</span>(<span class="number">1</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="3-2、关于this指向问题"><a href="#3-2、关于this指向问题" class="headerlink" title="3.2、关于this指向问题"></a><strong><font color='#10c300'>3.2、关于this指向问题</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是一个状态&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); <span class="comment">// undefined</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><p><strong><code>这里的this是undefined</code></strong><br>react中的onClick是⼀个自定义的事件名，中间经历过⼀次赋值（onClick&#x3D;{this.fn}），就是把等号后面的函数名赋给了前面的onClick，所以导致this丢失。</p><p>以下代码可以演示为什么经历过⼀次赋值，this会丢失：</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 正常调用能取到this</span></span><br><span class="line"><span class="meta">&quot;use strict&quot;</span>;</span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">  <span class="attr">display</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line">obj.<span class="title function_">display</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// 将函数当做一个实参传给fn函数的形参，这里相当于一次赋值，丢失this</span></span><br><span class="line"><span class="keyword">let</span> obj = &#123;</span><br><span class="line">  <span class="attr">display</span>: <span class="keyword">function</span> (<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>); <span class="comment">// undefined</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">fn</span>(<span class="params">cb</span>) &#123;</span><br><span class="line">  <span class="title function_">cb</span>();</span><br><span class="line">&#125;</span><br><span class="line"><span class="title function_">fn</span>(obj.<span class="property">display</span>);</span><br></pre></td></tr></table></figure><br><h3 id="3-3、如何解决this丢失的问题？"><a href="#3-3、如何解决this丢失的问题？" class="headerlink" title="3.3、如何解决this丢失的问题？"></a><strong><font color='#10c300'>3.3、如何解决this丢失的问题？</font></strong></h3><h4 id="1）在构造器中绑定this"><a href="#1）在构造器中绑定this" class="headerlink" title="1）在构造器中绑定this"></a><strong><font color='cornflowerblue'>1）在构造器中绑定this</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">fn</span> = <span class="variable language_">this</span>.<span class="property">fn</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>); <span class="comment">// 在构造器⾥完成this绑定</span></span><br><span class="line">  &#125;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是一个状态&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">fn</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="2）调用的时候绑定"><a href="#2）调用的时候绑定" class="headerlink" title="2）调用的时候绑定 "></a><strong><font color='cornflowerblue'>2）调用的时候绑定 </font></strong></h4><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="variable language_">this</span>.<span class="property">fn</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>)&#125;&gt;按钮&lt;/button&gt;</span><br></pre></td></tr></table></figure><br><h4 id="3）使用箭头函数-最推荐"><a href="#3）使用箭头函数-最推荐" class="headerlink" title="3）使用箭头函数(最推荐) "></a><strong><font color='cornflowerblue'>3）使用箭头函数(最推荐) </font></strong></h4><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><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">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是一个状态&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 使⽤箭头函数</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="3-4、函数参数的传递"><a href="#3-4、函数参数的传递" class="headerlink" title="3.4、函数参数的传递"></a><strong><font color='#10c300'>3.4、函数参数的传递</font></strong></h3><blockquote><p>注意一定不能直接写 <code>函数名()</code>，这种是函数调用语句。<br>React 中的事件后面跟的是函数名或者函数体（匿名函数）。</p></blockquote><h4 id="1）传入匿名函数的形式"><a href="#1）传入匿名函数的形式" class="headerlink" title="1）传入匿名函数的形式"></a><strong><font color='cornflowerblue'>1）传入匿名函数的形式</font></strong></h4><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><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="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是一个状态&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">(<span class="params">val</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(val);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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">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;()</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">          this.fn(66);</span></span><br><span class="line"><span class="language-xml">        &#125;&#125;</span></span><br><span class="line"><span class="language-xml">      &gt;</span></span><br><span class="line"><span class="language-xml">        按钮</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><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="2）bind的方式"><a href="#2）bind的方式" class="headerlink" title="2）bind的方式"></a><strong><font color='cornflowerblue'>2）bind的方式</font></strong></h4><figure class="highlight jsx"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&lt;button onClick=&#123;<span class="variable language_">this</span>.<span class="property">fn</span>.<span class="title function_">bind</span>(<span class="variable language_">this</span>, <span class="number">5</span>)&#125;&gt;按钮&lt;/button&gt;</span><br></pre></td></tr></table></figure><br><h4 id="3）案例"><a href="#3）案例" class="headerlink" title="3）案例"></a><strong><font color='cornflowerblue'>3）案例</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Welcome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">bgColor</span>: <span class="string">&quot;rgb(255,255,255)&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> r = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">256</span>);</span><br><span class="line">    <span class="keyword">const</span> g = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">256</span>);</span><br><span class="line">    <span class="keyword">const</span> b = <span class="title class_">Math</span>.<span class="title function_">floor</span>(<span class="title class_">Math</span>.<span class="title function_">random</span>() * <span class="number">256</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">bgColor</span>: <span class="string">`rgb(<span class="subst">$&#123;r&#125;</span>,<span class="subst">$&#123;g&#125;</span>,<span class="subst">$&#123;b&#125;</span>)`</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> box = &#123;</span><br><span class="line">      <span class="attr">width</span>: <span class="string">&quot;100px&quot;</span>,</span><br><span class="line">      <span class="attr">height</span>: <span class="string">&quot;100px&quot;</span>,</span><br><span class="line">      <span class="attr">border</span>: <span class="string">&quot;1px solid #ccc&quot;</span>,</span><br><span class="line">      <span class="attr">background</span>: <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">bgColor</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">div</span> <span class="attr">style</span>=<span class="string">&#123;box&#125;</span>&gt;</span><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">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>切换<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h2 id="四、组件之间的传值"><a href="#四、组件之间的传值" class="headerlink" title="四、组件之间的传值"></a><strong><font color='red'>四、组件之间的传值</font></strong></h2><h3 id="4-1、父传子-props"><a href="#4-1、父传子-props" class="headerlink" title="4.1、父传子(props)"></a><strong><font color='#10c300'>4.1、父传子(props)</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是父组件的数据&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; msg &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是父组件<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Child</span> <span class="attr">a</span>=<span class="string">&#123;msg&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; a &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h3</span>&gt;</span>我是子组件，&#123;a&#125;<span class="tag">&lt;/<span class="name">h3</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">GrandChild</span> <span class="attr">b</span>=<span class="string">&#123;a&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">function</span> <span class="title function_">GrandChild</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h5</span>&gt;</span>我是孙子组件，&#123;props.b&#125;<span class="tag">&lt;/<span class="name">h5</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Parent</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="4-2、子传父-回调函数"><a href="#4-2、子传父-回调函数" class="headerlink" title="4.2、子传父(回调函数)"></a><strong><font color='#10c300'>4.2、子传父(回调函数)</font></strong></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><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><span class="line">38</span><br><span class="line">39</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Parent</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">(<span class="params">val</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">msg</span>: val,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是父组件, 这是子组件传来的数据：&#123;this.state.msg || &quot;______&quot;&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Child</span> <span class="attr">fn</span>=<span class="string">&#123;this.fn&#125;</span> /&gt;</span> // 1、将fn方法 通过方法传给子组件</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是子组件的数据&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn1 = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 2、子组件通过props接收fn方法 并且调用传参</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">props</span>.<span class="title function_">fn</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn1&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          我是子组件，点击可将子组件数据传给父组件</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 class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Parent</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="4-3、案例"><a href="#4-3、案例" class="headerlink" title="4.3、案例"></a><strong><font color='#10c300'>4.3、案例</font></strong></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Qq</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">nickname</span>: <span class="string">&quot;爷傲奈我何&quot;</span>,</span><br><span class="line">    <span class="attr">show</span>: <span class="string">&quot;none&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  edit = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">show</span>: <span class="string">&quot;block&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  nclose = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">show</span>: <span class="string">&quot;none&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  changeName = <span class="function">(<span class="params">nickname</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      nickname,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> &#123; nickname, show &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</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> <span class="attr">className</span>=<span class="string">&quot;box&quot;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;nickname&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.edit&#125;</span>&gt;</span>编辑<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Modal</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">nkname</span>=<span class="string">&#123;nickname&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">show</span>=<span class="string">&#123;show&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">nclose</span>=<span class="string">&#123;this.nclose&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">changeName</span>=<span class="string">&#123;this.changeName&#125;</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">div</span>&gt;</span></span></span><br><span class="line">    );</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">class</span> <span class="title class_">Modal</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">let</span> &#123; nkname, show, nclose, changeName &#125; = <span class="variable language_">this</span>.<span class="property">props</span>;</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> <span class="attr">className</span>=<span class="string">&quot;sbox&quot;</span> <span class="attr">style</span>=<span class="string">&#123;&#123;</span> <span class="attr">display:</span> <span class="attr">show</span> &#125;&#125;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">h1</span>&gt;</span>&#123;nkname&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;nclose&#125;</span>&gt;</span>关闭弹窗<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> changeName(&quot;不吃香菜&quot;)&#125;&gt;改名称<span class="tag">&lt;/<span class="name">button</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><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Qq</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h2 id="五、条件渲染"><a href="#五、条件渲染" class="headerlink" title="五、条件渲染"></a><strong><font color='red'>五、条件渲染</font></strong></h2><p>通常你的组件会需要根据不同的情况显示不同的内容。在 React 中，你可以通过使用 JavaScript 的 <code>if</code> 语句、<code>&amp;&amp;</code> 和 <code>? :</code> 运算符来选择性地渲染 JSX。</p><h3 id="5-1、基本写法"><a href="#5-1、基本写法" class="headerlink" title="5.1、基本写法"></a><strong><font color='#10c300'>5.1、基本写法</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Welcome</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="comment">// 这里也可以用解构 &#123;flag&#125;</span></span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">flag</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>欢迎您尊贵的会员<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>下午好，普通用户<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">flag</span>=<span class="string">&#123;true&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">flag</span>=<span class="string">&#123;false&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h3 id="5-2、可以返回null"><a href="#5-2、可以返回null" class="headerlink" title="5.2、可以返回null"></a><strong><font color='#10c300'>5.2、可以返回null</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Welcome</span>(<span class="params">props</span>) &#123;</span><br><span class="line">  <span class="keyword">if</span> (props.<span class="property">flag</span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">Vip</span> /&gt;</span></span>;</span><br><span class="line">  &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">null</span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Vip</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是尊贵的vip<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(</span><br><span class="line">  <span class="language-xml"><span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">flag</span>=<span class="string">&#123;true&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">flag</span>=<span class="string">&#123;false&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">  <span class="tag">&lt;/&gt;</span></span>,</span><br><span class="line">);</span><br></pre></td></tr></table></figure><br><h3 id="5-3、-运算符"><a href="#5-3、-运算符" class="headerlink" title="5.3、&amp;&amp;运算符"></a><strong><font color='#10c300'>5.3、&amp;&amp;运算符</font></strong></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><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Email</span>(<span class="params">&#123; msg &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> msg.<span class="property">length</span> &gt; <span class="number">0</span> &amp;&amp; <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>你有&#123;msg.length&#125;条消息待处理<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> arr = [<span class="string">&quot;下午吃什么&quot;</span>, <span class="string">&quot;晚上吃什么&quot;</span>, <span class="string">&quot;明天吃什么&quot;</span>];</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Email</span> <span class="attr">msg</span>=<span class="string">&#123;arr&#125;</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="5-4、三元表达式"><a href="#5-4、三元表达式" class="headerlink" title="5.4、三元表达式"></a><strong><font color='#10c300'>5.4、三元表达式</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">var</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">var</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Welcome</span>(<span class="params">&#123; flag &#125;</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> flag ? <span class="language-xml"><span class="tag">&lt;<span class="name">Vip</span> /&gt;</span></span> : <span class="language-xml"><span class="tag">&lt;<span class="name">Normal</span> /&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Vip</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是尊贵的vip<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Normal</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是黄金普通用户<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Welcome</span> <span class="attr">flag</span>=<span class="string">&#123;true&#125;</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h2 id="六、列表渲染"><a href="#六、列表渲染" class="headerlink" title="六、列表渲染"></a><strong><font color='red'>六、列表渲染</font></strong></h2><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Week</span>(<span class="params">&#123; courseList &#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">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      &#123;courseList.map((item, index) =&gt; (</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">li</span> <span class="attr">key</span>=<span class="string">&#123;item.id&#125;</span>&gt;</span>&#123;item.courseName&#125;<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      ))&#125;</span></span><br><span class="line"><span class="language-xml">    <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line">  );</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">const</span> courseList = [</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;1&quot;</span>,</span><br><span class="line">    <span class="attr">courseName</span>: <span class="string">&quot;第一节课&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;2&quot;</span>,</span><br><span class="line">    <span class="attr">courseName</span>: <span class="string">&quot;第二节课&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;3&quot;</span>,</span><br><span class="line">    <span class="attr">courseName</span>: <span class="string">&quot;第三节课&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">  &#123;</span><br><span class="line">    <span class="attr">id</span>: <span class="string">&quot;4&quot;</span>,</span><br><span class="line">    <span class="attr">courseName</span>: <span class="string">&quot;第四节课&quot;</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">];</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Week</span> <span class="attr">courseList</span>=<span class="string">&#123;courseList&#125;</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><p><strong><code>为什么需要key？</code></strong></p><p>key是用来给列表中的每一项做标记，后续更新只更新有差别的部分，不变的部分就不更新</p><p>不要用index当key，key要求是独一无二的字符串，一般由后端返回</p><figure class="highlight js"><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 在原生js中，假如有一个渲染的列表如下：</span></span><br><span class="line">&lt;li&gt;<span class="number">1</span>&lt;/li&gt;</span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>2<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>3<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>4<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="comment">// 当数据发生变化，变成：</span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>1<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>2<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>4<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml"><span class="tag">&lt;<span class="name">li</span>&gt;</span>3<span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="comment">// 对于js来说不会因为只变了一个就只删除这一个然后创建一个新的，而是整个列表都删除，创建新的全部重新创建，这样列表数据多了就会造成性能问题</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 在react和vue中这种列表如果变化了，不会立马去更新。因为react和vue有一个虚拟dom，每次列表数据更新，就会先去更新虚拟dom（更新很快），然后和真实dom做对比（这种对比就是diff算法）去更新改动的地方。</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// key是用来给列表中的每一项做标记，后续更新只更新有差别的部分，不变的部分就不更新</span></span><br></pre></td></tr></table></figure><p><strong><code>不要用index当key，key要求是独一无二的字符串</code></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202507160102927.png" alt="image-20250716010216779"></p><figure class="highlight js"><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">// 为什么不用index索引去当key呢？</span></span><br><span class="line">如果用索引当key，那么图一列表以此是<span class="number">012345</span>。如果某天将列表第二个删除了(图<span class="number">2</span>)，那么是不是列表第一个之后的key全部都发生变化了，原来列表内容<span class="number">3</span>的key是<span class="number">2</span>，现在列表内容3key是<span class="number">1</span>了，这时候需求demo和真实dom一对比，会去更新变化的地方即列表内容<span class="number">2</span> <span class="number">3</span> <span class="number">4</span> <span class="number">5</span> <span class="number">6</span>的都要去做更新，如果又后端返回一个唯一id值，那么就不存在这种问题</span><br></pre></td></tr></table></figure><br><h2 id="七、表单处理"><a href="#七、表单处理" class="headerlink" title="七、表单处理"></a><strong><font color='red'>七、表单处理</font></strong></h2><p>在react中表单的处理通常和其他元素不一样， 正常情况下我们把html中会发上变化的地方都设置为state，然后通过更改state的更新视图</p><p>在 HTML 中，表单元素<code>（如&lt;input&gt;、 &lt;textarea&gt; 和 &lt;select&gt;）</code>通常自己维护 state，并根据用户输入进行更新。<br>而在 React 中，可变状态（mutable state）通常保存在组件的 state 属性中，并且只能通过使用 setState()来更新。</p><p>渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。</p><br><h3 id="7-1、基本使用"><a href="#7-1、基本使用" class="headerlink" title="7.1、基本使用"></a><strong><font color='#10c300'>7.1、基本使用</font></strong></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><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="keyword">class</span> <span class="title class_">Input</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  handleChange = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(e.<span class="property">target</span>.<span class="property">value</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">value</span>: e.<span class="property">target</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">input</span> <span class="attr">onChange</span>=<span class="string">&#123;this.handleChange&#125;</span> <span class="attr">value</span>=<span class="string">&#123;this.state.value&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;this.state.value&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="7-2、多个表单元素"><a href="#7-2、多个表单元素" class="headerlink" title="7.2、多个表单元素"></a><strong><font color='#10c300'>7.2、多个表单元素</font></strong></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Input</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">userValue</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    <span class="attr">emailValue</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  handleChange = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">const</span> name = e.<span class="property">target</span>.<span class="property">name</span> === <span class="string">&quot;user&quot;</span> ? <span class="string">&quot;userValue&quot;</span> : <span class="string">&quot;emailValue&quot;</span>;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      [name]: e.<span class="property">target</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">userValue</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">emailValue</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&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">onChange</span>=<span class="string">&#123;this.handleChange&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">value</span>=<span class="string">&#123;this.state.userValue&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;user&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">br</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">onChange</span>=<span class="string">&#123;this.handleChange&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">value</span>=<span class="string">&#123;this.state.emailValue&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;email&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;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="7-3、select、checkbox、radio等特殊元素"><a href="#7-3、select、checkbox、radio等特殊元素" class="headerlink" title="7.3、select、checkbox、radio等特殊元素"></a><font color='red' size="">7.3、select、checkbox、radio等特殊元素</font></h3><h4 id="1）select用法"><a href="#1）select用法" class="headerlink" title="1）select用法"></a><strong><font color='cornflowerblue'>1）select用法</font></strong></h4><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><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="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Input</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&quot;shanghai&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">value</span>: e.<span class="property">target</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">select</span> <span class="attr">value</span>=<span class="string">&#123;this.state.value&#125;</span> <span class="attr">onChange</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">&quot;beijing&quot;</span>&gt;</span>北京<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">&quot;shanghai&quot;</span>&gt;</span>上海<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          <span class="tag">&lt;<span class="name">option</span> <span class="attr">value</span>=<span class="string">&quot;shenzhen&quot;</span>&gt;</span>深圳<span class="tag">&lt;/<span class="name">option</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">select</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="2）checkbox用法"><a href="#2）checkbox用法" class="headerlink" title="2）checkbox用法"></a><strong><font color='cornflowerblue'>2）checkbox用法</font></strong></h4><p><strong><code>操作他的checked属性</code></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><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">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Input</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">isChecked</span>: <span class="literal">false</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">isChecked</span>: e.<span class="property">target</span>.<span class="property">checked</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&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">type</span>=<span class="string">&quot;checkbox&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;1&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">value</span>=<span class="string">&quot;beijing&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">checked</span>=<span class="string">&#123;this.state.isChecked&#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;this.change&#125;</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;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="3）radio的用法"><a href="#3）radio的用法" class="headerlink" title="3）radio的用法"></a><strong><font color='cornflowerblue'>3）radio的用法</font></strong></h4><p><strong><code>通过更改value，间接操作checked属性</code></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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Input</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">n</span>: <span class="string">&quot;option2&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">n</span>: e.<span class="property">target</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&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">type</span>=<span class="string">&quot;radio&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;city&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">value</span>=<span class="string">&quot;option1&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">checked</span>=<span class="string">&#123;this.state.n</span> == <span class="string">&quot;option1&quot;</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onChange</span>=<span class="string">&#123;this.change&#125;</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">input</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;radio&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">name</span>=<span class="string">&quot;city&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">value</span>=<span class="string">&quot;option2&quot;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">checked</span>=<span class="string">&#123;this.state.n</span> == <span class="string">&quot;option2&quot;</span>&#125;</span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">onChange</span>=<span class="string">&#123;this.change&#125;</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;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Input</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h2 id="八、ref"><a href="#八、ref" class="headerlink" title="八、ref"></a><strong><font color='red'>八、ref</font></strong></h2><blockquote><p>什么是ref：ref是React提供的用来操纵React组件实例或者DOM元素的接口。</p><p>基本跟vue中的ref用法一样 <strong><code>ref拿到的是真实dom </code></strong></p></blockquote><p>简单来说，就是提供了一种方式能让你直接获取到dom元素对象或者组件实例。</p><p><strong><code>ref不能用于函数组件上（函数组件没有实例），ref只适用于实例对象上，例如class类</code></strong></p><h3 id="8-1、回调形式的ref-（老版本推荐）"><a href="#8-1、回调形式的ref-（老版本推荐）" class="headerlink" title="8.1、回调形式的ref （老版本推荐）"></a><strong><font color='#10c300'>8.1、回调形式的ref （老版本推荐）</font></strong></h3><h4 id="1）在元素中"><a href="#1）在元素中" class="headerlink" title="1）在元素中"></a><strong><font color='cornflowerblue'>1）在元素中</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  test = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">ins</span>); <span class="comment">// 获取到p标签这个dom</span></span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">ref</span>=<span class="string">&#123;(dom)</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">            this.ins = dom;</span></span><br><span class="line"><span class="language-xml">          &#125;&#125;</span></span><br><span class="line"><span class="language-xml">        &gt;</span></span><br><span class="line"><span class="language-xml">          你好啊</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.test&#125;</span>&gt;</span>测试<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Dome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="2）在组件中"><a href="#2）在组件中" class="headerlink" title="2）在组件中"></a><strong><font color='cornflowerblue'>2）在组件中</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Dome</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  test = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">ins</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">ins1</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">ins1</span>.<span class="title function_">fn</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">ref</span>=<span class="string">&#123;(dom)</span> =&gt;</span> &#123;</span></span><br><span class="line"><span class="language-xml">            this.ins = dom;</span></span><br><span class="line"><span class="language-xml">          &#125;&#125;</span></span><br><span class="line"><span class="language-xml">        &gt;</span></span><br><span class="line"><span class="language-xml">          你好啊</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.test&#125;</span>&gt;</span>测试<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;(dom)</span> =&gt;</span> (this.ins1 = dom)&#125; /&gt;</span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是子组件的数据&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">666666666</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>我是子组件<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Dome</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="8-2、React-createRef-（适用于类组件）"><a href="#8-2、React-createRef-（适用于类组件）" class="headerlink" title="8.2、React.createRef （适用于类组件） "></a><strong><font color='#10c300'>8.2、React.createRef （适用于类组件） </font></strong></h3><blockquote><p>通过在class中使用React.createRef()方法创建一些变量，可以将这些变量绑定到标签的ref中</p><p>那么该变量的current则指向绑定的标签dom</p></blockquote><h4 id="1）在元素中-1"><a href="#1）在元素中-1" class="headerlink" title="1）在元素中"></a><strong><font color='cornflowerblue'>1）在元素中</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  myRef = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">myRef</span>.<span class="property">current</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">ref</span>=<span class="string">&#123;this.myRef&#125;</span>&gt;</span>这是一个组件<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Demo</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h4 id="2）在组件中-1"><a href="#2）在组件中-1" class="headerlink" title="2）在组件中"></a><strong><font color='cornflowerblue'>2）在组件中</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  myRef = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line">  myRef1 = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">myRef</span>.<span class="property">current</span>);</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="variable language_">this</span>.<span class="property">myRef1</span>.<span class="property">current</span>.<span class="property">state</span>.<span class="property">msg</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">myRef1</span>.<span class="property">current</span>.<span class="title function_">childFn</span>();</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">p</span> <span class="attr">ref</span>=<span class="string">&#123;this.myRef&#125;</span>&gt;</span>这是一个组件<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Child</span> <span class="attr">ref</span>=<span class="string">&#123;this.myRef1&#125;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Child</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;我是一个子组件数据&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  childFn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="number">6666666</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>我是子组件<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Demo</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h3 id="8-3、useRef-（适用于函数组件）"><a href="#8-3、useRef-（适用于函数组件）" class="headerlink" title="8.3、useRef （适用于函数组件） "></a><strong><font color='#10c300'>8.3、useRef （适用于函数组件） </font></strong></h3><p>是一个hooks，后面讲</p><br><h3 id="8-4、综合案例"><a href="#8-4、综合案例" class="headerlink" title="8.4、综合案例 "></a><strong><font color='#10c300'>8.4、综合案例 </font></strong></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> container = <span class="variable language_">document</span>.<span class="title function_">getElementById</span>(<span class="string">&quot;app&quot;</span>);</span><br><span class="line"><span class="keyword">const</span> root = <span class="title class_">ReactDOM</span>.<span class="title function_">createRoot</span>(container);</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  myRef = <span class="title class_">React</span>.<span class="title function_">createRef</span>();</span><br><span class="line"></span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">value</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    <span class="attr">list</span>: [],</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">(<span class="params">e</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">value</span>: e.<span class="property">target</span>.<span class="property">value</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  add = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="keyword">if</span> (<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">value</span>) &#123;</span><br><span class="line">      <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">        <span class="attr">list</span>: [...<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">list</span>, <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">value</span>],</span><br><span class="line">        <span class="attr">value</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">      &#125;);</span><br><span class="line">      <span class="variable language_">this</span>.<span class="property">myRef</span>.<span class="property">current</span>.<span class="title function_">focus</span>();</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;;</span><br><span class="line">  del = <span class="function">(<span class="params">index</span>) =&gt;</span> &#123;</span><br><span class="line">    <span class="comment">// 注意：在 React 中不建议直接修改原状态（splice 会修改原数组）</span></span><br><span class="line">    <span class="comment">// 推荐做法：const newList = this.state.list.filter((_, i) =&gt; i !== index);</span></span><br><span class="line">    <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">list</span>.<span class="title function_">splice</span>(index, <span class="number">1</span>);</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">list</span>: [...<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">list</span>],</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">const</span> &#123; list &#125; = <span class="variable language_">this</span>.<span class="property">state</span>;</span><br><span class="line">    <span class="keyword">return</span> (</span><br><span class="line">      <span class="language-xml"><span class="tag">&lt;&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;this.state.value&#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;this.change&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">ref</span>=<span class="string">&#123;this.myRef&#125;</span></span></span></span><br><span class="line"><span class="tag"><span class="language-xml">          <span class="attr">type</span>=<span class="string">&quot;text&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">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.add&#125;</span>&gt;</span>添加<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">          &#123;list.length &gt; 0 &amp;&amp;</span></span><br><span class="line"><span class="language-xml">            list.map((item, index) =&gt; (</span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;<span class="name">li</span> <span class="attr">onClick</span>=<span class="string">&#123;()</span> =&gt;</span> this.del(index)&#125; key=&#123;index&#125;&gt;</span></span><br><span class="line"><span class="language-xml">                &#123;item&#125;</span></span><br><span class="line"><span class="language-xml">              <span class="tag">&lt;/<span class="name">li</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            ))&#125;</span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;/<span class="name">ul</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br><span class="line">root.<span class="title function_">render</span>(<span class="language-xml"><span class="tag">&lt;<span class="name">Demo</span> /&gt;</span></span>);</span><br></pre></td></tr></table></figure><br><h2 id="九、脚手架搭建项目"><a href="#九、脚手架搭建项目" class="headerlink" title="九、脚手架搭建项目"></a><strong><font color='red'>九、脚手架搭建项目</font></strong></h2><h3 id="9-1、基于webpack创建项目"><a href="#9-1、基于webpack创建项目" class="headerlink" title="9.1、基于webpack创建项目 "></a><strong><font color='#10c300'>9.1、基于webpack创建项目 </font></strong></h3><p>基于webpack创建是使用create-react-app搭建项目，Create React App 是一个官方支持的创建 React 单页应用程序的方法。</p><h4 id="1）安装"><a href="#1）安装" class="headerlink" title="1）安装"></a><strong><font color='cornflowerblue'>1）安装</font></strong></h4><figure class="highlight js"><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">// 方式一</span></span><br><span class="line"><span class="comment">//全局安装脚手架</span></span><br><span class="line">npm install -g create-react-app</span><br><span class="line"><span class="comment">//利用脚手架创建项目</span></span><br><span class="line">create-react-app my-app</span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式二（推荐）</span></span><br><span class="line">npx create-react-app my-app</span><br><span class="line">cd my-app</span><br><span class="line">npm start</span><br></pre></td></tr></table></figure><p>npx 是 npm5.2.0版本新增的一个工具包，定义为npm包的执行者，相比 npm，npx 会自动安装依赖包并执行某个命令。</p><p>使用npx创建项目，创建的时候会检查电脑是否有create-react-app，没有就安装create-react-app，有就跳过。</p><br><h4 id="2）生成的项目文件解读"><a href="#2）生成的项目文件解读" class="headerlink" title="2）生成的项目文件解读"></a><strong><font color='#10c300'>2）生成的项目文件解读</font></strong></h4><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202507201844527.png" alt="image-20250720184443319"></p><ul><li><p>node_modules:项目的核心模块，依赖包</p></li><li><p>public：存放静态资源文件</p><ul><li>.ico:页签的logo</li><li>index.html:唯一的页面文件，只提供根节点</li><li>manifest.json:移动端的配置一文件</li><li>robots.txt:告诉爬虫者，不可爬的页面，没有实质作用只是警告</li></ul></li><li><p>.gitignore:声明一些再在git上传的时候需要忽略的文件</p></li><li><p>package.json:项目的说明文件，有哪些依赖，依赖了哪个版本</p></li><li><p>package-lock.json：项目依赖的安装包的一些版本会做一些限制，进行版本锁定</p></li><li><p>Readme.md:作者的一些话</p></li><li><p>src</p><ul><li>App.css:App组件的样式文件</li><li>App.js 项目的根组件</li><li>App.test.js：自动化测试文件</li><li>index.css全局样式文件</li><li>index.js：项目的入口文件，html只会加载这个js文件(类似vue中main.js)</li><li>reportWebVitals.js:谷歌浏览器退出的一个浏览器性能优化的库</li><li>setupTests：针对index.js的单元测试原件</li></ul></li></ul><blockquote><p>React.StrictMode的作用</p><p>1.识别一些不安全的生命周期</p><p>2.检测意外的副作用</p><p>3.检测过时的context api</p></blockquote><br><h3 id="9-2、基于Vite创建项目"><a href="#9-2、基于Vite创建项目" class="headerlink" title="9.2、基于Vite创建项目 "></a><strong><font color='#10c300'>9.2、基于Vite创建项目 </font></strong></h3><p><strong><code>node 版本要大于 18 以上，建议用 20 版本，否则运行会报错</code></strong></p><p>使用 NPM:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm create vite@latest</span><br></pre></td></tr></table></figure><p>使用 Yarn:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarn create vite</span><br></pre></td></tr></table></figure><p>使用 PNPM:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm create vite</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202507201845867.png" alt="image-20250720184501683"></p><br><h3 id="9-3、项目的基本使用"><a href="#9-3、项目的基本使用" class="headerlink" title="9.3、项目的基本使用 "></a><strong><font color='#10c300'>9.3、项目的基本使用 </font></strong></h3><blockquote><p>组件的后缀名我们推荐用jsx，表示是一个组件</p><p>普通js后缀名就用.js</p></blockquote><p>默认引入：</p><blockquote><p>引入的时候可以不需要加文件后缀，编辑器会自动查找.jsx后缀的文件，如果找不到，会接着查找后缀为.js的文件</p><p>如果直接引入的是文件夹的名字，那么默认去查找该文件夹下的index.jsx文件。</p></blockquote><br><h2 id="十、模块化样式"><a href="#十、模块化样式" class="headerlink" title="十、模块化样式"></a><strong><font color='red'>十、模块化样式</font></strong></h2><h3 id="10-1、xxx-module-css"><a href="#10-1、xxx-module-css" class="headerlink" title="10.1、xxx.module.css "></a><strong><font color='#10c300'>10.1、xxx.module.css </font></strong></h3><p><strong>只需要将样式文件名字改 ：名字.module.css即可</strong></p><p>CSS Modules 允许通过自动创建 [filename]_[classname]__[hash] 格式的唯一 classname 来确定 CSS 的作用域,如下</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><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./Child&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> style <span class="keyword">from</span> <span class="string">&quot;./App.module.css&quot;</span>;</span><br><span class="line"><span class="keyword">function</span> <span class="title function_">App</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">h1</span> <span class="attr">className</span>=<span class="string">&#123;style.App&#125;</span>&gt;</span>你好<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">h2</span> <span class="attr">className</span>=<span class="string">&#123;style.title&#125;</span>&gt;</span>哈哈哈哈哈<span class="tag">&lt;/<span class="name">h2</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;<span class="name">Child</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><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br><h3 id="10-2、预处理器（Sass-Less-Stylus）"><a href="#10-2、预处理器（Sass-Less-Stylus）" class="headerlink" title="10.2、预处理器（Sass&#x2F;Less&#x2F;Stylus） "></a><strong><font color='#10c300'>10.2、预处理器（Sass&#x2F;Less&#x2F;Stylus） </font></strong></font></h3><figure class="highlight scss"><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="variable">$text-color</span>: blue;</span><br><span class="line"><span class="selector-class">.component</span> &#123;</span><br><span class="line">  <span class="attribute">color</span>: <span class="variable">$text-color</span>;</span><br><span class="line">  <span class="attribute">font-size</span>: <span class="number">20px</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><figure class="highlight js"><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">// Component.js</span></span><br><span class="line"><span class="keyword">import</span> <span class="string">&quot;./Component.scss&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span> <span class="attr">className</span>=<span class="string">&quot;component&quot;</span>&gt;</span>Styled Text<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h3 id="10-3、css-in-js库"><a href="#10-3、css-in-js库" class="headerlink" title="10.3、css-in-js库 "></a><strong><font color='#10c300'>10.3、css-in-js库 </font></strong></h3><p>使用JS编写CSS的库，如styled-components、emotion等</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><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 使用styled-components</span></span><br><span class="line"><span class="keyword">import</span> styled <span class="keyword">from</span> <span class="string">&quot;styled-components&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">StyledDiv</span> = styled.<span class="property">div</span><span class="string">`</span></span><br><span class="line"><span class="string">  color: blue;</span></span><br><span class="line"><span class="string">  font-size: 20px;</span></span><br><span class="line"><span class="string">`</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">Component</span>(<span class="params"></span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">StyledDiv</span>&gt;</span>Styled Text<span class="tag">&lt;/<span class="name">StyledDiv</span>&gt;</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h2 id="十一、组件的生命周期"><a href="#十一、组件的生命周期" class="headerlink" title="十一、组件的生命周期"></a><strong><font color='red'>十一、组件的生命周期</font></strong></h2><h3 id="11-1、生命周期图谱"><a href="#11-1、生命周期图谱" class="headerlink" title="11.1、生命周期图谱 "></a><strong><font color='#10c300'>11.1、生命周期图谱 </font></strong></h3><p>组件的生命周期函数就是在特定时间节点上会自动运行的函数</p><p><strong><code>重点：函数组件没有生命周期</code></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202507202212214.png" alt="image-20240416135406354"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202507202212318.png" alt="image-20240416135418172"></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./Child&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">constructor</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">super</span>();</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;constructor&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">    <span class="attr">info</span>: <span class="string">&quot;hahahaha&quot;</span>,</span><br><span class="line">    <span class="attr">flag</span>: <span class="literal">true</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">componentDidUpdate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//组件更新完毕的时候执行</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;componentDidUpdate&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//组件挂载完毕的时候执行</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;componentDidMount&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentWillUnmount</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="comment">//组件即将卸载的时候执行</span></span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;componentWillUnmount&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">flag</span>: <span class="literal">false</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  change = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">info</span>: <span class="string">&quot;呵呵呵呵&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;render&quot;</span>);</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">h1</span>&gt;</span>我是App组件&#123;this.state.msg&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.change&#125;</span>&gt;</span>改info<span class="tag">&lt;/<span class="name">button</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        &#123;this.state.flag &amp;&amp; <span class="tag">&lt;<span class="name">Child</span> /&gt;</span>&#125;</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><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br><h3 id="11-2、shouldComponentUpdate"><a href="#11-2、shouldComponentUpdate" class="headerlink" title="11.2、shouldComponentUpdate "></a><strong><font color='#10c300'>11.2、shouldComponentUpdate </font></strong></h3><p>shouldComponentUpdate 是一个可以在**<code>组件更新之前</code>**触发的生命周期方法，它允许你通过返回一个布尔值来决定一个组件的输出是否受当前状态或属性的改变影响而更新。如果 shouldComponentUpdate 返回 false，那么组件就不会进行更新。</p><p>简单来说就是用于更新数据时，如果更新的数据和上一次数据是一样的，理论上应该不需要再次更新组件了，但是实际上确实会更新，使用shouldComponentUpdate 则会避免这种情况，如果shouldComponentUpdate 返回false就说明数据一样，那么组件就不需要更新。</p><h4 id="1）基本用法"><a href="#1）基本用法" class="headerlink" title="1）基本用法"></a><strong><font color='cornflowerblue'>1）基本用法</font></strong></h4><p>shouldComponentUpdate 接收两个参数：nextProps 和 nextState，分别表示组件即将接收的新属性和新状态。你需要在这个方法中比较当前组件的属性和状态与新的属性和状态，然后返回一个布尔值来决定组件是否需要更新。</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></pre></td><td class="code"><pre><span class="line"><span class="title function_">shouldComponentUpdate</span>(<span class="params">nextProps,nextState</span>)&#123;</span><br><span class="line">  <span class="keyword">if</span>(nextState.<span class="property">msg</span>===<span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>)&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">  &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">  &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><br><h4 id="2）案例"><a href="#2）案例" class="headerlink" title="2）案例"></a><strong><font color='cornflowerblue'>2）案例</font></strong></h4><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">msg</span>: <span class="string">&quot;world&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">shouldComponentUpdate</span>(<span class="params">nextProps, nextState</span>) &#123;</span><br><span class="line">    <span class="keyword">if</span> (nextState.<span class="property">msg</span> === <span class="variable language_">this</span>.<span class="property">state</span>.<span class="property">msg</span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">false</span>;</span><br><span class="line">    &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="literal">true</span>;</span><br><span class="line">    &#125;</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidUpdate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;组件更新了&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;render&quot;</span>);</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">h1</span>&gt;</span>我是App组件&#123;this.state.msg&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</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><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><p><code>自React 16.3版本起，推荐使用 PureComponent（对于类组件） 或 React.memo (对于函数组件) 来进行浅比较，这样可以减少手动编写 shouldComponentUpdate 的需求。</code></p><br><h3 id="11-3、PureComponent"><a href="#11-3、PureComponent" class="headerlink" title="11.3、PureComponent "></a><strong><font color='#10c300'>11.3、PureComponent </font></strong></h3><p>React.PureComponent 与 React.Component 很相似，两者的区别在于 React.Component 并未实现 shouldComponentUpdate()，而 React.PureComponent 中以浅层对比 prop 和 state 的方式来实现了该函数。</p><p><strong><font color='orange'>setState存在两个不合理之处:</font></strong></p><ol><li><p>setState无论是否更新了state，render函数都会重现调用，这是不合理的</p></li><li><p>如果父组件更新了，无论子组件用没用到父组件的数据也都会重新渲染子组件，这是不合理的</p></li></ol><br><p><strong><font color='orange'>传统的解决方案：</font></strong></p><p>通过生命周期函数shouldComponentUpdate(){}判断两次不一致再更新，否则不更新</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></pre></td><td class="code"><pre><span class="line">  <span class="title function_">shouldComponentUpdate</span>(<span class="params">nextProps,nextState</span>)&#123;</span><br><span class="line">        <span class="keyword">if</span>(<span class="variable language_">this</span>.<span class="property">props</span>.<span class="property">someprops</span>===nextProps.<span class="property">someprops</span>)&#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">false</span></span><br><span class="line">        &#125;<span class="keyword">else</span>&#123;</span><br><span class="line">          <span class="keyword">return</span> <span class="literal">true</span></span><br><span class="line">        &#125;</span><br><span class="line">  &#125;</span><br><span class="line"><span class="comment">// 但是这仅仅是一个属性，如果有多个属性的话，一个一个对比会比较麻烦</span></span><br></pre></td></tr></table></figure><br><p><strong><font color='orange'>因此使用PureComponent：</font></strong></p><p>直接将React.Component替换成React.PureComponent</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Mouse</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">  <span class="comment">// 与之前写法相同，只不过不用写shouldComponentUpdate了，会自动帮我们判断</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// app.jsx</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;hello&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line">  fn = <span class="function">() =&gt;</span> &#123;</span><br><span class="line">    <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">      <span class="attr">msg</span>: <span class="string">&quot;world&quot;</span>,</span><br><span class="line">    &#125;);</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">componentDidUpdate</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;组件更新了&quot;</span>);</span><br><span class="line">  &#125;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;render&quot;</span>);</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">h1</span>&gt;</span>我是App组件&#123;this.state.msg&#125;<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">button</span> <span class="attr">onClick</span>=<span class="string">&#123;this.fn&#125;</span>&gt;</span>按钮<span class="tag">&lt;/<span class="name">button</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><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br><p><strong><font color='orange'>注意：</font></strong></p><p>React.PureComponent 中的 shouldComponentUpdate() 仅作对象的浅层比较。如果对象中包含复杂的数据结构，则有可能因为无法检查深层的差别，产生错误的比对结果。仅在你的 props 和 state 较为简单时，才使用React.PureComponent，或者在深层数据结构发生变化时调用 forceUpdate() 来确保组件被正确地更新。</p><br><h2 id="十二、context多层级传值"><a href="#十二、context多层级传值" class="headerlink" title="十二、context多层级传值"></a><strong><font color='red'>十二、context多层级传值</font></strong></h2><p>Context 提供了一个无需为每层组件手动添加 props，就能在组件树间进行数据传递的方法。</p><p><strong><font color='orange'>使用步骤</font></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 1.创建需要传递的数据React.Context()实例：括号里存放默认初始数据</span></span><br><span class="line"><span class="comment">// src\utils\context.js</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyContext</span>=<span class="title class_">React</span>.<span class="title function_">createContext</span>(<span class="string">&quot;&quot;</span>);</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">MyContext</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 2.父组件  提供Context数据：</span></span><br><span class="line"><span class="comment">// src\App.jsx</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">Child</span> <span class="keyword">from</span> <span class="string">&quot;./components/Child&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">MyContext</span> <span class="keyword">from</span> <span class="string">&quot;./utils/context&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">    state = &#123;</span><br><span class="line">        <span class="attr">msg</span>: <span class="string">&quot;父组件数据&quot;</span>,</span><br><span class="line">    &#125;;</span><br><span class="line"></span><br><span class="line">    <span class="title function_">render</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">MyContext.Provider</span> <span class="attr">value</span>=<span class="string">&#123;this.state.msg&#125;</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                    <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是父组件<span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                    <span class="tag">&lt;<span class="name">Child</span> <span class="attr">msg</span>=<span class="string">&#123;this.state.msg&#125;/</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;/<span class="name">MyContext.Provider</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><span class="line">&#125;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">// 3.后代组件获取Context数据</span></span><br><span class="line"><span class="comment">// src\components\GrandChild.jsx</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式一：通过consumer</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&#x27;react&#x27;</span></span><br><span class="line"><span class="keyword">import</span> <span class="title class_">MyContext</span> <span class="keyword">from</span> <span class="string">&#x27;../utils/context&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GrandChild</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">    <span class="title function_">render</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">MyContext.Consumer</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                &#123;</span></span><br><span class="line"><span class="language-xml">                    (value) =&gt; (</span></span><br><span class="line"><span class="language-xml">                        <span class="tag">&lt;&gt;</span></span></span><br><span class="line"><span class="language-xml">                            <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是孙子组件 <span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                            <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;value&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                        <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">                    )</span><br><span class="line">                &#125;</span><br><span class="line">            &lt;/<span class="title class_">MyContext</span>.<span class="property">Consumer</span>&gt;</span><br><span class="line">        )</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">export</span> <span class="keyword">default</span> <span class="title class_">GrandChild</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 方式二：通过Class.contextType (类组件使用)</span></span><br><span class="line">可以通过<span class="title class_">Class</span>.<span class="property">contextType</span>直接将<span class="title class_">Context</span>对象挂载到<span class="keyword">class</span>的contextType属性，然后就可以使用<span class="variable language_">this</span>.<span class="property">context</span>对context对象进行使用</span><br><span class="line">contextType属于类的属性，不属于某个实例，所以只能在外部添加或者使用<span class="keyword">static</span>将其添加给类属性</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">GrandChild</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">    <span class="comment">// static contextType=MyContext; // 这里写在类里面并没有添加到实例上去，因为添加了static</span></span><br><span class="line">    <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">h1</span>&gt;</span>我是孙子组件 <span class="tag">&lt;/<span class="name">h1</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">                <span class="tag">&lt;<span class="name">p</span>&gt;</span>&#123;this.context&#125;<span class="tag">&lt;/<span class="name">p</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">            <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">        )</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="title class_">GrandChild</span>.<span class="property">contextType</span> = <span class="title class_">MyContext</span>; <span class="comment">// 添加在类属性上面</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> <span class="title class_">GrandChild</span></span><br></pre></td></tr></table></figure><br><h2 id="十三、高阶组件-HOC"><a href="#十三、高阶组件-HOC" class="headerlink" title="十三、高阶组件 (HOC)"></a><strong><font color='red'>十三、高阶组件 (HOC)</font></strong></h2><p>高阶组件（Higher-Order Component，简称HOC）是React中用于复用组件逻辑的一种高级技术。本质上，高阶组件是一个函数，它接收一个组件并返回一个新的组件。它主要用于逻辑的共享和重用，而不是直接渲染UI。这种模式类似于JavaScript中的高阶函数，那些以函数为参数或返回一个函数的函数。</p><ul><li>HOC 应当是纯函数，无副作用。</li><li>不要在 HOC 内部修改原始组件，而是返回一个新组件</li><li>高阶组件的命名一般<strong>以 “with” 开头</strong>，表示它是为组件提供附加功能的。</li></ul><h3 id="13-1、基本使用"><a href="#13-1、基本使用" class="headerlink" title="13.1、基本使用 "></a><strong><font color='#10c300'>13.1、基本使用 </font></strong></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Demo1</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>我是demo1 &#123;this.props.a&#125;<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</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">class</span> <span class="title class_">Demo2</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">div</span>&gt;</span>我是demo2<span class="tag">&lt;/<span class="name">div</span>&gt;</span></span>;</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">function</span> <span class="title function_">withLog</span>(<span class="params">WrapComponent</span>) &#123;</span><br><span class="line">  <span class="comment">// 高阶函数</span></span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">extends</span> <span class="title class_">React</span>.<span class="property">Component</span> &#123;</span><br><span class="line">    <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;挂载了&quot;</span>);</span><br><span class="line">    &#125;</span><br><span class="line">    <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">WrapComponent</span> &#123;<span class="attr">...this.props</span>&#125; /&gt;</span></span>; <span class="comment">// 传值必须在这里解构props，要不然Demo1组件拿不到传的参数a和c</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><br><span class="line"><span class="comment">// 使用高阶函数</span></span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyCom</span> = <span class="title function_">withLog</span>(<span class="title class_">Demo1</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">MyCom1</span> = <span class="title function_">withLog</span>(<span class="title class_">Demo2</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.PureComponent</span> &#123;</span><br><span class="line">  state = &#123;</span><br><span class="line">    <span class="attr">msg</span>: <span class="string">&quot;父组件数据&quot;</span>,</span><br><span class="line">  &#125;;</span><br><span class="line"></span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">MyCom</span> <span class="attr">a</span>=<span class="string">&#123;this.state.msg&#125;</span> <span class="attr">c</span>=<span class="string">&quot;1231231&quot;</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">MyCom1</span> /&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br><h3 id="13-2、高阶组件实战"><a href="#13-2、高阶组件实战" class="headerlink" title="13.2、高阶组件实战"></a><strong><font color='#10c300'>13.2、高阶组件实战</font></strong></h3><p>请求地址</p><p>dogApi：<a href="https://dog.ceo/api/breeds/image/random">https://dog.ceo/api/breeds/image/random</a></p><p>catApi：<a href="https://api.thecatapi.com/v1/images/search">https://api.thecatapi.com/v1/images/search</a></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><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> axios <span class="keyword">from</span> <span class="string">&quot;axios&quot;</span>;</span><br><span class="line"><span class="keyword">import</span> <span class="title class_">React</span> <span class="keyword">from</span> <span class="string">&quot;react&quot;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">MyCat</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#123;this.props.url&#125;</span> <span class="attr">width</span>=<span class="string">&quot;200&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</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 class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">class</span> <span class="title class_">MyDog</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">img</span> <span class="attr">src</span>=<span class="string">&#123;this.props.url&#125;</span> <span class="attr">width</span>=<span class="string">&quot;200&quot;</span> <span class="attr">alt</span>=<span class="string">&quot;&quot;</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 class="tag">&lt;/<span class="name">div</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">function</span> <span class="title function_">withAnimal</span>(<span class="params">WrapComponent, url, type</span>) &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">class</span> <span class="title class_">extends</span> <span class="title class_">React</span>.<span class="property">Component</span> &#123;</span><br><span class="line">    state = &#123;</span><br><span class="line">      <span class="attr">imgUrl</span>: <span class="string">&quot;&quot;</span>,</span><br><span class="line">    &#125;;</span><br><span class="line">    <span class="title function_">componentDidMount</span>(<span class="params"></span>) &#123;</span><br><span class="line">      axios.<span class="title function_">get</span>(url).<span class="title function_">then</span>(<span class="function">(<span class="params">res</span>) =&gt;</span> &#123;</span><br><span class="line">        <span class="variable language_">this</span>.<span class="title function_">setState</span>(&#123;</span><br><span class="line">          <span class="attr">imgUrl</span>: type ? res.<span class="property">data</span>.<span class="property">message</span> : res.<span class="property">data</span>[<span class="number">0</span>].<span class="property">url</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="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">      <span class="keyword">return</span> <span class="language-xml"><span class="tag">&lt;<span class="name">WrapComponent</span> <span class="attr">url</span>=<span class="string">&#123;this.state.imgUrl&#125;</span> /&gt;</span></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><br><span class="line"><span class="keyword">const</span> <span class="title class_">Cat</span> = <span class="title function_">withAnimal</span>(<span class="title class_">MyCat</span>, <span class="string">&quot;https://api.thecatapi.com/v1/images/search&quot;</span>, <span class="number">0</span>);</span><br><span class="line"><span class="keyword">const</span> <span class="title class_">Dog</span> = <span class="title function_">withAnimal</span>(<span class="title class_">MyDog</span>, <span class="string">&quot;https://dog.ceo/api/breeds/image/random&quot;</span>, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">class</span> <span class="title class_">App</span> <span class="keyword">extends</span> <span class="title class_ inherited__">React.Component</span> &#123;</span><br><span class="line">  <span class="title function_">render</span>(<span class="params"></span>) &#123;</span><br><span class="line">    <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">&quot;渲染&quot;</span>);</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;&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Cat</span>&gt;</span><span class="tag">&lt;/<span class="name">Cat</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">        <span class="tag">&lt;<span class="name">Dog</span>&gt;</span><span class="tag">&lt;/<span class="name">Dog</span>&gt;</span></span></span><br><span class="line"><span class="language-xml">      <span class="tag">&lt;/&gt;</span></span></span><br><span class="line">    );</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">export</span> <span class="keyword">default</span> <span class="title class_">App</span>;</span><br></pre></td></tr></table></figure><br>]]></content>
    
    
      
      
    <summary type="html">&lt;h2 id=&quot;一、组件与类组件&quot;&gt;&lt;a href=&quot;#一、组件与类组件&quot; class=&quot;headerlink&quot; title=&quot;一、组件与类组件&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、组件与类组件&lt;/font&gt;&lt;/strong&gt;&lt;/h2&gt;&lt;p&gt;组件其实</summary>
      
    
    
    
    <category term="框架与生态" scheme="http://example.com/categories/%E6%A1%86%E6%9E%B6%E4%B8%8E%E7%94%9F%E6%80%81/"/>
    
    
    <category term="React" scheme="http://example.com/tags/React/"/>
    
    <category term="React18" scheme="http://example.com/tags/React18/"/>
    
    <category term="类组件" scheme="http://example.com/tags/%E7%B1%BB%E7%BB%84%E4%BB%B6/"/>
    
  </entry>
  
  <entry>
    <title>Cusros的使用</title>
    <link href="http://example.com/posts/aed47f69.html"/>
    <id>http://example.com/posts/aed47f69.html</id>
    <published>2025-12-02T00:38:45.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、解决限制国内使用"><a href="#一、解决限制国内使用" class="headerlink" title="一、解决限制国内使用"></a><strong><font color='red'>一、解决限制国内使用</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251115161133203.png" alt="image-20251115161133203"></p><p>原因是美国那边制裁了 claude ，限制咱们国内地区的ip使用，因此我们就无法使用了。</p><p><strong>解决步骤</strong></p><ol><li><p>打开自己的魔法工具（大家懂得）</p></li><li><p>在Cursor编辑器设置代理，ip的端口号设置成魔法工具的端口号</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251115161504320.png" alt="image-20251115161504320"></p></li><li><p>将http&#x2F;2设置成http&#x2F;1.1即可</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251115161623548.png" alt="image-20251115161623548"></p></li><li><p>测试</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251115161703956.png" alt="image-20251115161703956"></p></li></ol><br><h3 id="二、Cusros对话模式模板"><a href="#二、Cusros对话模式模板" class="headerlink" title="二、Cusros对话模式模板"></a><strong><font color='red'>二、Cusros对话模式模板</font></strong></h3><p><strong>提示词技巧总结:</strong></p><ol><li>提供上下文:提及项目语言、框架、业务背景等信息</li><li>分点描述:将复杂需求拆解为具体步骤或要求</li><li>使用技术术语:准确的术语能帮助 AI 更精准理解需求</li><li>明确边界:说明必须保留的现有功能或禁止的实现方式</li><li>示例引导:附上期望输出示例或参考代码风格</li></ol><br><h4 id="2-1、代码生成类"><a href="#2-1、代码生成类" class="headerlink" title="2.1、代码生成类"></a><strong><font color='#10c300'>2.1、代码生成类</font></strong></h4><figure class="highlight markdown"><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="symbol">任务类型</span>]:<span class="link">请生成一个&#123;功能描述&#125;的&#123;编程语言/框架&#125;实现</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">具体要求</span>]:</span><br><span class="line"><span class="link">1.使用&#123;特定技术/库&#125;</span></span><br><span class="line">2.包含&#123;特定功能点&#125;</span><br><span class="line">3.符合&#123;编码规范/设计模式&#125;</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight plaintext"><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></pre></td><td class="code"><pre><span class="line">请生成一个学习计划页面的HTML+CSS+Javascript实现</span><br><span class="line"></span><br><span class="line">[具体要求]:</span><br><span class="line">1.使用Tailwind Css v3和Font Awesome</span><br><span class="line">2.包含任务添加、编辑、删除功能</span><br><span class="line">3.包含日历视图展示学习计划</span><br><span class="line">4.包含学习进度可视化图表</span><br><span class="line">5.符合现代UI设计原则和响应式设计</span><br><span class="line">6.具有平滑的动画和交互效果</span><br></pre></td></tr></table></figure><br><h4 id="2-2、代码修改类"><a href="#2-2、代码修改类" class="headerlink" title="2.2、代码修改类"></a><strong><font color='#10c300'>2.2、代码修改类</font></strong></h4><figure class="highlight markdown"><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="symbol">任务类型</span>]:<span class="link">请帮我修改&#123;上下文:具体文件/代码片段&#125;，实现&#123;预期功能&#125;</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">当前问题</span>]:<span class="link">&#123;现有的错误/不足描述&#125;</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">具体要求</span>]:</span><br><span class="line"><span class="link">1.保持&#123;现有功能/结构&#125;不变</span></span><br><span class="line">2.使用&#123;特定方法/技术&#125;&#125;改进</span><br><span class="line">3.修复&#123;具体错误/警告&#125;</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight plaintext"><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">请帮我修改当前的 React 组件，优化列表渲染性能</span><br><span class="line">当前问题:滚动时列表卡顿，存在明显性能问题。要求:</span><br><span class="line">1.保持现有 UI 不变</span><br><span class="line">2.使用 React.memo 和虚拟列表技术优化</span><br><span class="line">3.添加性能监控日志</span><br></pre></td></tr></table></figure><br><h4 id="2-3、代码解释类"><a href="#2-3、代码解释类" class="headerlink" title="2.3、代码解释类"></a><strong><font color='#10c300'>2.3、代码解释类</font></strong></h4><figure class="highlight markdown"><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="symbol">任务类型</span>]:<span class="link">请解释&#123;代码片段/功能模块&#125;的&#123;具体方面&#125;</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">上下文信息</span>]:<span class="link">&#123;相关业务背景/技术栈&#125;</span></span><br><span class="line"></span><br><span class="line">[具体问题]</span><br><span class="line">1.&#123;不理解的语法/逻辑&#125;</span><br><span class="line">2.&#123;特定设计选择的原因]</span><br><span class="line">3.&#123;潜在的问题/优化点&#125;</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight plaintext"><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">请解释这段 Typescript 代码的泛型约束和类型推导逻辑。</span><br><span class="line">上下文:这是一个用于数据验证的工具函数。</span><br><span class="line">具体问题:</span><br><span class="line">1.&lt;T extends object&gt;这里为什么要加 extends object?</span><br><span class="line">2.类型推导是如何工作的?</span><br><span class="line">3.是否存在类型安全隐患?</span><br></pre></td></tr></table></figure><br><h4 id="2-4、流程自动化类"><a href="#2-4、流程自动化类" class="headerlink" title="2.4、流程自动化类"></a><strong><font color='#10c300'>2.4、流程自动化类</font></strong></h4><figure class="highlight markdown"><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="symbol">任务类型</span>]:<span class="link">请创建一个自动化流程，实现&#123;目标描述&#125;</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">操作步骤</span>]:</span><br><span class="line"><span class="link">1.从&#123;数据源&#125;获取&#123;数据类型&#125;</span></span><br><span class="line">2.执行&#123;数据处理/转换操作&#125;</span><br><span class="line">3.将结果保存到&#123;目标位置&#125;</span><br><span class="line">4.触发&#123;后续操作/通知&#125;</span><br><span class="line"></span><br><span class="line">[<span class="symbol">具体要求</span>]:</span><br><span class="line"><span class="link">1.使用&#123;特定工具/API&#125;</span></span><br><span class="line">2.添加&#123;错误处理/重试机制&#125;</span><br><span class="line">3.生成&#123;日志/报告&#125;</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight plaintext"><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">请创建一个自动化流程，每天凌晨从 GitHub API 获取仓库星标数，保存到 Goog1e sheets 并生成趋势图。</span><br><span class="line">要求:</span><br><span class="line">1.使用 GitHub REST API V3</span><br><span class="line">2.添加异常处理和邮件通知</span><br><span class="line">3.生成周/月增长趋势图表</span><br></pre></td></tr></table></figure><br><h4 id="2-5、命令行辅助类"><a href="#2-5、命令行辅助类" class="headerlink" title="2.5、命令行辅助类"></a><strong><font color='#10c300'>2.5、命令行辅助类</font></strong></h4><figure class="highlight markdown"><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="symbol">任务类型</span>]:<span class="link">请提供&#123;操作场景&#125;的&#123;操作系统&#125;命令</span></span><br><span class="line"></span><br><span class="line">[<span class="symbol">具体需求</span>]:</span><br><span class="line"><span class="link">1.&#123;执行的具体操作&#125;</span></span><br><span class="line">2.包含&#123;特定参数/选项&#125;</span><br><span class="line">3.处理&#123;特殊情况/错误&#125;</span><br></pre></td></tr></table></figure><p>示例：</p><figure class="highlight markdown"><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">请提供在 macos 上批量压缩图片的命令行方案。需求:</span><br><span class="line">1.将当前目录下所有 PNG/JPG 图片压缩 50%</span><br><span class="line">2.保留原始文件并添加“-compressed”后缀</span><br><span class="line">3.显示每个文件的压缩前后大小对比</span><br></pre></td></tr></table></figure><br><h3 id="三、Cursor精准上下文指定"><a href="#三、Cursor精准上下文指定" class="headerlink" title="三、Cursor精准上下文指定"></a><strong><font color='red'>三、Cursor精准上下文指定</font></strong></h3><p>在 Cursor 工具里，“上下文（Context）” 可理解为 <strong>让 AI 准确理解需求、辅助编码的 “信息参考范围”</strong> ，是 AI 读懂代码、精准响应的关键!</p><h4 id="3-1-Codebase-Indexing-代码库索引"><a href="#3-1-Codebase-Indexing-代码库索引" class="headerlink" title="3.1 Codebase Indexing 代码库索引"></a><strong><font color='#10c300'>3.1 Codebase Indexing 代码库索引</font></strong></h4><h5 id="1）、概念和作用"><a href="#1）、概念和作用" class="headerlink" title="1）、概念和作用"></a><strong><font color='cornflowerblue'>1）、概念和作用</font></strong></h5><p>打开项目时，每个 Cursor 实例都将初始化该工作区的索引。初始索引设置完成后，Cursor 将自动为添加到工作区的任何新文件编制索引，以使您的代码库上下文保持最新：</p><ul><li>快速 “读懂” 你的项目结构（哪些是工具文件、哪些是业务逻辑）</li><li>定位相关代码（如搜索 <code>getUser</code> 时，知道优先查 <code>userService.js</code>）</li><li>理解代码关系（如 <code>Order</code> 类和 <code>Product</code> 类的关联）</li></ul><p><strong>Cursor 中的作用</strong>：AI 分析索引内容后，生成代码时会更贴合项目实际（如使用已有工具函数、遵循命名规范）。</p><br><h5 id="2）、代码库索引配置和示例"><a href="#2）、代码库索引配置和示例" class="headerlink" title="2）、代码库索引配置和示例"></a><strong><font color='cornflowerblue'>2）、代码库索引配置和示例</font></strong></h5><p>代码库索引的状态位于cursor settings &gt; Indexing &amp; Docs</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114004719325.png" alt="image-20251114004719325"></p><h5 id="3-、忽略文件配置"><a href="#3-、忽略文件配置" class="headerlink" title="3)、忽略文件配置"></a><strong><font color='cornflowerblue'>3)、忽略文件配置</font></strong></h5><p>Cursor 读取项目的代码库并为其编制索引以支持其功能。可以通过将.cursorignore 文件添加到根目录来控制<br>哪些文件被忽略和Cursor限制访问。</p><ul><li>提升索引速度：排除大型依赖、生成文件(如node_modu1es、dist)</li><li>避免干扰：某些配置文件可能包含敏感信息或与当前任务无关</li></ul><p>配置.cursorignore忽略文件的两种方法：</p><ol><li><p>自己创建 .cursorignore 文件添加到代码库目录的根目录下，并列出要忽略的目录和文件</p></li><li><p>使用cursor配置快捷创建忽略文件&#96;cursorsetting&gt;indexing&gt;Configure ignored files</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251113233732232.png" alt="image-20251113233732232"></p></li></ol><br><h4 id="3-2、Rules规则"><a href="#3-2、Rules规则" class="headerlink" title="3.2、Rules规则"></a><strong><font color='#10c300'>3.2、Rules规则</font></strong></h4><h5 id="1）规则介绍"><a href="#1）规则介绍" class="headerlink" title="1）规则介绍"></a><strong><font color='cornflowerblue'>1）规则介绍</font></strong></h5><p>Rules是给Cursor AI功能(规则适用于Chat和Cmd K)生成结果添加规则和限制，让 AI生成的代码贴合团队规范，减少人工二次修改成本，主要的作用如下:</p><ul><li>可约束代码风格(如强制用驼峰命名、要求函数必须写注释)</li><li>能限定技术选型(如禁止使用某老旧库、优先用项目指定工具类)</li><li>提前指定核心参数(如提前设置连接数据库的地址和账号密码等)</li></ul><table><thead><tr><th>维度</th><th>项目规则(Project Rules)</th><th>用户规则(User Rules)</th></tr></thead><tbody><tr><td>作用范围</td><td>仅对当前项目生效，团队成员共享相同规则</td><td>对所有项目生效，个人专属配置</td></tr><tr><td>存储位置</td><td>项目根目录下的.cursor&#x2F;rules&#x2F;随意.mdc 文件</td><td>用户配置目录(如~&#x2F;.cursor&#x2F;ru1es)</td></tr><tr><td>同步方式</td><td>随项目代码提交到版本库(如 Git)，团队共享</td><td>仅本地生效，不随项目同步</td></tr><tr><td>适用场景</td><td>统一团队编码规范(如函数注释格式、依赖版本)</td><td>个人习惯(如快捷键、AI 响应风格)</td></tr></tbody></table><p><font color='orange'>注意：项目规则和用户规则同时存在并且规则冲突，项目规则优先级更高–</font></p><br><h5 id="2）项目规则配置"><a href="#2）项目规则配置" class="headerlink" title="2）项目规则配置"></a><strong><font color='cornflowerblue'>2）项目规则配置</font></strong></h5><p><strong>a、项目下创建规则文件的两种方法</strong></p><ol><li>在项目根目录创建文件夹<code>.cursor/rules/随意命名.mdc</code></li><li>快捷命令方式创建 ctr1+ shift +P &gt; “New Cursor Rule”<img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114001431714.png" alt="image-20251114001431714"></li></ol><br><p><strong>b、编写项目规则文件</strong></p><figure class="highlight markdown"><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><br><span class="line">alwaysApply: true</span><br><span class="line">description: &quot;团队前端项目规范&quot;</span><br><span class="line"><span class="section">priority: 1000</span></span><br><span class="line"><span class="section">---</span></span><br><span class="line"></span><br><span class="line"><span class="section"># 代码风格</span></span><br><span class="line"><span class="bullet">1.</span> 函数必须包含 JSDoc 注释</span><br><span class="line"><span class="bullet">2.</span> 禁止使用<span class="code">`var`</span>,  统一使用 <span class="code">`const`</span>/<span class="code">`let`</span></span><br><span class="line"><span class="bullet">3.</span> 函数命名必须添加 zwf<span class="emphasis">_前缀，例如:zwf_</span>login</span><br><span class="line"></span><br><span class="line"><span class="section"># 技术选型</span></span><br><span class="line"><span class="bullet">-</span> 优先使用项目内已有的工具函数(如 <span class="code">`uti1s/request`</span>)</span><br><span class="line"><span class="bullet">-</span> 禁止引入低版本的 1odash(&lt;4.0.0)</span><br></pre></td></tr></table></figure><br><p><strong>c、项目规则文件生效测试</strong></p><p><code>ctrl+k</code>调出内联智能输入<img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114002810947.png" alt="image-20251114002810947"></p><br><h5 id="3）用户规则配置"><a href="#3）用户规则配置" class="headerlink" title="3）用户规则配置"></a><strong><font color='cornflowerblue'>3）用户规则配置</font></strong></h5><p><code>ctrl+shift+j</code>快捷打开Cursor Settings，用户规则不支持 MDC，它们只是纯文本。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114003925641.png" alt="image-20251114003925641"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114003947859.png" alt="image-20251114003947859"></p><p>实现每行代码都用中文注释了</p><br><h4 id="3-3、mdc语法了解"><a href="#3-3、mdc语法了解" class="headerlink" title="3.3、mdc语法了解"></a><strong><font color='#10c300'>3.3、mdc语法了解</font></strong></h4><p>Cursor 的 MDC（Markdown with Cursor）语法是专门为编写项目规则设计的轻量级格式，它结合了 Markdown 的可读性和元数据配置能力。接下来，我们来说明下mdc文件语法。</p><h5 id="1）-MDC-文件组成部分"><a href="#1）-MDC-文件组成部分" class="headerlink" title="1） MDC 文件组成部分"></a><strong><font color='cornflowerblue'>1） MDC 文件组成部分</font></strong></h5><ul><li>前置元数据（Frontmatter）<ul><li>用 <code>---</code> 包裹的 YAML 格式配置</li><li>定义规则的基本属性（如作用范围、优先级）</li></ul></li><li>规则内容（Markdown 正文）<ul><li>用 Markdown 语法写具体规则</li></ul></li></ul><br><h5 id="2）前置元数据"><a href="#2）前置元数据" class="headerlink" title="2）前置元数据"></a><strong><font color='cornflowerblue'>2）前置元数据</font></strong></h5><figure class="highlight yaml"><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></pre></td><td class="code"><pre><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="comment"># 官方约定字段（推荐用，AI 更易理解）</span></span><br><span class="line"><span class="attr">description:</span> <span class="string">&quot;前端项目规则&quot;</span></span><br><span class="line"><span class="attr">globs:</span> <span class="string">&quot;src/**/*.tsx&quot;</span></span><br><span class="line"><span class="attr">priority:</span> <span class="number">1000</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 自定义字段（自己或团队约定含义）</span></span><br><span class="line"><span class="attr">author:</span> <span class="string">&quot;技术团队&quot;</span></span><br><span class="line"><span class="attr">review_date:</span> <span class="string">&quot;2025-06-04&quot;</span></span><br><span class="line"><span class="attr">special_rule:</span> <span class="string">&quot;仅周一至周五生效&quot;</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><table><thead><tr><th>字段</th><th>作用</th><th>示例</th></tr></thead><tbody><tr><td><code>description</code></td><td>描述规则用途，指导 AI 如何应用规则</td><td><code>&quot;前端组件编码规范&quot;</code></td></tr><tr><td><code>globs</code></td><td>指定规则生效的文件范围（支持 glob 语法）</td><td><code>&quot;src/**/*.&#123;js,ts,jsx&#125;&quot;</code></td></tr><tr><td><code>priority</code></td><td>规则优先级（数值越大越优先），解决规则冲突</td><td><code>1000</code></td></tr><tr><td><code>version</code></td><td>规则版本号（可选）</td><td><code>&quot;1.0.0&quot;</code></td></tr></tbody></table><br><h5 id="3）规则内容（Markdown-正文）"><a href="#3）规则内容（Markdown-正文）" class="headerlink" title="3）规则内容（Markdown 正文）"></a><strong><font color='cornflowerblue'>3）规则内容（Markdown 正文）</font></strong></h5><p>用 Markdown 的标题、列表、代码块等语法写具体规则，常见结构：</p><p><strong>代码风格规则（最常用）</strong></p><figure class="highlight markdown"><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></pre></td><td class="code"><pre><span class="line"><span class="section"># 一、代码风格</span></span><br><span class="line"><span class="bullet">1.</span> 函数必须包含 JSDoc 注释  </span><br><span class="line"><span class="bullet">   -</span> 至少包含 <span class="code">`@param`</span> 和 <span class="code">`@return`</span> 描述  </span><br><span class="line"><span class="bullet">2.</span> 变量命名必须使用驼峰命名法（camelCase）  </span><br><span class="line"><span class="bullet">3.</span> 每行代码长度不超过 120 个字符  </span><br><span class="line"></span><br><span class="line"><span class="section"># 二、技术选型</span></span><br><span class="line"><span class="bullet">-</span> 禁止直接使用原生 fetch，必须通过项目封装的 request 工具  </span><br><span class="line"><span class="bullet">-</span> 优先使用 React Hooks 而非 Class 组件  </span><br></pre></td></tr></table></figure><p><strong>安全约束规则</strong></p><figure class="highlight markdown"><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="section"># 安全规范</span></span><br><span class="line"><span class="bullet">1.</span> 禁止使用 eval() 函数  </span><br><span class="line"><span class="bullet">2.</span> SQL 查询必须使用参数化查询，防止注入攻击  </span><br><span class="line"><span class="bullet">3.</span> 敏感信息（如 API 密钥）必须从环境变量读取  </span><br></pre></td></tr></table></figure><p><strong>特殊语法：引用项目文件</strong></p><p>用 <code>@file</code> 引用项目内的配置文件，让 AI 参考：</p><figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="section"># 工具链配置</span></span><br><span class="line"><span class="bullet">1.</span> ESLint 规则必须符合 @file .eslintrc.js  </span><br><span class="line"><span class="bullet">2.</span> 测试用例必须遵循 Jest 框架规范  </span><br></pre></td></tr></table></figure><br><h5 id="4）完整示例（TypeScript-项目规则）"><a href="#4）完整示例（TypeScript-项目规则）" class="headerlink" title="4）完整示例（TypeScript 项目规则）"></a><strong><font color='cornflowerblue'>4）完整示例（TypeScript 项目规则）</font></strong></h5><figure class="highlight markdown"><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></pre></td><td class="code"><pre><span class="line">---</span><br><span class="line">description: &quot;TypeScript 项目编码规范&quot;</span><br><span class="line">globs: &quot;src/<span class="strong">**/<span class="emphasis">*.ts&quot;</span></span></span><br><span class="line"><span class="emphasis"><span class="strong">priority: 1000</span></span></span><br><span class="line"><span class="emphasis"><span class="strong">---</span></span></span><br><span class="line"><span class="emphasis"><span class="strong"></span></span></span><br><span class="line"><span class="emphasis"><span class="strong"># 一、基础规范</span></span></span><br><span class="line"><span class="emphasis"><span class="strong">1. 所有文件必须使用 UTF-8 编码  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong">2. 统一使用 2 空格缩进  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong"></span></span></span><br><span class="line"><span class="emphasis"><span class="strong"># 二、类型约束</span></span></span><br><span class="line"><span class="emphasis"><span class="strong">1. 禁止使用隐式 any 类型  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong">   - 示例：`const num: number = 123`（显式）  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong">   - 禁止：`const num = 123`（隐式）  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong">2. 接口命名必须以 `I` 开头（如 `interface IUser`）  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong"></span></span></span><br><span class="line"><span class="emphasis"><span class="strong"># 三、项目约束</span></span></span><br><span class="line"><span class="emphasis"><span class="strong">- 所有 HTTP 请求必须通过 @file src/utils/request.ts 封装的工具  </span></span></span><br><span class="line"><span class="emphasis"><span class="strong">- 状态管理必须使用 Redux Toolkit，禁止直接修改 state  </span></span></span><br></pre></td></tr></table></figure><br><h4 id="3-4、-符号"><a href="#3-4、-符号" class="headerlink" title="3.4、@ 符号"></a><strong><font color='#10c300'>3.4、@ 符号</font></strong></h4><p>在 Cursor 中使用 @ 符号在聊天中引用代码、文件、文档和其他上下文的指南，直接更具体的指定上下文环境！</p><p>以下是所有可用 @ 符号的列表：</p><ul><li>@Files &amp; Folders - 引用项目中的特定文件或文件夹 ✅</li><li>@Docs- 访问文档和指南✅</li><li>@Git- 访问 git 历史记录和更改</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114013559213.png" alt="image-20251114013559213"></p><h5 id="1）-Files-Folders-文件和文件夹"><a href="#1）-Files-Folders-文件和文件夹" class="headerlink" title="1）@Files &amp; Folders 文件和文件夹"></a><strong><font color='cornflowerblue'>1）@Files &amp; Folders 文件和文件夹</font></strong></h5><p>选择 <code>@Files &amp; Folders</code>，然后选择要搜索的文件名即可引用整个文件。你也可以将侧边栏中的文件直接拖拽到 Agent 中作为上下文。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114013919773.png" alt="image-20251114013919773"></p><p>选择文件夹后，输入 “&#x2F;” 以继续下钻并查看所有子文件夹。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114013934820.png" alt="image-20251114013934820"></p><br><h5 id="2）-Docs"><a href="#2）-Docs" class="headerlink" title="2）@Docs"></a><strong><font color='cornflowerblue'>2）@Docs</font></strong></h5><p><code>@Docs</code> 功能可让你用文档来辅助写代码</p><p><strong>添加文档</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114014157560.png" alt="image-20251114014157560"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114014346829.png" alt="image-20251114014346829"></p><br><p><strong>提问</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251114014547119.png" alt="image-20251114014547119"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、解决限制国内使用&quot;&gt;&lt;a href=&quot;#一、解决限制国内使用&quot; class=&quot;headerlink&quot; title=&quot;一、解决限制国内使用&quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、解决限制国内使用&lt;/font&gt;&lt;/strong&gt;&lt;/h3</summary>
      
    
    
    
    <category term="工具" scheme="http://example.com/categories/%E5%B7%A5%E5%85%B7/"/>
    
    
    <category term="Cursor" scheme="http://example.com/tags/Cursor/"/>
    
    <category term="AI" scheme="http://example.com/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>UniApp多端开发</title>
    <link href="http://example.com/posts/57fa3c28.html"/>
    <id>http://example.com/posts/57fa3c28.html</id>
    <published>2025-12-02T00:29:04.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<h3 id="一、uniApp生命周期"><a href="#一、uniApp生命周期" class="headerlink" title="一、uniApp生命周期 "></a><strong><font color='red'>一、uniApp生命周期 </font></strong></h3><h4 id="1-1、应用生命周期"><a href="#1-1、应用生命周期" class="headerlink" title="1-1、应用生命周期"></a><strong><font color='#10c300'>1-1、应用生命周期</font></strong></h4><table><thead><tr><th align="left">函数名</th><th align="left">说明</th><th align="left">平台兼容</th></tr></thead><tbody><tr><td align="left">onLaunch</td><td align="left">当<code>uni-app</code> 初始化完成时触发（全局只触发一次），参数为应用启动参数，同 <a href="https://uniapp.dcloud.net.cn/api/getLaunchOptionsSync.html#getlaunchoptionssync">uni.getLaunchOptionsSync</a> 的返回值</td><td align="left"></td></tr><tr><td align="left">onShow</td><td align="left">当 <code>uni-app</code> 启动，或从后台进入前台显示，参数为应用启动参数，同 <a href="https://uniapp.dcloud.net.cn/api/getLaunchOptionsSync.html#getlaunchoptionssync">uni.getLaunchOptionsSync</a> 的返回值</td><td align="left"></td></tr><tr><td align="left">onHide</td><td align="left">当 <code>uni-app</code> 从前台进入后台</td><td align="left"></td></tr><tr><td align="left">onError</td><td align="left">当 <code>uni-app</code> 报错时触发</td><td align="left">app-uvue 不支持</td></tr><tr><td align="left">onUniNViewMessage</td><td align="left">对 <code>nvue</code> 页面发送的数据进行监听，可参考 <a href="https://uniapp.dcloud.io/tutorial/nvue-api?id=communication">nvue 向 vue 通讯</a></td><td align="left">app-uvue 不支持</td></tr><tr><td align="left">onUnhandledRejection</td><td align="left">对未处理的 Promise 拒绝事件监听函数（2.8.1+ app-uvue 暂不支持）</td><td align="left">app-uvue 不支持</td></tr><tr><td align="left">onPageNotFound</td><td align="left">页面不存在监听函数</td><td align="left">app-uvue 不支持</td></tr><tr><td align="left">onThemeChange</td><td align="left">监听系统主题变化</td><td align="left">app-uvue 不支持</td></tr><tr><td align="left">onLastPageBackPress</td><td align="left">最后一个页面按下Android back键，常用于自定义退出</td><td align="left">app-uvue-android 3.9+</td></tr><tr><td align="left">onExit</td><td align="left">监听应用退出</td><td align="left">app-uvue-android 3.9+</td></tr></tbody></table><p>应用生命周期仅可在<code>App.vue</code>中监听，在其它页面监听无效。</p><br><h4 id="1-2、面生命周期"><a href="#1-2、面生命周期" class="headerlink" title="1-2、面生命周期"></a><strong><font color='#10c300'>1-2、面生命周期</font></strong></h4><table><thead><tr><th align="left">函数名</th><th align="left">说明</th><th align="left">平台差异说明</th></tr></thead><tbody><tr><td align="left">onInit</td><td align="left">监听页面初始化，其参数同 onLoad 参数，为上个页面传递的数据，参数类型为 Object（用于页面传参），触发时机早于 onLoad</td><td align="left">百度小程序</td></tr><tr><td align="left">onLoad</td><td align="left">监听页面加载，该钩子被调用时，响应式数据、计算属性、方法、侦听器、props、slots 已设置完成，其参数为上个页面传递的数据，参数类型为 Object（用于页面传参）</td><td align="left"></td></tr><tr><td align="left">onShow</td><td align="left">监听页面显示，页面每次出现在屏幕上都触发，包括从下级页面点返回露出当前页面</td><td align="left"></td></tr><tr><td align="left">onReady</td><td align="left">监听页面初次渲染完成，此时组件已挂载完成，DOM 树($el)已可用，注意如果渲染速度快，会在页面进入动画完成前触发</td><td align="left"></td></tr><tr><td align="left">onHide</td><td align="left">监听页面隐藏</td><td align="left"></td></tr><tr><td align="left">onUnload</td><td align="left">监听页面卸载</td><td align="left"></td></tr><tr><td align="left">onResize</td><td align="left">监听窗口尺寸变化</td><td align="left">App、微信小程序、快手小程序</td></tr><tr><td align="left">onPullDownRefresh</td><td align="left">监听用户下拉动作，一般用于下拉刷新</td><td align="left"></td></tr><tr><td align="left">onReachBottom</td><td align="left">页面滚动到底部的事件（不是scroll-view滚到底），常用于下拉下一页数据。具体见下方注意事项</td><td align="left"></td></tr><tr><td align="left">onTabItemTap</td><td align="left">点击 tab 时触发，参数为Object，具体见下方注意事项</td><td align="left">微信小程序、QQ小程序、支付宝小程序、百度小程序、H5、App、快手小程序、京东小程序</td></tr><tr><td align="left">onShareAppMessage</td><td align="left">用户点击右上角分享</td><td align="left">微信小程序、QQ小程序、支付宝小程序、抖音小程序、飞书小程序、快手小程序、京东小程序</td></tr><tr><td align="left">onPageScroll</td><td align="left">监听页面滚动，参数为Object</td><td align="left">nvue不支持</td></tr><tr><td align="left">onNavigationBarButtonTap</td><td align="left">监听原生标题栏按钮点击事件，参数为Object</td><td align="left">App、H5</td></tr><tr><td align="left">onBackPress</td><td align="left">监听页面返回，返回 event &#x3D; {from:backbutton、 navigateBack} ，backbutton 表示来源是左上角返回按钮或 android 返回键；navigateBack表示来源是 uni.navigateBack；<a href="https://uniapp.dcloud.net.cn/tutorial/page.html#onbackpress">详见</a></td><td align="left">app、H5、支付宝小程序</td></tr><tr><td align="left">onNavigationBarSearchInputChanged</td><td align="left">监听原生标题栏搜索输入框输入内容变化事件</td><td align="left">App、H5</td></tr><tr><td align="left">onNavigationBarSearchInputConfirmed</td><td align="left">监听原生标题栏搜索输入框搜索事件，用户点击软键盘上的“搜索”按钮时触发。</td><td align="left">App、H5</td></tr><tr><td align="left">onNavigationBarSearchInputClicked</td><td align="left">监听原生标题栏搜索输入框点击事件（pages.json 中的 searchInput 配置 disabled 为 true 时才会触发）</td><td align="left">App、H5</td></tr><tr><td align="left">onShareTimeline</td><td align="left">监听用户点击右上角转发到朋友圈</td><td align="left">微信小程序</td></tr><tr><td align="left">onAddToFavorites</td><td align="left">监听用户点击右上角收藏</td><td align="left">微信小程序、QQ小程序</td></tr></tbody></table><h5 id="1）Vue2-页面及组件生命周期流程图"><a href="#1）Vue2-页面及组件生命周期流程图" class="headerlink" title="1）Vue2 页面及组件生命周期流程图"></a><strong><font color='cornflowerblue'>1）Vue2 页面及组件生命周期流程图</font></strong></h5><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/uni-app-lifecycle-vue2.jpg" alt="img"></p><br><h5 id="Vue3-页面及组件生命周期流程图"><a href="#Vue3-页面及组件生命周期流程图" class="headerlink" title="Vue3 页面及组件生命周期流程图"></a><strong><font color='cornflowerblue'>Vue3 页面及组件生命周期流程图</font></strong></h5><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/uni-app-lifecycle-vue3.jpg" alt="img"></p><br><h4 id="1-3、组件生命周期"><a href="#1-3、组件生命周期" class="headerlink" title="1-3、组件生命周期"></a><strong><font color='#10c300'>1-3、组件生命周期</font></strong></h4><table><thead><tr><th align="left">函数名</th><th align="left">说明</th><th align="left">平台差异说明</th></tr></thead><tbody><tr><td align="left">beforeCreate</td><td align="left">在实例初始化之前被调用。</td><td align="left"></td></tr><tr><td align="left">created</td><td align="left">在实例创建完成后被立即调用。</td><td align="left"></td></tr><tr><td align="left">beforeMount</td><td align="left">在挂载开始之前被调用。</td><td align="left"></td></tr><tr><td align="left">mounted</td><td align="left">挂载到实例上去之后调用。注意：此处并不能确定子组件被全部挂载，如果需要子组件完全挂载之后在执行操作可以使用<code>$nextTick</code></td><td align="left"></td></tr><tr><td align="left">beforeUpdate</td><td align="left">数据更新时调用，发生在虚拟 DOM 打补丁之前。</td><td align="left">仅H5平台支持</td></tr><tr><td align="left">updated</td><td align="left">由于数据更改导致的虚拟 DOM 重新渲染和打补丁，在这之后会调用该钩子。</td><td align="left">仅H5平台支持</td></tr><tr><td align="left">beforeDestroy</td><td align="left">实例销毁之前调用。在这一步，实例仍然完全可用。</td><td align="left"></td></tr><tr><td align="left">destroyed</td><td align="left">Vue 实例销毁后调用。调用后，Vue 实例指示的所有东西都会解绑定，所有的事件监听器会被移除，所有的子实例也会被销毁。</td><td align="left"></td></tr></tbody></table><br><h3 id="二、多端调试环境配置"><a href="#二、多端调试环境配置" class="headerlink" title="二、多端调试环境配置 "></a><strong><font color='red'>二、多端调试环境配置 </font></strong></h3><h4 id="2-1、安卓真机调试配置"><a href="#2-1、安卓真机调试配置" class="headerlink" title="2-1、安卓真机调试配置"></a><strong><font color='#10c300'>2-1、安卓真机调试配置</font></strong></h4><p><strong>以小米手机为例：</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604101545567.png" alt="image-20250604101545567"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604102525380.png" alt="image-20250604102525380"></p><br><h4 id="2-2、IOS真机调试配置"><a href="#2-2、IOS真机调试配置" class="headerlink" title="2-2、IOS真机调试配置"></a><strong><font color='#10c300'>2-2、IOS真机调试配置</font></strong></h4><p>2022年9月，因收到苹果公司警告，目前开发者已无法在iOS真机设备使用未签名的标准基座，所以现在要运行到 IOS ，也需要进行签名。Windows系统，开发者就可以使用三方工具（如爱思助手）对标准基座签名。</p><p><strong><code>注意：使用Apple ID签名的IPA文件有效期为7天，7天后需要重签。如果需要“自动续签”功能，IOS手机需要越狱</code></strong></p><h5 id="1）前期准备"><a href="#1）前期准备" class="headerlink" title="1）前期准备"></a><strong><font color='cornflowerblue'>1）前期准备</font></strong></h5><ul><li><p>HBuilder 3.6.9+(一般我会把软件更新到最新版，版本跟不上容易出现问题)</p></li><li><p>ios设备</p></li><li><p>原装数据线</p></li><li><p>爱思助手软件（还有iTunes工具，下载爱思助手后会自动安装该工具）</p></li><li><p>安装基座：在HBuildex安装目录下面的plugins\launcher\base 目录下找到安装的ios基座：iPhone_base.ipa</p><p>（如果没有需要安装），后面添加IPA文件的时候用到。</p></li></ul><br><h5 id="2）操作步骤"><a href="#2）操作步骤" class="headerlink" title="2）操作步骤"></a><strong><font color='cornflowerblue'>2）操作步骤</font></strong></h5><ul><li><p><strong>下载爱思助手</strong>，打开爱思助手并用数据线连接ios设备。</p></li><li><p><strong>进行签名：</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604111911639.png" alt="image-20250604111911639"></p><p>使用Apple ID 签名，依次点击使用Apple ID签名—&gt;添加Apple ID—&gt; 输入Apple ID和密码（账号最好是邮箱， 如果 Apple ID 账号是手机号码，在签名输入 ID 账号时，手机号前面需要加 86。例如：8615012345678。 ）—&gt;确定。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604113014783.png" alt="image-20250604113014783"></p><p>如果签名成功，打开已签名IPA的位置，找到签名后的ipa文件，<strong><code>并命名为iPhone_base_signed.ipa</code></strong>，然后将其拷贝到HBuilderX安装目录\plugins\launcher\base</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/2052514-20250121112902666-19879140.png" alt="img"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/2052514-20250121113924730-1273769980.png" alt="img"></p><p>打开HBuilderX，选择要运行的项目，点击工具栏运行图标，选择【运行到iOS App基座】</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/2052514-20250121113938712-386846139.png" alt="img"></p><p>如果没运行出来，可能是由于 iphone 没有开启 “开发者模式”，需要自己手动将开发者模式打开。</p></li><li><p><strong>打开开发者模式</strong></p><p>设置—&gt;隐私与安全—&gt;开发者模式 ，打开后会提示重启手机。</p><p><strong>如果你的ios系统。是 16 以上，可能在 设置—隐私与安全 里面没有 “开发者模式这一项” ，需要利用 爱思助手 来将选项打开</strong></p><p>打开爱思助手 工具箱—&gt;虚拟定位， 随便输入一个经纬度，点击修改 </p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604113829151.png" alt="image-20250604113829151"></p><p>确定开发者模式已开启 ：在手机上，设置—隐私与安全性 里面，就能看到有”开发者选项“了，开启，然后提示重启手机。完成即可。</p></li><li><p><strong>运行ios基座遇到的问题</strong></p><p>下图运行到ios基座成功</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/2052514-20250121114101865-331084701.png" alt="img"></p><p>1.ios上面安装了HBuilder调试基座，但是移动端未出现运行app的界面，点击安装的HBuilder调试基座，弹出 需要互联网连接以验证是否信任开发者iphone…..，此时确保网络是否链接是否通畅，如果设备已经连接到互联网，仍然没解决，我选择了重启设备，解决了上述问题；</p><p>2.重启设备之后，再次点击HBuilder调试基座，提示不收信任的开发者，此时在设备上找到设置&gt;通用&gt;vpn与设备管理这个选项，可以看到我们的开发者APP当前是不受信任的 我们只需点开，信任当前开发APP就可以了</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20250604114032005.png" alt="image-20250604114032005"></p><p>再重新打开这个APP，就可以发现已经可以进行iOS真机预览了。</p></li></ul><br><h4 id="2-3、微信小程序调试配置"><a href="#2-3、微信小程序调试配置" class="headerlink" title="2-3、微信小程序调试配置"></a><strong><font color='#10c300'>2-3、微信小程序调试配置</font></strong></h4><br><h4 id="2-4、支付宝小程序调试配置"><a href="#2-4、支付宝小程序调试配置" class="headerlink" title="2-4、支付宝小程序调试配置"></a><strong><font color='#10c300'>2-4、支付宝小程序调试配置</font></strong></h4><br><h3 id="三、多端调试环境配置"><a href="#三、多端调试环境配置" class="headerlink" title="三、多端调试环境配置 "></a><strong><font color='red'>三、多端调试环境配置 </font></strong></h3>]]></content>
    
    
      
      
    <summary type="html">&lt;h3 id=&quot;一、uniApp生命周期&quot;&gt;&lt;a href=&quot;#一、uniApp生命周期&quot; class=&quot;headerlink&quot; title=&quot;一、uniApp生命周期 &quot;&gt;&lt;/a&gt;&lt;strong&gt;&lt;font color=&#39;red&#39;&gt;一、uniApp生命周期 &lt;/font&gt;&lt;/s</summary>
      
    
    
    
    <category term="框架与生态" scheme="http://example.com/categories/%E6%A1%86%E6%9E%B6%E4%B8%8E%E7%94%9F%E6%80%81/"/>
    
    
    <category term="Uniapp" scheme="http://example.com/tags/Uniapp/"/>
    
    <category term="多端开发" scheme="http://example.com/tags/%E5%A4%9A%E7%AB%AF%E5%BC%80%E5%8F%91/"/>
    
  </entry>
  
  <entry>
    <title>VMware17 Pro安装CentOS 8</title>
    <link href="http://example.com/posts/f6649ba0.html"/>
    <id>http://example.com/posts/f6649ba0.html</id>
    <published>2025-11-25T22:29:48.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<p><strong>软件版本</strong></p><blockquote><p>VMware 17 Pro  17.5.2 build-23775571</p><p>CentOS-8.1.1911-x86_64-dvd1.iso</p></blockquote><h3 id="一、-创建虚拟机"><a href="#一、-创建虚拟机" class="headerlink" title="一、 创建虚拟机"></a><strong><font color='red'>一、 创建虚拟机</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234224316.png" alt="image-20251021234224316"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234501665.png" alt="image-20251021234501665"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234533907.png" alt="image-20251021234533907"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234601087.png" alt="image-20251021234601087"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234734894.png" alt="image-20251021234734894"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021234936363.png" alt="image-20251021234936363"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021235013900.png" alt="image-20251021235013900"></p><br><h3 id="二、-配置虚拟机"><a href="#二、-配置虚拟机" class="headerlink" title="二、 配置虚拟机"></a><strong><font color='red'>二、 配置虚拟机</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251021235209438.png" alt="image-20251021235209438"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022000540363.png" alt="image-20251022000540363"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022000621830.png" alt="image-20251022000621830"></p><h4 id="虚拟化处于禁用状态"><a href="#虚拟化处于禁用状态" class="headerlink" title="虚拟化处于禁用状态"></a><strong><font color='#10c300'>虚拟化处于禁用状态</font></strong></h4><p>AMD处理器提示AMD-V处于禁用状态。</p><p>Intel处理器提示ntel VT-x处于禁用状态。</p><p>因为我是AMD的处理器以及ROG的主板，因为这里主要操作怎么在ROG主板的BIOS上开启虚拟化技术</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022232519026.png" alt="image-20251022232519026"></p><p><strong>开启步骤</strong></p><ol><li>电脑开启的时候一直按F2或Del进入BIOS界面</li><li>找到Advanced进入CPU Configguration中找到SVM Mode将其状态改成Enabled</li></ol><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022234218884.png" alt="image-20251022234218884"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022234544337.png" alt="image-20251022234544337"></p><br><h3 id="三、-系统安装引导界面"><a href="#三、-系统安装引导界面" class="headerlink" title="三、 系统安装引导界面"></a><strong><font color='red'>三、 系统安装引导界面</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/5e9856fd57ad3cd601de474ed0a9d59b.png" alt="img"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/da83ad02a5d26c3f865a68503efcbe2c.png" alt="img"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/71e0855efe957df4c85e67e7642a1538.png" alt="img"></p><br><h3 id="四、-定制化内容"><a href="#四、-定制化内容" class="headerlink" title="四、 定制化内容"></a><strong><font color='red'>四、 定制化内容</font></strong></h3><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022001114895.png" alt="image-20251022001114895"></p><br><h4 id="4-1、调整时间差"><a href="#4-1、调整时间差" class="headerlink" title="4.1、调整时间差"></a><strong><font color='#10c300'>4.1、调整时间差</font></strong></h4><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022003530905.png" alt="image-20251022003530905"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022003918721.png" alt="image-20251022003918721"></p><br><h4 id="4-2、配置磁盘分区"><a href="#4-2、配置磁盘分区" class="headerlink" title="4.2、配置磁盘分区"></a><strong><font color='#10c300'>4.2、配置磁盘分区</font></strong></h4><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023215033391.png" alt="image-20251023215033391"></p><br><h5 id="1）默认磁盘分区配置"><a href="#1）默认磁盘分区配置" class="headerlink" title="1）默认磁盘分区配置"></a><strong><font color='cornflowerblue'>1）默认磁盘分区配置</font></strong><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251022004121785.png" alt="image-20251022004121785"></h5><br><h5 id="2）自定义磁盘分区"><a href="#2）自定义磁盘分区" class="headerlink" title="2）自定义磁盘分区"></a><strong><font color='cornflowerblue'>2）自定义磁盘分区</font></strong></h5><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023215120757.png" alt="image-20251023215120757"></p><br><p><strong><font color='orange'>a、手动添加分区</font></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023220443302.png" alt="image-20251023220443302"></p><br><p><strong><font color='orange'>b、添加 boot 区 给上 1G 容量后点击添加挂载点</font></strong></p><p>挂载点<code>/boot</code>是系统启动的引导分区</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023220707214.png" alt="image-20251023220707214"></p><p>文件系统默认选择ext4，即第四代文件扩展系统，其容量可以达到EB（GB→TB→EB）单个文件容量可以达到16TB。</p><p>我们选择xfs文件系统，这是一个高性能日志文件系统，特别擅长处理大文件，64位系统中最大能够支持8EB的文件系统，目前性能最强。</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023224710364.png" alt="image-20251023224710364"></p><br><p><strong><font color='orange'>c、添加 swap 交换分区</font></strong></p><p>swap交换分区：</p><p>​在硬盘中单独创建一块分区来，单独去作为扩展内存。就是我们在配置虚拟机的时候设置的那个内存不足时，则可以将swap交换分区作为裸战内存来使用（我们当时是分了8G内存）。</p><p>执行的一个过程：</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221045499.png" alt="image-20251023221045499"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221112598.png" alt="image-20251023221112598"></p><br><p><strong><font color='orange'>d、配置根目录<code>/</code></font></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221245884.png" alt="image-20251023221245884"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221339058.png" alt="image-20251023221339058"></p><br><p><strong><font color='orange'>e、分区配置完毕，点击完成，然后点击弹窗的接受更改</font></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221441203.png" alt="image-20251023221441203"></p><br><h4 id="4-3、配置kdump"><a href="#4-3、配置kdump" class="headerlink" title="4.3、配置kdump"></a><strong><font color='#10c300'>4.3、配置kdump</font></strong></h4><p>关闭 kdump 本身虚拟机内存就不够，他会吃掉一部分内存，我们尽量省一点</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023222056185.png" alt="image-20251023222056185"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023221802804.png" alt="image-20251023221802804"></p><br><h4 id="4-4、配置网络和主机名"><a href="#4-4、配置网络和主机名" class="headerlink" title="4.4、配置网络和主机名"></a><strong><font color='#10c300'>4.4、配置网络和主机名</font></strong></h4><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023222257482.png" alt="image-20251023222257482"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251023222457521.png" alt="image-20251023222457521"></p><br><h4 id="4-5、开始安装"><a href="#4-5、开始安装" class="headerlink" title="4.5、开始安装"></a><strong><font color='#10c300'>4.5、开始安装</font></strong></h4><h5 id="1）设置Root用户密码"><a href="#1）设置Root用户密码" class="headerlink" title="1）设置Root用户密码"></a><strong><font color='cornflowerblue'>1）设置Root用户密码</font></strong></h5><p>默认有个root用户，也就是超级管理员</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025162343490.png" alt="image-20251025162343490"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025162349989.png" alt="image-20251025162349989"></p><br><h5 id="2）创建用户"><a href="#2）创建用户" class="headerlink" title="2）创建用户"></a><strong><font color='cornflowerblue'>2）创建用户</font></strong></h5><p>新创建了一个zhb用户</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025162417872.png" alt="image-20251025162417872"></p><p>等待安装</p><br><h4 id="4-6、虚拟机的使用引导界面"><a href="#4-6、虚拟机的使用引导界面" class="headerlink" title="4.6、虚拟机的使用引导界面"></a><strong><font color='#10c300'>4.6、虚拟机的使用引导界面</font></strong></h4><p>重启进入引导界面</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163325360.png" alt="image-20251025163325360"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163408100.png" alt="image-20251025163408100"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163426606.png" alt="image-20251025163426606"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163508011.png" alt="image-20251025163508011"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163545397.png" alt="image-20251025163545397"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163555338.png" alt="image-20251025163555338"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163609171.png" alt="image-20251025163609171"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025163627645.png" alt="image-20251025163627645"></p><p>安装完成</p><br><h3 id="五、-切换-root-用户"><a href="#五、-切换-root-用户" class="headerlink" title="五、 切换 root 用户"></a><strong><font color='red'>五、 切换 root 用户</font></strong></h3><p>当前登录的用户是刚刚创建的用户，权限会缺少，所以使用 root，修改一些内容更加方便</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025165233836.png" alt="image-20251025165233836"><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025165459175.png" alt="image-20251025165459175"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025165518127.png" alt="image-20251025165518127"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251025165706320.png" alt="image-20251025165706320"></p><br><h3 id="六、-网络配置"><a href="#六、-网络配置" class="headerlink" title="六、 网络配置"></a><strong><font color='red'>六、 网络配置</font></strong></h3><p>linux系统下，查看网络配置IP地址</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ifconfig</span><br></pre></td></tr></table></figure><p>windows系统下，查看网络配置IP地址</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ipconfig</span><br></pre></td></tr></table></figure><p>测试你的计算机与目标主机（可以是域名或IP地址）之间的连通性</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ping + ip地址</span><br></pre></td></tr></table></figure><br><p><strong><font color='orange'>VMware提供了三种网络连接模式：</font></strong></p><ol><li>桥接模式：虚拟机直接连接外部物理网络的模式，主机起到了网桥的作用。在这种模式下，虚拟机可以直接访问外部网络，并且对外部网络是可见的</li><li>NAT模式：虚拟机和主机构建一个专用网络，并通过虚拟网络地址转换（NAT）设备对IP进行转换。虚拟机通过共享主机IP可以访问外部网络，但外部网络无法访问虚拟机。</li><li>仅主机模式：虚拟机只与主机共享一个专用网络，与外部网络无法通信</li></ol><p><strong><font color='orange'>获取到虚拟机的ip</font></strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201225649883.png" alt="image-20251201225649883"></p><br><h4 id="6-1、虚拟机修改静态ip"><a href="#6-1、虚拟机修改静态ip" class="headerlink" title="6.1、虚拟机修改静态ip"></a><strong><font color='#10c300'>6.1、虚拟机修改静态ip</font></strong></h4><h5 id="1）为什么要修改静态ip"><a href="#1）为什么要修改静态ip" class="headerlink" title="1）为什么要修改静态ip"></a><strong><font color='cornflowerblue'>1）为什么要修改静态ip</font></strong></h5><p>因为虚拟默认是动态ip的，每次重新都会更新ip的，因此需要修改成静态ip方便网络管理，比如你要通过SSH、远程桌面等方式连接虚拟机，固定IP更容易记忆和配置。</p><br><h5 id="2）修改静态ip"><a href="#2）修改静态ip" class="headerlink" title="2）修改静态ip"></a><strong><font color='cornflowerblue'>2）修改静态ip</font></strong></h5><p>Linux的所有配置文件放在 &#x2F;etc 目录下</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201225950902.png" alt="image-20251201225950902"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201230744458.png" alt="image-20251201230744458"></p><br><p>修改为BOOTPROTO&#x3D;”static”<br>在底部添加</p><figure class="highlight plaintext"><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">#IP地址</span><br><span class="line">IPADDR=192.168.244.100</span><br><span class="line">#网关</span><br><span class="line">GATEWAY=192.168.244.2</span><br><span class="line">#域名解析器</span><br><span class="line">DNS1=192.168.244.2</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201232035172.png" alt="image-20251201232035172"></p><p><strong>最后需要重启网络</strong>，注意：conterOS7和8的命令不一样注意区分，我这里是直接重启虚拟机</p><br><p><strong>这里的ip地址要和虚拟网络的VMnet8的子网IP的网段保持一致（就是前三位保持一致，后一位可自行命名）</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201231010889.png" alt="image-20251201231010889"></p><br><p><strong>网关和域名解析器则和VMnet8的NAT设置中的网关IP保持一致</strong></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201231230608.png" alt="image-20251201231230608"></p><br><h4 id="6-2、修改主机名"><a href="#6-2、修改主机名" class="headerlink" title="6.2、修改主机名"></a><strong><font color='#10c300'>6.2、修改主机名</font></strong></h4><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hostname  # 查看主机名</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201234837992.png" alt="image-20251201234837992"></p><br><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">vim /etc/hostname  # 修改hostname的配置，此方式需要重启才会生效</span><br></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hostnamectl set-hostname hadop100  # 此方式不需要重启，就可以生效 修改主机名为hadop100</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251201235132862.png" alt="image-20251201235132862"></p>]]></content>
    
    
      
      
    <summary type="html">&lt;p&gt;&lt;strong&gt;软件版本&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;VMware 17 Pro  17.5.2 build-23775571&lt;/p&gt;
&lt;p&gt;CentOS-8.1.1911-x86_64-dvd1.iso&lt;/p&gt;
&lt;/blockquote&gt;
&lt;</summary>
      
    
    
    
    <category term="后端与运维" scheme="http://example.com/categories/%E5%90%8E%E7%AB%AF%E4%B8%8E%E8%BF%90%E7%BB%B4/"/>
    
    
    <category term="CentOS 8" scheme="http://example.com/tags/CentOS-8/"/>
    
    <category term="虚拟机" scheme="http://example.com/tags/%E8%99%9A%E6%8B%9F%E6%9C%BA/"/>
    
    <category term="VMware17" scheme="http://example.com/tags/VMware17/"/>
    
  </entry>
  
  <entry>
    <title>Hexo搭建个人博客</title>
    <link href="http://example.com/posts/ec7d7221.html"/>
    <id>http://example.com/posts/ec7d7221.html</id>
    <published>2025-11-25T21:57:23.000Z</published>
    <updated>2026-04-02T08:39:33.921Z</updated>
    
    <content type="html"><![CDATA[<div align="center">    <img src="https://img.shields.io/badge/hexo-8.1.0-d1a8.svg"/>    <img src="https://img.shields.io/badge/hexoCli-4.3.2-green.svg"/>    <img src="https://img.shields.io/badge/Node-=22.21.0-ffbc39.svg"/>    <br>    <br>    <img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/202409131343278.png"/></div><br><h3 id="一、前置条件"><a href="#一、前置条件" class="headerlink" title="一、前置条件"></a><strong><font color='red'>一、前置条件</font></strong></h3><h4 id="1-1、要求✏️"><a href="#1-1、要求✏️" class="headerlink" title="1.1、要求✏️"></a><strong><font color='#10c300'>1.1、要求✏️</font></strong></h4><p>安装 Hexo 相当简单，只需要先安装下列应用程序即可：</p><ul><li><a href="http://nodejs.org/">Node.js</a> (Node.js 版本需不低于 10.13，建议使用 Node.js 12.0 及以上版本，本次使用的是18.18.0版本)</li><li><a href="http://git-scm.com/">Git</a></li></ul><p>如果您的电脑中已经安装上述必备程序，那么恭喜您！ 你可以直接前往 <a href="https://hexo.io/zh-cn/docs/#%E5%AE%89%E8%A3%85-Hexo">安装 Hexo</a> 步骤。</p><h4 id="1-2、安装-Hexo"><a href="#1-2、安装-Hexo" class="headerlink" title="1.2、安装 Hexo"></a><strong><font color='#10c300'>1.2、安装 Hexo</font></strong></h4><p>在电脑上全局安装Hexo</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g hexo-cli</span><br></pre></td></tr></table></figure><br><h3 id="二、创建博客"><a href="#二、创建博客" class="headerlink" title="二、创建博客"></a><strong><font color='red'>二、创建博客</font></strong></h3><p>安装 Hexo 完成后，请执行下列命令，Hexo 将会在指定文件夹中新建博客项目文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo init &lt;folder&gt;</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251030145443715.png" alt="image-20251030145443715"></p><figure class="highlight bash"><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">$ npm install</span><br><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251027230420967.png" alt="image-20251027230420614"></p><h4 id="2-1、目录结构"><a href="#2-1、目录结构" class="headerlink" title="2.1、目录结构"></a><strong><font color='#10c300'>2.1、目录结构</font></strong></h4><p>初始化后，您的项目文件夹将如下所示：</p><figure class="highlight plaintext"><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><br><span class="line">├── _config.yml</span><br><span class="line">├── package.json</span><br><span class="line">├── scaffolds</span><br><span class="line">├── source</span><br><span class="line">|   └── _posts</span><br><span class="line">└── themes</span><br></pre></td></tr></table></figure><h4 id="2-1、-config-yml"><a href="#2-1、-config-yml" class="headerlink" title="2.1、_config.yml"></a><strong><font color='#10c300'>2.1、_config.yml</font></strong></h4><p>网站的 <a href="https://hexo.io/zh-cn/docs/configuration">配置</a> 文件。 您可以在此配置大部分的参数。</p><br><h3 id="三、主题"><a href="#三、主题" class="headerlink" title="三、主题"></a><strong><font color='red'>三、主题</font></strong></h3><p><a href="https://github.com/shenliyang/hexo-theme-snippet">Hexo Themes列表</a></p><p>我们这次使用后是<a href="https://github.com/shenliyang/hexo-theme-snippet">Asnippet</a>主题</p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251027230809516.png" alt="image-20251027230809516"></p><h4 id="3-1、下载主题"><a href="#3-1、下载主题" class="headerlink" title="3.1、下载主题"></a><strong><font color='#10c300'>3.1、下载主题</font></strong></h4><p>有两种方式获取本主题–下载 <code>*.zip</code> 文件和通过 <code>git</code>方式：</p><ol><li>下载 <a href="https://github.com/shenliyang/hexo-theme-snippet">Snippet 主题</a> 文件解压后放在 <code>themes</code> 目录下，和博客中的 landscape 为同级目录</li><li>Git 方式，在 Hexo 根目录执行：</li></ol><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git <span class="built_in">clone</span> git://github.com/shenliyang/hexo-theme-snippet.git themes/hexo-theme-snippet</span><br></pre></td></tr></table></figure><br><h4 id="3-2、切换主题"><a href="#3-2、切换主题" class="headerlink" title="3.2、切换主题"></a><strong><font color='#10c300'>3.2、切换主题</font></strong></h4><p>在根目录的<code>_config.yml</code>文件中，找到 <code>theme: landscape</code>，将landscape替换成新的主题名</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">theme:</span> <span class="string">hexo-theme-snippet</span></span><br></pre></td></tr></table></figure><br><h4 id="3-3、安装主题插件"><a href="#3-3、安装主题插件" class="headerlink" title="3.3、安装主题插件"></a><strong><font color='#10c300'>3.3、安装主题插件</font></strong></h4><p>1️⃣因为 <strong>hexo-theme-snippet</strong> 使用了 <code>ejs</code> 模版引擎 、 <code>Less</code> CSS 预编译语言以及在官方插件的基础上 进行功能的开发，以下为必装插件：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pnpm i hexo-renderer-ejs hexo-renderer-less hexo-deployer-git -S</span><br></pre></td></tr></table></figure><p>2️⃣启用站内本地搜索功能</p><p>如果要使用本地站点搜索，必须安装插件 hexo-generator-json-content 来创建本地搜索 json 文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pnpm i hexo-generator-json-content@2.2.0 -S</span><br></pre></td></tr></table></figure><p>然后修改主题配置_config.yml 文件下<code>jsonContent</code>相关参数。</p><br><h4 id="3-4、部署主题"><a href="#3-4、部署主题" class="headerlink" title="3.4、部署主题"></a><strong><font color='#10c300'>3.4、部署主题</font></strong></h4><h5 id="1）未修改过主题源文件"><a href="#1）未修改过主题源文件" class="headerlink" title="1）未修改过主题源文件"></a><strong><font color='cornflowerblue'>1）未修改过主题源文件</font></strong></h5><p>1️⃣  清空 hexo 静态文件和缓存，并重新生成</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean &amp;&amp; hexo g  //清空缓存并生成静态文件</span><br></pre></td></tr></table></figure><p>2️⃣ 本地预览，确没有问题再进行发布</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo s -p 4000 或者 hexo s  //启动本地服务默认</span><br></pre></td></tr></table></figure><p>3️⃣ 当 gulp 执行完成，并提示 <code>please execute： hexo d</code> 时，可以进行发布</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo d 或者 gulp deploy  //部署发布</span><br></pre></td></tr></table></figure><br><h5 id="2）修改过主题源文件"><a href="#2）修改过主题源文件" class="headerlink" title="2）修改过主题源文件"></a><strong><font color='cornflowerblue'>2）修改过主题源文件</font></strong></h5><p>1️⃣ 拷贝主题目录下<code>package.json</code>文件到 Hexo 根目录下，然后安装项目的开发依赖。</p><p>如下是拷贝过来的，并删除了多余的代码。</p><figure class="highlight json"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;blog&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;0.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;private&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;module&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;deploy&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo deploy&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;hexo s&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gulp --gulpfile gulpfile.js&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;hexo&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="string">&quot;7.3.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;dependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;ejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.1.10&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.3.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-abbrlink&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.2.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-deployer-git&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-archive&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-category&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-feed&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-index&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-json-content&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.2.3&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-sitemap&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-generator-tag&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-renderer-ejs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-renderer-marked&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^7.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;hexo-server&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.0.0&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;devDependencies&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;gulp&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-autoprefixer&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^9.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-clean-css&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.3.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-htmlclean&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.7.22&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-htmlmin&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-jshint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.1.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-less&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-notify&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-path&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^4.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-plumber&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.2.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-rename&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-rev-append&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.1.8&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-sequence&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^1.0.0&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-uglify&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^3.0.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;gulp-watch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^5.0.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jshint&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.13.6&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;jshint-stylish&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^2.2.1&quot;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;notify-send&quot;</span><span class="punctuation">:</span> <span class="string">&quot;^0.1.2&quot;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>2️⃣ 在 Hexo 根目录下创建一个名为 gulpfile.js 的文件并引入主题中的gulpfile.mjs：</p><figure class="highlight js"><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="keyword">import</span> * <span class="keyword">as</span> gulpTasks <span class="keyword">from</span> <span class="string">&#x27;./themes/hexo-theme-snippet/gulpfile.mjs&#x27;</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">export</span> <span class="keyword">default</span> gulpTasks.<span class="property">default</span>;</span><br><span class="line"><span class="keyword">export</span> <span class="keyword">const</span> &#123; build, dev, watchFiles, compileLess &#125; = gulpTasks;</span><br></pre></td></tr></table></figure><p>3️⃣ 编译less→css</p><p>在 Hexo 根目录下package.json添加并执行</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">&quot;scripts&quot;: &#123;</span><br><span class="line">   &quot;compile-less&quot;: &quot;gulp --gulpfile themes/hexo-theme-snippet/gulpfile.mjs compileLess&quot;</span><br><span class="line"> &#125;,</span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pnpm run compile-less</span><br></pre></td></tr></table></figure><p>4️⃣  清空 hexo 静态文件和缓存，并重新生成</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean &amp;&amp; hexo g  //清空缓存并生成静态文件</span><br></pre></td></tr></table></figure><p>5️⃣ 运行 gulp：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pnpm run gulp</span><br></pre></td></tr></table></figure><ul><li><p>pnpm run gulp 执行的是gulpfile.js中的默认任务，该任务引用自主题目录中的gulpfile.mjs</p></li><li><p>gulp默认执行的build任务主要功能是优化已存在的public目录中的文件，包括：</p><ul><li>压缩CSS并添加前缀</li><li>压缩JS</li><li>添加版本号到HTML文件</li><li>压缩HTML文件</li></ul></li><li><p>但是，gulp任务是在 .&#x2F;public 目录下工作的，它 不会 自动生成public目录</p></li><li><p>在Hexo博客框架中，public目录是由 hexo g (hexo generate)命令生成的，该命令会将source目录中的Markdown文件等转换为静态HTML文件</p></li></ul><p>**🔚结论 ：**要完成完整的打包流程，你需要先执行 hexo g 来生成public目录，然后再执行 pnpm run gulp 来优化public目录中的文件</p><p>6️⃣ 本地预览，确没有问题再进行发布</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo s -p 4000 或者 hexo s  //启动本地服务默认</span><br></pre></td></tr></table></figure><p>7️⃣ 当 gulp 执行完成，并提示 <code>please execute： hexo d</code> 时，可以进行发布</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo d 或者 gulp deploy  //部署发布</span><br></pre></td></tr></table></figure><br><h5 id="3）拓展优化💡"><a href="#3）拓展优化💡" class="headerlink" title="3）拓展优化💡"></a><strong><font color='cornflowerblue'>3）拓展优化💡</font></strong></h5><p>1️⃣ 本地开发启动一个开发服务器，监听主题中的Less和JS文件变化，当文件发生变化时自动进行编译或校验，提高开发效率。</p><p>在 Hexo 根目录下package.json添加并执行</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;gulp:dev&quot;</span><span class="punctuation">:</span> <span class="string">&quot;gulp --gulpfile themes/hexo-theme-snippet/gulpfile.mjs watchFiles&quot;</span><span class="punctuation">,</span></span><br><span class="line"><span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ pnpm run <span class="string">&quot;gulp:dev&quot;</span></span><br></pre></td></tr></table></figure><p>开发者在修改样式或脚本文件后无需手动执行编译命令，系统会自动检测并处理文件变化</p><p>2️⃣优化指令</p><br><h4 id="3-5、更新主题"><a href="#3-5、更新主题" class="headerlink" title="3.5、更新主题"></a><strong><font color='#10c300'>3.5、更新主题</font></strong></h4><p>❌不建议更新，如果自己更改过主题源文件，更新主题会把自己改动的全部替换掉</p><p>主题可能会不定时优化和更新，更新主题代码：</p><figure class="highlight bash"><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="built_in">cd</span> themes/hexo-theme-snippet</span><br><span class="line">$ git pull</span><br></pre></td></tr></table></figure><br><h4 id="3-6、主题配置"><a href="#3-6、主题配置" class="headerlink" title="3.6、主题配置"></a><strong><font color='#10c300'>3.6、主题配置</font></strong></h4><figure class="highlight yaml"><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><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br><span class="line">174</span><br><span class="line">175</span><br><span class="line">176</span><br><span class="line">177</span><br><span class="line">178</span><br><span class="line">179</span><br><span class="line">180</span><br><span class="line">181</span><br><span class="line">182</span><br><span class="line">183</span><br><span class="line">184</span><br><span class="line">185</span><br><span class="line">186</span><br><span class="line">187</span><br><span class="line">188</span><br><span class="line">189</span><br><span class="line">190</span><br><span class="line">191</span><br><span class="line">192</span><br><span class="line">193</span><br><span class="line">194</span><br><span class="line">195</span><br><span class="line">196</span><br><span class="line">197</span><br><span class="line">198</span><br><span class="line">199</span><br><span class="line">200</span><br><span class="line">201</span><br><span class="line">202</span><br><span class="line">203</span><br><span class="line">204</span><br><span class="line">205</span><br><span class="line">206</span><br><span class="line">207</span><br><span class="line">208</span><br><span class="line">209</span><br><span class="line">210</span><br><span class="line">211</span><br><span class="line">212</span><br><span class="line">213</span><br><span class="line">214</span><br><span class="line">215</span><br><span class="line">216</span><br><span class="line">217</span><br><span class="line">218</span><br><span class="line">219</span><br><span class="line">220</span><br><span class="line">221</span><br><span class="line">222</span><br><span class="line">223</span><br><span class="line">224</span><br><span class="line">225</span><br><span class="line">226</span><br><span class="line">227</span><br><span class="line">228</span><br><span class="line">229</span><br><span class="line">230</span><br><span class="line">231</span><br><span class="line">232</span><br><span class="line">233</span><br><span class="line">234</span><br><span class="line">235</span><br><span class="line">236</span><br><span class="line">237</span><br><span class="line">238</span><br><span class="line">239</span><br><span class="line">240</span><br><span class="line">241</span><br><span class="line">242</span><br><span class="line">243</span><br><span class="line">244</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">## menu -- 导航菜单显示&#123;[@page:名字,@url:地址,@icon:图标]&#125;</span></span><br><span class="line"><span class="attr">menu:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">page:</span> <span class="string">home</span></span><br><span class="line">    <span class="attr">url:</span> <span class="string">/</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">fa-home</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## favicon -- 网站图标位置&#123;@favicon&#125;</span></span><br><span class="line"><span class="attr">favicon:</span> <span class="string">/favicon.ico</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## rss --rss文件位置&#123;@rss&#125;</span></span><br><span class="line"><span class="attr">rss:</span> <span class="string">/atom.xml</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 各个小工具的设置</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## widgets -- 6个左边小工具&#123;@widgets:[notification,category,archive,tagcloud,friends]&#125;</span></span><br><span class="line"><span class="attr">widgets:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">search</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">notification</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">social</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">category</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">archive</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">tagcloud</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">friends</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 各个小工具的设置</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 搜索</span></span><br><span class="line"><span class="attr">jsonContent:</span></span><br><span class="line">  <span class="attr">searchLocal:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">是否启用本地搜索</span></span><br><span class="line">  <span class="attr">searchGoogle:</span> <span class="literal">true</span> <span class="string">//是否启用谷歌搜索</span></span><br><span class="line">  <span class="attr">posts:</span></span><br><span class="line">    <span class="attr">title:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">text:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">content:</span> <span class="literal">true</span></span><br><span class="line">    <span class="attr">categories:</span> <span class="literal">false</span></span><br><span class="line">    <span class="attr">tags:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## notification config --网站公告设置,支持 html 和 纯文本</span></span><br><span class="line"><span class="attr">notification:</span> <span class="string">|-</span></span><br><span class="line"><span class="string">            &lt;p&gt;主题已经上线！欢迎下载或更新~ &lt;br/&gt;</span></span><br><span class="line"><span class="string">            主题下载：&lt;a href=&quot;https://github.com/shenliyang/hexo-theme-snippet&quot; title=&quot;fork me&quot; target=&quot;_blank&quot;&gt;Snippet主题&lt;/a&gt; &lt;br/&gt;</span></span><br><span class="line"><span class="string">            &lt;hr/&gt;接受贡献，包括不限于提交问题与需求，修复代码。欢迎Pull Request&lt;br/&gt;支持主题：&lt;a href=&quot;https://github.com/shenliyang/hexo-theme-snippet/stargazers&quot;&gt;Star一下&lt;/a&gt;&lt;/p&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="comment">## 社交设置&#123;@name:社交工具名字，@icon:社交工具图标，@href:设置工具链接&#125; [参考图标](http://fontawesome.io/icons/)</span></span><br><span class="line"><span class="attr">social:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="attr">name:</span> <span class="string">Github</span></span><br><span class="line">    <span class="attr">icon:</span> <span class="string">git</span></span><br><span class="line">    <span class="attr">href:</span> <span class="string">//github.com/shenliyang</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 文章分类设置&#123;@cate_config:&#123;@show_count:是否显示数字，@show_current: 是否高亮当前category&#125;&#125;</span></span><br><span class="line"><span class="attr">cate_config:</span></span><br><span class="line">   <span class="attr">show_count:</span> <span class="literal">true</span></span><br><span class="line">   <span class="attr">show_current:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 文章归档设置&#123;@arch_config:/*参数参考：https://hexo.io/zh-cn/docs/helpers.html#list-archives*/&#125;</span></span><br><span class="line"><span class="comment">## 推荐组合方式：[&#123;type: &#x27;monthly&#x27;,format: &#x27;YYYY年MM月&#x27;&#125;,&#123;type: &#x27;yearly&#x27;,format: &#x27;YYYY年&#x27;&#125;]</span></span><br><span class="line"><span class="attr">arch_config:</span></span><br><span class="line">   <span class="attr">type:</span> <span class="string">&#x27;monthly&#x27;</span></span><br><span class="line">   <span class="attr">format:</span> <span class="string">&#x27;YYYY年MM月&#x27;</span></span><br><span class="line">   <span class="attr">show_count:</span> <span class="literal">true</span></span><br><span class="line">   <span class="attr">order:</span> <span class="number">-1</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 标签云设置&#123;/*参数参考：http://www.goat1000.com/tagcanvas-options.php */&#125;</span></span><br><span class="line"><span class="attr">tagcloud:</span></span><br><span class="line">  <span class="attr">tag3d:</span> <span class="literal">false</span> <span class="string">//</span> <span class="string">是否启用3D标签云</span></span><br><span class="line">  <span class="attr">textColour:</span> <span class="string">&#x27;#444&#x27;</span> <span class="string">//</span> <span class="string">字体颜色</span></span><br><span class="line">  <span class="attr">outlineMethod:</span> <span class="string">&#x27;block&#x27;</span> <span class="string">//</span> <span class="string">选中模式(outline|classic|block|colour|size|none)</span></span><br><span class="line">  <span class="attr">outlineColour:</span> <span class="string">&#x27;#FFDAB9&#x27;</span> <span class="string">//</span> <span class="string">选中模式的颜色</span></span><br><span class="line">  <span class="attr">interval:</span> <span class="number">30</span> <span class="string">//</span> <span class="string">动画帧之间的时间间隔，值越大，转动幅度越大</span></span><br><span class="line">  <span class="attr">freezeActive:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">选中的标签是否继续滚动</span></span><br><span class="line">  <span class="attr">frontSelect:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">不选标签云后部的标签</span></span><br><span class="line">  <span class="attr">reverse:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">是否反向触发</span></span><br><span class="line">  <span class="attr">wheelZoom:</span> <span class="literal">false</span> <span class="string">//</span> <span class="string">是否启用鼠标滚轮</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 友链设置&#123;@链接名称：链接地址&#123;@links:[,,,]&#125;&#125;</span></span><br><span class="line"><span class="attr">links:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">Hexo官网:</span> <span class="string">https://hexo.io/zh-cn/</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment"># 主题自定义个性化配置</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 网站宣传语&#123;@branding：网站宣传语(不设置显示本地图片)&#125;</span></span><br><span class="line"><span class="attr">branding:</span> <span class="string">从未如此简单有趣</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 设置banner背景图片&#123;@img:自定义图片地址(支持绝对和相对路径),主题默认&#123;&quot;静态背景&quot;:&quot;banner.jpg&quot;&#125;,&#123;&quot;动态背景&quot;:&quot;banner2.jpg&quot;&#125;,&#123;&quot;动态星空背景&quot;:&quot;banner3.jpg&quot;&#125;&#125;</span></span><br><span class="line"><span class="comment">## 例如：https://hexo-theme-snippet-1251680922.cos.ap-beijing.myqcloud.com/img/banner|2|3.jpg, 或者 &#x27;./img/banner-img.jpg&#x27;(相对本地资源地址)</span></span><br><span class="line"><span class="attr">banner:</span></span><br><span class="line">  <span class="attr">img:</span> <span class="string">https://hexo-theme-snippet-1251680922.cos.ap-beijing.myqcloud.com/img/banner.jpg</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">## 设置carousel&#123;@img:图片地址,@url:点击跳转链接(默认值:&quot;javascript:&quot;)&#125;</span></span><br><span class="line"><span class="attr">carousel:</span></span><br><span class="line">  <span class="attr">img:</span> <span class="string">&#x27;img/head-img.jpg&#x27;</span></span><br><span class="line">  <span class="attr">url:</span> <span class="string">&#x27;javascript:&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 首页列表底部面板&#123;@homePanel: 是否开启&#125;</span></span><br><span class="line"><span class="attr">homePanel:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 首页文章列表缩略图</span></span><br><span class="line"><span class="comment">### 加载规则: 自定义文章缩略图(在Front-matter中添加的&#x27;img&#x27;字段) &gt; 文章内的图片 &gt; defaultImgs(随机获取) &gt; 无图模式列表</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 自定义随机图片</span></span><br><span class="line"><span class="attr">defaultImgs:</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">http://www.example.jpg</span> <span class="string">//远程图片链接示例</span></span><br><span class="line">  <span class="bullet">-</span> <span class="string">/img/default-1.jpg</span> <span class="string">//本地图片链接示例</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 文章摘要&#123;@摘要显示优先级：自定义摘要 &gt; 自动截取摘要 &#125;</span></span><br><span class="line"><span class="comment">### 自定义摘要范围&#123;@&lt;!--more--&gt;:截取more之前的内容为摘要&#125;</span></span><br><span class="line"><span class="comment">### 自动截取摘要&#123;@excerptLength:自动截取文章前多少个字为摘要，不配置默认：120字&#125;</span></span><br><span class="line"><span class="attr">excerptLength:</span> <span class="number">120</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 是否开启文章目录</span></span><br><span class="line"><span class="attr">toc:</span> <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 代码高亮配置&#123;@highlightTheme: 主题名称,(配置暂时不可用，后续开发中…)&#125;</span></span><br><span class="line"></span><br><span class="line"><span class="attr">highlightTheme:</span> <span class="string">default</span> <span class="string">//TODO</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 文章过期提醒功能 &#123;@warning:&#123;days:临界天数(默认300天,设置0关闭功能),text:提醒文字/*%d为过期总天数占位符*/&#125;&#125;</span></span><br><span class="line"><span class="attr">warning:</span></span><br><span class="line">  <span class="attr">days:</span> <span class="number">300</span></span><br><span class="line">  <span class="attr">text:</span> <span class="string">&#x27;本文于%d天之前发表，文中内容可能已经过时。&#x27;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 文章内声明&#123;@declaration: &#123;enable:是否开启,title:声明标题,tip:提示内容&#125;&#125;</span></span><br><span class="line"><span class="attr">declaration:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">title:</span> <span class="string">&#x27;转载声明&#x27;</span></span><br><span class="line">  <span class="attr">tip:</span> <span class="string">|-</span></span><br><span class="line"><span class="string">      商业转载请联系作者获得授权,非商业转载请注明出处 © &lt;a href=&quot;&quot; target=&quot;_blank&quot;&gt;Snippet&lt;/a&gt;</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="comment">## 文章打赏&#123;@reward: &#123;alipay:支付宝打赏,wepay:微信打赏,tip:打赏提示语; 链接都为空,关闭打赏功能&#125;&#125;</span></span><br><span class="line"><span class="attr">reward:</span></span><br><span class="line">  <span class="attr">alipay:</span> <span class="string">&#x27;&#x27;</span></span><br><span class="line">  <span class="attr">wepay:</span> <span class="string">&#x27;../img/reward-wepay.jpg&#x27;</span></span><br><span class="line">  <span class="attr">tip:</span> <span class="string">赞赏是不耍流氓的鼓励</span></span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="comment">## 主题评论</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## utterances评论: 一款基于 GitHub issues 的评论工具; 首先在 github 上进行安装 utterances，访问 [utterances应用程序](https://github.com/apps/utterances)；然后在主题内配置 [utterances更多配置](https://utteranc.es/)</span></span><br><span class="line"><span class="attr">utterances:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">true</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">shenliyang/snippet-comment</span> <span class="string">//</span> <span class="string">github仓库名字,</span> <span class="string">格式为</span> <span class="string">user-name/repo-name</span></span><br><span class="line">  <span class="attr">issueTerm:</span> <span class="string">pathname</span> <span class="string">//</span> <span class="string">标识issue类型</span> <span class="number">1</span><span class="string">.</span> <span class="string">pathname(推荐);</span> <span class="number">2</span><span class="string">.</span> <span class="string">url;</span> <span class="number">3.</span><span class="string">title;</span> <span class="number">3</span><span class="string">.</span> <span class="string">og:title;</span> <span class="number">4</span><span class="string">.</span> <span class="string">issue-number</span> <span class="number">5</span><span class="string">.</span> <span class="string">specific-term;</span></span><br><span class="line">  <span class="attr">issueNumber:</span> <span class="number">123</span> <span class="string">//</span> <span class="string">非必填，当配置</span> <span class="string">issueTerm</span> <span class="string">=</span> <span class="string">&quot;issue-number&quot;</span><span class="string">时，需要配置issue号</span></span><br><span class="line">  <span class="attr">theme:</span> <span class="string">github-light</span> <span class="string">//</span> <span class="string">主题配置</span> <span class="number">1</span><span class="string">.</span> <span class="string">github-light(推荐);</span> <span class="number">2</span><span class="string">.</span> <span class="string">github-dark;</span> <span class="number">3</span><span class="string">.</span> <span class="string">preferred-color-scheme;</span> <span class="number">4</span><span class="string">.</span> <span class="string">github-dark-orange;</span> <span class="number">5</span><span class="string">.</span> <span class="string">icy-dark;</span> <span class="number">6</span><span class="string">.</span> <span class="string">dark-blue;</span> <span class="number">7</span><span class="string">.</span> <span class="string">photon-dark;</span> <span class="number">8</span><span class="string">.</span> <span class="string">boxy-light</span></span><br><span class="line">  <span class="attr">label:</span> <span class="string">//</span> <span class="string">非必填</span></span><br><span class="line">  <span class="attr">cdn:</span> <span class="string">//</span> <span class="string">使用自定义utteranc脚本加载。</span> <span class="string">非必填，默认：&quot;https://utteranc.es/client.js&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### gitment</span></span><br><span class="line"><span class="attr">gitment:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">owner:</span></span><br><span class="line">  <span class="attr">repo:</span></span><br><span class="line">  <span class="attr">client_id:</span></span><br><span class="line">  <span class="attr">client_secret:</span></span><br><span class="line">  <span class="attr">labels:</span></span><br><span class="line">  <span class="attr">perPage:</span></span><br><span class="line">  <span class="attr">maxCommentHeight:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 来必力</span></span><br><span class="line"><span class="attr">livere:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">livere_uid:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 友言评论(服务不稳定，经常无法加载)</span></span><br><span class="line"><span class="attr">uyan:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">uyan_id:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### Disqus评论(需要翻墙，或者搭建代理)</span></span><br><span class="line"><span class="attr">disqus:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">shortname:</span> <span class="string">snippet</span></span><br><span class="line">  <span class="attr">count:</span> <span class="literal">false</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 畅言评论(需要ICP备案)</span></span><br><span class="line"><span class="attr">changyan:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">appid:</span></span><br><span class="line">  <span class="attr">conf:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### Valine评论(leancloud需要实名认证) 参考网站: [valine评论](https://valine.js.org/)</span></span><br><span class="line"><span class="attr">valine:</span></span><br><span class="line">  <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">  <span class="attr">appId:</span></span><br><span class="line">  <span class="attr">appKey:</span></span><br><span class="line">  <span class="attr">placeholder:</span> <span class="string">说点什么吧</span></span><br><span class="line">  <span class="attr">notify:</span> <span class="literal">false</span> <span class="string">//</span> <span class="string">邮件通知</span></span><br><span class="line">  <span class="attr">verify:</span> <span class="literal">false</span> <span class="string">//</span> <span class="string">验证码</span></span><br><span class="line">  <span class="attr">avatar:</span> <span class="string">mm</span> <span class="string">//</span> <span class="string">avatar头像</span></span><br><span class="line">  <span class="attr">meta:</span> <span class="string">nick,mail</span> <span class="string">//</span> <span class="string">输入框内容，可选值nick,mail,link</span></span><br><span class="line">  <span class="attr">pageSize:</span> <span class="number">10</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## Gitalk评论 参考网站: [一个基于Github Issue和Preact开发的评论插件](https://gitalk.github.io/)</span></span><br><span class="line"><span class="attr">gitalk:</span></span><br><span class="line">   <span class="attr">enable:</span> <span class="literal">false</span></span><br><span class="line">   <span class="attr">clientID:</span> <span class="string">&quot;&quot;</span> <span class="string">//</span> <span class="string">Github</span> <span class="string">应用ID</span></span><br><span class="line">   <span class="attr">clientSecret:</span> <span class="string">&quot;&quot;</span> <span class="string">//</span> <span class="string">Github</span> <span class="string">应用密钥</span></span><br><span class="line">   <span class="attr">repo:</span> <span class="string">shenliyang.github.io</span> <span class="string">//</span> <span class="string">Github仓库地址</span></span><br><span class="line">   <span class="attr">owner:</span> <span class="string">shenliyang</span>  <span class="string">//</span> <span class="string">Github</span> <span class="string">用户名(Github仓库拥有者)</span></span><br><span class="line">   <span class="attr">admin:</span> <span class="string">shenliyang</span> <span class="string">//</span> <span class="string">GitHub</span> <span class="string">repository</span> <span class="string">的所有者和合作者</span> <span class="string">(对这个</span> <span class="string">repository</span> <span class="string">有写权限的用户)可以有一个或多个，如果有多名可使用，例如：admin:</span> <span class="string">admin1,admin2</span> <span class="string">配置</span></span><br><span class="line">   <span class="attr">perPage:</span> <span class="number">10</span> <span class="string">//</span> <span class="string">每次加载的数据大小，最多100</span></span><br><span class="line">   <span class="attr">distractionFreeMode:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">是否启用无干扰模式，类似Facebook评论框的全屏遮罩效果</span></span><br><span class="line"></span><br><span class="line">   <span class="string">//</span> <span class="string">以下参数主题会默认处理，不需要配置</span></span><br><span class="line">   <span class="string">language</span> <span class="string">//</span> <span class="string">语言类型，默认为站点配置中选项</span></span><br><span class="line">   <span class="string">id</span> <span class="string">//</span> <span class="string">页面的唯一标识,</span> <span class="string">已使用md5对pathname转换生成唯一id处理</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 网站访客统计 [不蒜子统计](http://busuanzi.ibruce.info/)</span></span><br><span class="line"><span class="attr">visit_counter:</span></span><br><span class="line">   <span class="attr">site:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">总访问量和访问人数统计</span></span><br><span class="line">   <span class="attr">page:</span> <span class="literal">true</span> <span class="string">//</span> <span class="string">文章阅读量统计</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 网站访问统计</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 网盟CNZZ统计 参考网站: [网盟CNZZ](http://www.umeng.com/)</span></span><br><span class="line"><span class="attr">cnzz_anaylytics:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 百度统计 参考网站: [百度统计](https://tongji.baidu.com/)</span></span><br><span class="line"><span class="attr">baidu_anaylytics:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 谷歌统计 参考网站：[谷歌统计](https://www.google-analytics.com/)</span></span><br><span class="line"><span class="attr">google_anaylytics:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 腾讯分析 参考网站：[腾讯分析](http://ta.qq.com/)</span></span><br><span class="line"><span class="attr">tencent_analytics:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 百度站点认证</span></span><br><span class="line"><span class="attr">baidu-site-verification:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">### 百度自动推送(@baidu_push: 是否启用百度自动推送)  参考网站: [百度站长资源](https://ziyuan.baidu.com/college/courseinfo?id=267&amp;page=2#h2_article_title18)</span></span><br><span class="line"><span class="attr">baidu_push:</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## ICON配置 (不配则启用本地Font Icon)</span></span><br><span class="line"><span class="attr">fontAwesome:</span> <span class="string">//cdn.bootcss.com/font-awesome/4.7.0/css/font-awesome.min.css</span></span><br><span class="line"></span><br><span class="line"><span class="comment">## 网站主题配置</span></span><br><span class="line"><span class="attr">since:</span> <span class="number">2017</span>  <span class="string">//建站时间</span></span><br><span class="line"><span class="attr">beian:</span> <span class="string">&#x27;京ICP备04000001号&#x27;</span> <span class="string">//网站备案号</span></span><br><span class="line"><span class="attr">robot:</span> <span class="string">&#x27;all&#x27;</span>  <span class="string">//控制搜索引擎的抓取和索引编制行为，默认为all</span></span><br><span class="line"><span class="attr">version:</span> <span class="number">1.3</span><span class="number">.0</span>  <span class="string">//当前主题版本号</span></span><br></pre></td></tr></table></figure><br><h4 id="3-7、主题使用技巧及功能扩展"><a href="#3-7、主题使用技巧及功能扩展" class="headerlink" title="3.7、主题使用技巧及功能扩展"></a><strong><font color='#10c300'>3.7、主题使用技巧及功能扩展</font></strong></h4><p>修改新增文章 Front-matter 模板,修改<code>scaffolds</code>目录下的<code>post.md</code>模板</p><figure class="highlight yaml"><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="meta">---</span></span><br><span class="line"><span class="attr">title:</span> &#123;&#123; <span class="string">title</span> &#125;&#125; <span class="string">//</span> <span class="string">标题</span></span><br><span class="line"><span class="attr">date:</span> &#123;&#123; <span class="string">date</span> &#125;&#125;   <span class="string">//</span> <span class="string">时间</span></span><br><span class="line"><span class="attr">categories:</span> [<span class="string">&#x27;分类1&#x27;</span>,<span class="string">&#x27;分类2&#x27;</span>] <span class="string">//</span> <span class="string">分类</span></span><br><span class="line"><span class="attr">tags:</span> [<span class="string">&#x27;标签1&#x27;</span>,<span class="string">&#x27;标签2&#x27;</span>]       <span class="string">//</span> <span class="string">标签</span></span><br><span class="line"><span class="attr">comments:</span> <span class="literal">false</span>    <span class="string">//</span> <span class="string">是否开启评论</span></span><br><span class="line"><span class="attr">img:</span>               <span class="string">//</span> <span class="string">自定义缩略图</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><br><h3 id="四、自定义路由-导航"><a href="#四、自定义路由-导航" class="headerlink" title="四、自定义路由(导航)"></a><strong><font color='red'>四、自定义路由(导航)</font></strong></h3><p>以我们这次<a href="https://github.com/shenliyang/hexo-theme-snippet">Asnippet</a>主题为例</p><p><a href="#aa">前置元数据详解</a>(前置知识，必先：ctrl+左键点击查看)</p><h4 id="4-1、配置文件中添加导航项"><a href="#4-1、配置文件中添加导航项" class="headerlink" title="4.1、配置文件中添加导航项"></a><strong><font color='#10c300'>4.1、配置文件中添加导航项</font></strong></h4><ul><li>主题配置文件中编辑 themes&#x2F;hexo-theme-snippet&#x2F;_config.yml</li><li>在 menu 部分添加新的菜单项（分类&#x2F;关于），格式如下：</li></ul><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251028185224676.png" alt="image-20251028185224676"></p><h4 id="4-2、创建页面内容"><a href="#4-2、创建页面内容" class="headerlink" title="4.2、创建页面内容"></a><strong><font color='#10c300'>4.2、创建页面内容</font></strong></h4><p><strong><font color='orange'>1）创建的页面就是以.md文件内容为展示的路由页面</font></strong></p><ul><li><p>在Hexo项目的根目录的source目录下创建对应的文件夹和文件</p><p>例如，要创建 <code>/about/</code>路由，需要在 source 目录下创建 <code>source\about\index.md</code>文件</p></li><li><p>在 index.md 中添加页面内容和前置元数据</p><figure class="highlight yaml"><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="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">关于我</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2025-10-28 16:03:32</span></span><br><span class="line"><span class="meta">---</span></span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251029000623224.png" alt="image-20251029000623224"></p></li></ul><p><strong><font color='orange'>2）创建自定义页面模板</font></strong></p><ul><li><p>如果需要自定义页面模板，可以在<code>themes\hexo-theme-snippet\layout\</code>目录下创建新的<code>.ejs</code> 文件（<code>categories.ejs</code>）</p></li><li><p>在页面的前置元数据中指定布局文件。例如在layout文件夹中创建<code>categories.ejs</code>文件，那么在元数据中layout的属性值就得设置成<code>categories</code>和layout文件夹中创建的<code>.ejs</code> 文件名保持一致</p><figure class="highlight plaintext"><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><br><span class="line">title: 分类</span><br><span class="line">date: 2025-10-28 16:15:28</span><br><span class="line">layout: categories</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251029001736475.png" alt="image-20251029001736475"></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251029000804848.png" alt="image-20251029000804848"></p></li></ul><br><h3 id="五、前置元数据-Front-matter"><a href="#五、前置元数据-Front-matter" class="headerlink" title="五、前置元数据(Front-matter)"></a><strong><font color='red'>五、前置元数据(Front-matter)</font></strong></h3><p>在Hexo中，页面的前置元数据（Front-matter）和内容的组织是创建自定义页面的关键。</p><p>前置元数据是位于文件顶部、由 — 包围的YAML或JSON格式数据，它定义了页面的各种属性。</p><h4 id="5-1、基本结构"><a href="#5-1、基本结构" class="headerlink" title="5.1、基本结构"></a><strong><font color='#10c300'>5.1、基本结构</font></strong></h4><figure class="highlight yaml"><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="meta">---</span></span><br><span class="line"><span class="attr">title:</span> <span class="string">页面标题</span></span><br><span class="line"><span class="attr">date:</span> <span class="number">2025-10-28 16:03:32</span></span><br><span class="line"><span class="attr">layout:</span> <span class="string">布局名称</span></span><br><span class="line"><span class="comment"># 其他属性...</span></span><br><span class="line"><span class="meta">---</span></span><br><span class="line"><span class="meta"></span></span><br><span class="line"><span class="comment"># 这里开始是页面内容</span></span><br></pre></td></tr></table></figure><h4 id="5-2、常用前置元数据属性"><a href="#5-2、常用前置元数据属性" class="headerlink" title="5.2、常用前置元数据属性"></a><strong><font color='#10c300'>5.2、常用前置元数据属性</font></strong></h4><p>根据您的项目中的示例，以下是常用的属性：</p><ol><li>title : 页面标题</li><li>date : 创建日期，格式为 YYYY-MM-DD HH:mm:ss</li><li>layout : 指定使用的布局模板（如 categories ）</li><li>comments : 是否开启评论（true&#x2F;false）</li><li>categories : 分类</li><li>tags : 标签</li><li>permalink : 自定义永久链接</li></ol><br><h3 id="六、博客部署到Github-Pages"><a href="#六、博客部署到Github-Pages" class="headerlink" title="六、博客部署到Github Pages"></a><strong><font color='red'>六、博客部署到Github Pages</font></strong></h3><h4 id="6-1、新建仓库"><a href="#6-1、新建仓库" class="headerlink" title="6.1、新建仓库"></a><strong><font color='#10c300'>6.1、新建仓库</font></strong></h4><p>创建一个和你用户名相同的仓库，后面加 <code>.github.io</code></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251031115006280.png" alt="image-20251031115006280"></p><h4 id="6-2、实现免密登录"><a href="#6-2、实现免密登录" class="headerlink" title="6.2、实现免密登录"></a><strong><font color='#10c300'>6.2、实现免密登录</font></strong></h4><p><strong>为了实现安全的免密访问 GitHub 仓库</strong>，也就是在你每次部署或推送内容到 GitHub Pages 时，不再需要输入账号密码，同时保证传输是安全加密的。</p><h5 id="1）配置-Git-用户信息"><a href="#1）配置-Git-用户信息" class="headerlink" title="1）配置 Git 用户信息"></a><strong><font color='cornflowerblue'>1）配置 Git 用户信息</font></strong></h5><figure class="highlight bash"><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">git config --global user.name <span class="string">&quot;yourname&quot;</span></span><br><span class="line">git config --global user.email <span class="string">&quot;youremail&quot;</span></span><br></pre></td></tr></table></figure><p>这两条命令不是用来登录的，而是：</p><ul><li><p>告诉 Git <strong>是谁在提交代码</strong>。</p></li><li><p>每一次 commit 都会写入这两个信息，形成类似：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Author: yourname &lt;youremail&gt;</span><br></pre></td></tr></table></figure></li></ul><blockquote><p> <strong>🚨注意：</strong><br> 这个邮箱最好与你 GitHub 账号绑定的邮箱一致，否则 GitHub 无法正确识别为你的提交</p></blockquote><p>查看当前配置：</p><figure class="highlight bash"><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">bashgit config user.name</span><br><span class="line">git config user.email</span><br></pre></td></tr></table></figure><h5 id="2）生成-SSH-密钥"><a href="#2）生成-SSH-密钥" class="headerlink" title="2）生成 SSH 密钥"></a><strong><font color='cornflowerblue'>2）生成 SSH 密钥</font></strong></h5><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ssh-keygen -t rsa -C <span class="string">&quot;yourEmail&quot;</span></span><br></pre></td></tr></table></figure><p>这条命令是<strong>创建一对 SSH 密钥</strong>（key pair）：</p><ul><li><strong>私钥 (<code>id_rsa</code>)</strong>：保存在你电脑上，不要泄露。</li><li><strong>公钥 (<code>id_rsa.pub</code>)</strong>：可以安全地上传到 GitHub（或其他服务器）。</li></ul><p>然后你会发现 <code>C:\Users\19584\</code> 下有一个 <code>.ssh</code> 目录，如果没有，打开隐藏文件夹，<code>.ssh</code> 文件夹下有 <code>id_rsa</code> 和 <code>id_rsa.pub</code>，前一个是私钥，后一个是公钥</p><h5 id="3）配置到-GitHub"><a href="#3）配置到-GitHub" class="headerlink" title="3）配置到 GitHub"></a><strong><font color='cornflowerblue'>3）配置到 GitHub</font></strong></h5><p>将 <code>id_rsa.pub</code> 内的内容复制下来，然后进入 GitHub，点击 <code>Settings</code>，进入 <code>SSH and GPG keys</code> 选项，点击 <code>New SSH key</code>，然后填写 <code>Title: Blog</code>，<code>Key:你刚刚复制的公钥</code></p><p><img src="https://picgo-2024.oss-cn-beijing.aliyuncs.com/img/image-20251031140039333.png" alt="image-20251031140039333"></p><h4 id="6-3、一键部署"><a href="#6-3、一键部署" class="headerlink" title="6.3、一键部署"></a><strong><font color='#10c300'>6.3、一键部署</font></strong></h4><ol><li><p>安装 <a href="https://github.com/hexojs/hexo-deployer-git">hexo-deployer-git</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">pnpm add hexo-deployer-git</span><br></pre></td></tr></table></figure></li><li><p>在根目录 <code>_config.yml</code> 中添加以下配置（如果配置已经存在，请将其替换为如下）:</p></li></ol><figure class="highlight yaml"><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="attr">deploy:</span></span><br><span class="line">  <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line">  <span class="attr">repo:</span> <span class="string">https://github.com/&lt;username&gt;/&lt;username&gt;.github.io</span></span><br><span class="line">  <span class="comment"># example, https://github.com/hexojs/hexojs.github.io</span></span><br><span class="line">  <span class="attr">branch:</span> <span class="string">main</span>  <span class="string">//</span> <span class="string">和你创建的&lt;username&gt;.github.io创库的主分支保持一致</span></span><br></pre></td></tr></table></figure><ul><li>执行 <code>hexo clean &amp;&amp; hexo deploy</code> 。</li><li>浏览 <em>username</em>.github.io，检查你的网站能否运作。</li></ul><p>添加组合命令</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">&quot;scripts&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">   <span class="attr">&quot;push&quot;</span><span class="punctuation">:</span> <span class="string">&quot;pnpm run compile-less &amp;&amp; pnpm run del &amp;&amp; pnpm run clean &amp;&amp; pnpm run build &amp;&amp; pnpm run gulp &amp;&amp; pnpm run deploy&quot;</span><span class="punctuation">,</span></span><br><span class="line"> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br></pre></td></tr></table></figure>]]></content>
    
    
      
      
    <summary type="html">&lt;div align=&quot;center&quot;&gt;
    &lt;img src=&quot;https://img.shields.io/badge/hexo-8.1.0-d1a8.svg&quot;/&gt;
    &lt;img src=&quot;https://img.shields.io/badge/hexoCli-4.</summary>
      
    
    
    
    <category term="其他" scheme="http://example.com/categories/%E5%85%B6%E4%BB%96/"/>
    
    
    <category term="hexo" scheme="http://example.com/tags/hexo/"/>
    
  </entry>
  
</feed>
